打包jar文件 外部调用资源 so等
一个非常好的从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 }
获取当前apk路径
final ApplicationInfo appInfo = context.getApplicationInfo();
Log.d("zhibin","appInfo.sourceDir: "+ appInfo.sourceDir);
输出:/system/app/xxx.apk
对应sdk 5.0以下版本,修正一些不支持的变量:
@SuppressWarnings("deprecation") //required for old API levels private static ZipEntry getLibraryEntry(final String libName, final ZipFile zipFile) { String jniNameInApk; ZipEntry libraryEntry = null; // if (Build.VERSION.SDK_INT >= 21 && Build.SUPPORTED_ABIS.length > 0) { // for (final String ABI : Build.SUPPORTED_ABIS) { // jniNameInApk = "lib/" + ABI + "/" + libName; // libraryEntry = zipFile.getEntry(jniNameInApk); // // if (libraryEntry != null) { // break; // } // } // } else { jniNameInApk = "lib/" + Build.CPU_ABI + "/" + libName; Log.d("zhibin","Search directory for jniNameInApk: "+ jniNameInApk); libraryEntry = zipFile.getEntry(jniNameInApk); //直接指定 if(libraryEntry == null){ jniNameInApk = "lib/armeabi" + "/" + libName; Log.d("zhibin","Correct it to jniNameInApk: "+ jniNameInApk); libraryEntry = zipFile.getEntry(jniNameInApk); } } if (libraryEntry == null) { throw new IllegalStateException("Library " + libName + " for supported ABIs not found in APK file"); } return libraryEntry; }
共外部调用资源:
背景:工作中需要开发一个广告插件,并提供给其它人使用。这里就需要把自己的插件程序,打成jar来提供给他人引用。
但是遇到一个问题:插件程序中无法使用资源文件。
试过以下几种方式解决:
1、从插件程序中导出jar包
论坛上有人说导出的jar包中无法包含Drawable等资源文件,一些图片等数据,需要放到Assert文件中使用。
其实,关于这个问题,我做了尝试:
首先,需要说明导出jar包含什么文件是由你导出时选择来决定的,比如下图:
如果你选择了res文件夹,则打包出的jar文件是可以包含res文件到。
但是包含文件并不代表可以使用。如果你想当然在插件程序中使用R.drawable.XXXX等方式获取
资源会报错!
当然别人通过R.XX.XX也只能看到自己的资源文件,而无法获取jar中的资源文件。
2、获取jar包中的文件
虽然无法直接引用资源文件,但是如果外边程序想获取某个资源文件时,也是可行的。
其原理是以数据流读取jar中指定的文件。
比如读取Assert文件下的icon.jpg文件:
你可以在插件中封装一个对外的方法:
publicstatic Drawable getAssertDrawable(Context context,StringfileName){
try {
InputStreaminStream=context.getAssets().open(fileName);
return newBitmapDrawable(BitmapFactory.decodeStream(inStream));
} catch(IOException e) {
Log.e(LOG_TAG, "Assert中"+fileName+"不存在");
}
returnnull;
}
直接使用该方法可以得到文件。
后来又尝试在外部程序,直接使用context.getAssets().open(fileName)方法获取jar中文件,
让人喜出望外的是竟然成功了。呵呵!
后来分析,外部程序编译时,其实连同jar包中内容一起混编。jar包中的Assert文件会同外部程序的Assert一起
由AssertManager管理。
所以当你jar包中Assert内部文件和外部Assert中的文件有命名冲突时,编译器会报错的。
另外,还有人提供另外一种方法来读取诸如Drawable等文件夹下的文件。
publicstatic Drawable getDrawableForJar(String resName,Classclass){
InputStreaminStream=class.getResourceAsStream(resName);
return newBitmapDrawable(BitmapFactory.decodeStream(inStream));
}
使用class.getResourceAsStream()方法读取,注意这里resName是文件的相对路径,比如jar根目录下res/drawable/icon.png,
则调用方法为:class.getResourceAsStream(/res/drawable/icon.png);
这里主要是采用ClassLoader的下面几个方法来实现:
public URL getResource(String name);
public InputStream getResourceAsStream(String name)
public static InputStreamgetSystemResourceAsStream(String name)
public static URL getSystemResource(String name)
后两个方法可以看出是静态的方法,这几个方法都可以从Jar中读取图片资源,但是对与动画的gif文件,笔者在尝试过程中发现,存在一些差异。
String gifName为Gif文件在Jar中的相对路径。
(1)使用了两个静态方法
或者
这两种方式可以成功地读取gif文件,但是对于gif动画,显示出来地是静态的。
(2)使用其他两个方法
再这种方式下动画可以正常显示了。
3、使用library方法加载资源文件
在论坛中看到帖子讲述如何把工程作为libarary,让其他工程添加library,编译后会自动生成jar,然后在哪来使用。
当时看到此贴,喜出望外,所以赶紧尝试下!
方法:选择插件工程,右键选择属性,选择Android,勾选下面Is Liabrary选项。
然后,选择我们现有的工程,右键属性,选择Android,在library下add相应的库。你会看到,刚才我们设置的插件项目,就在其中。最后,点击应用,完成。
详细步骤:
按如下方法设置:
1. 假设要引用的android工程叫LibProject,引入到的工程叫MainProject;
2.设置LibProject,右键->Properties->Android,将Islibrary项选中,然后Apply;
3.设置MainProject,右键->->Properties->Android,在Library中,点击Add按钮,将LibProject工程加入,Apply即可。
你会看到我们的工程中多出插件工程的引用,而且可以使用R.XXX.XXX获取资源文件。
以为可以解决了,但是发现并没有生成想要的jar文件。在插件工程中,倒是有编译的class文件,却没有jar包。
而我们往往是不能像这样把原工程给别人直接引用的。
经过多次试验,始终没有生成jar,非常奇怪别人怎么弄得。。。
另外,拿以前通过这种方式生成的jar文件看,里面也不包含资源文件夹。。
可以把生成的类共享出去。
把.so文件打包到jar中
查了一些方法,其中一个我比较喜欢,再load动态库的时候,把so文件复制到tmp目录下,然后删掉
//modify the static block
static {
try {
Class c = HelloJNI.class;
URL location =
c.getProtectionDomain().getCodeSource().getLocation();
ZipFile zf = new ZipFile(location.getPath());
// libhellojni.so is put in the lib folder
InputStream in = zf.getinputStream(zf.getEntry("lib/libhellojni.so"));
File f = File.createTempFile("JNI-", "Temp");
FileOutputStream out = new FileOutputStream(f);
byte [] buf = new byte[1024];
int len;
while ((len = in.read(buf)) > 0)
out.write(buf, 0, len);
in.close();
out.close();
System.load(f.getAbsolutePath());
f.delete();
} catch (Exception e) { // I am still lazy ~~~
e.printStackTrace();
}
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· 单线程的Redis速度为什么快?
2014-12-08 Android开源框架Afinal第一篇——揭开圣女的面纱
2014-12-08 教程] 《开源框架-Afinal》之FinalHttp 01一步一脚