一个非常好的从jar文件中加载so动态库方法,在android的gif支持开源中用到。这个项目的gif解码是用jni c实现的,避免了OOM等问题。
项目地址:https://github.com/koral--/android-gif-drawable
如果是把java文件生成jar。jni生成的so文件放到使用apk的libs/armeabi/lib_gif.so.....
gifExample.apk:
/libs/gif.jar
/libs/armeabi/lib_gi.so
这样做会报错,提示xml里面找不到GifImageView。
只能用项目之间依赖,so会自动进入生成的apk,不用拷贝。
调用方法:
//开始调用: static { LibraryLoader.loadLibrary(null, LibraryLoader.BASE_LIBRARY_NAME); }
进入这里:
package pl.droidsonroids.gif; import android.content.Context; import android.support.annotation.NonNull; import java.lang.reflect.Method; /** * Helper used to work around native libraries loading on some systems. * See <a href="https://medium.com/keepsafe-engineering/the-perils-of-loading-native-libraries-on-android-befa49dce2db">ReLinker</a> for more details. */ public class LibraryLoader { static final String SURFACE_LIBRARY_NAME = "pl_droidsonroids_gif_surface"; static final String BASE_LIBRARY_NAME = "pl_droidsonroids_gif"; private static Context sAppContext; /** * Intitializes loader with given `Context`. Subsequent calls should have no effect since application Context is retrieved. * Libraries will not be loaded immediately but only when needed. * @param context any Context except null */ public static void initialize(@NonNull final Context context) { sAppContext = context.getApplicationContext(); } static Context getContext() { if (sAppContext == null) { try { final Class<?> activityThread = Class.forName("android.app.ActivityThread"); final Method currentApplicationMethod = activityThread.getDeclaredMethod("currentApplication"); sAppContext = (Context) currentApplicationMethod.invoke(null); } catch (Exception e) { throw new RuntimeException("LibraryLoader not initialized. Call LibraryLoader.initialize() before using library classes.", e); } } return sAppContext; } static void loadLibrary(Context context, final String library) { try { System.loadLibrary(library); } catch (final UnsatisfiedLinkError e) { if (SURFACE_LIBRARY_NAME.equals(library)) { loadLibrary(context, BASE_LIBRARY_NAME); } if (context == null) { context = getContext(); } ReLinker.loadLibrary(context, library); } } }
最终到这里:
1 /** 2 * Copyright 2015 KeepSafe Software, Inc. 3 * <p/> 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * <p/> 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * <p/> 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 package pl.droidsonroids.gif; 17 18 import android.annotation.SuppressLint; 19 import android.content.Context; 20 import android.content.pm.ApplicationInfo; 21 import android.os.Build; 22 23 import java.io.Closeable; 24 import java.io.File; 25 import java.io.FileOutputStream; 26 import java.io.FilenameFilter; 27 import java.io.IOException; 28 import java.io.InputStream; 29 import java.io.OutputStream; 30 import java.util.zip.ZipEntry; 31 import java.util.zip.ZipFile; 32 33 /** 34 * Based on https://github.com/KeepSafe/ReLinker 35 * ReLinker is a small library to help alleviate {@link UnsatisfiedLinkError} exceptions thrown due 36 * to Android's inability to properly install / load native libraries for Android versions before 37 * API 21 38 */ 39 class ReLinker { 40 private static final String LIB_DIR = "lib"; 41 private static final int MAX_TRIES = 5; 42 private static final int COPY_BUFFER_SIZE = 8192; 43 44 private ReLinker() { 45 // No instances 46 } 47 48 /** 49 * Utilizes the regular system call to attempt to load a native library. If a failure occurs, 50 * then the function extracts native .so library out of the app's APK and attempts to load it. 51 * <p/> 52 * <strong>Note: This is a synchronous operation</strong> 53 */ 54 static void loadLibrary(Context context, final String library) { 55 final String libName = System.mapLibraryName(library); 56 synchronized (ReLinker.class) { 57 final File workaroundFile = unpackLibrary(context, libName); 58 System.load(workaroundFile.getAbsolutePath()); 59 } 60 } 61 62 /** 63 * Attempts to unpack the given library to the workaround directory. Implements retry logic for 64 * IO operations to ensure they succeed. 65 * 66 * @param context {@link Context} to describe the location of the installed APK file 67 * @param libName The name of the library to load 68 */ 69 private static File unpackLibrary(final Context context, final String libName) { 70 File outputFile = new File(context.getDir(LIB_DIR, Context.MODE_PRIVATE), libName);// + BuildConfig.VERSION_NAME); 71 if (outputFile.isFile()) { 72 return outputFile; 73 } 74 75 final File cachedLibraryFile = new File(context.getCacheDir(), libName );//+ BuildConfig.VERSION_NAME); 76 if (cachedLibraryFile.isFile()) { 77 return cachedLibraryFile; 78 } 79 80 final FilenameFilter filter = new FilenameFilter() { 81 @Override 82 public boolean accept(File dir, String filename) { 83 return filename.startsWith(libName); 84 } 85 }; 86 clearOldLibraryFiles(outputFile, filter); 87 clearOldLibraryFiles(cachedLibraryFile, filter); 88 89 final ApplicationInfo appInfo = context.getApplicationInfo(); 90 final File apkFile = new File(appInfo.sourceDir); 91 ZipFile zipFile = null; 92 try { 93 zipFile = openZipFile(apkFile); 94 95 int tries = 0; 96 while (tries++ < MAX_TRIES) { 97 ZipEntry libraryEntry = getLibraryEntry(libName, zipFile); 98 99 InputStream inputStream = null; 100 FileOutputStream fileOut = null; 101 try { 102 inputStream = zipFile.getInputStream(libraryEntry); 103 fileOut = new FileOutputStream(outputFile); 104 copy(inputStream, fileOut); 105 } catch (IOException e) { 106 if (tries > MAX_TRIES / 2) { 107 outputFile = cachedLibraryFile; 108 } 109 continue; 110 } finally { 111 closeSilently(inputStream); 112 closeSilently(fileOut); 113 } 114 setFilePermissions(outputFile); 115 break; 116 } 117 } finally { 118 closeSilently(zipFile); 119 } 120 return outputFile; 121 } 122 123 @SuppressWarnings("deprecation") //required for old API levels 124 private static ZipEntry getLibraryEntry(final String libName, final ZipFile zipFile) { 125 String jniNameInApk; 126 127 ZipEntry libraryEntry = null; 128 // if (Build.VERSION.SDK_INT >= 21 && Build.SUPPORTED_ABIS.length > 0) { 129 // for (final String ABI : Build.SUPPORTED_ABIS) { 130 // jniNameInApk = "lib/" + ABI + "/" + libName; 131 // libraryEntry = zipFile.getEntry(jniNameInApk); 132 // 133 // if (libraryEntry != null) { 134 // break; 135 // } 136 // } 137 // } else 138 139 { 140 jniNameInApk = "lib/" + Build.CPU_ABI + "/" + libName; 141 libraryEntry = zipFile.getEntry(jniNameInApk); 142 } 143 144 if (libraryEntry == null) { 145 throw new IllegalStateException("Library " + libName + " for supported ABIs not found in APK file"); 146 } 147 return libraryEntry; 148 } 149 150 private static ZipFile openZipFile(final File apkFile) { 151 int tries = 0; 152 ZipFile zipFile = null; 153 while (tries++ < MAX_TRIES) { 154 try { 155 zipFile = new ZipFile(apkFile, ZipFile.OPEN_READ); 156 break; 157 } catch (IOException ignored) { 158 } 159 } 160 161 if (zipFile == null) { 162 throw new RuntimeException("Could not open APK file: " + apkFile.getAbsolutePath()); 163 } 164 return zipFile; 165 } 166 167 @SuppressWarnings("ResultOfMethodCallIgnored") //intended, nothing useful can be done 168 private static void clearOldLibraryFiles(final File outputFile, final FilenameFilter filter) { 169 final File[] fileList = outputFile.getParentFile().listFiles(filter); 170 if (fileList != null) { 171 for (File file : fileList) { 172 file.delete(); 173 } 174 } 175 } 176 177 @SuppressWarnings("ResultOfMethodCallIgnored") //intended, nothing useful can be done 178 @SuppressLint("SetWorldReadable") //intended, default permission 179 private static void setFilePermissions(File outputFile) { 180 // Try change permission to rwxr-xr-x 181 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.GINGERBREAD) { 182 outputFile.setReadable(true, false); 183 outputFile.setExecutable(true, false); 184 outputFile.setWritable(true); 185 } 186 } 187 188 /** 189 * Copies all data from an {@link InputStream} to an {@link OutputStream}. 190 * 191 * @param in The stream to read from. 192 * @param out The stream to write to. 193 * @throws IOException when a stream operation fails. 194 */ 195 private static void copy(InputStream in, OutputStream out) throws IOException { 196 final byte[] buf = new byte[COPY_BUFFER_SIZE]; 197 while (true) { 198 final int bytesRead = in.read(buf); 199 if (bytesRead == -1) { 200 break; 201 } 202 out.write(buf, 0, bytesRead); 203 } 204 } 205 206 /** 207 * Closes a {@link Closeable} silently (without throwing or handling any exceptions) 208 * 209 * @param closeable {@link Closeable} to close 210 */ 211 private static void closeSilently(final Closeable closeable) { 212 try { 213 if (closeable != null) { 214 closeable.close(); 215 } 216 } catch (IOException ignored) { 217 } 218 } 219 }