Android徒手打造一个超精简的插件加载工具(创建Context)
来源:互联网 发布:rime输入法 mac 编辑:程序博客网 时间:2024/06/14 20:58
最近插件化,热修复又火了一阵,插件化和热修复基本实现原理都是靠ClassLoader,自己在业余之下也凑了一下热闹,呵呵,造一造自行车的轮子。
首先实现插件化,肯定就是要动态的访问里面的方法和资源了,其实对于已经安装的APK可以通过Context.createPackageContext创建Contxt,然后通过反射调用方法和获取资源,但是没有安装的APK,Context 就没有那么容易创建了。
创建一个没安装的APK的Context,其实只要把一个旧的Context包裹一下覆写一些方法就行了,但有一些细节需要注意。
1)ClassLoader创建
a.首先创建一个ClassLoader,方法如下
/** * * @param context 上下文 * @param src apk路径 * @param internalPath 解压classes.dex的位置(不会重复解压) * @return ClassLoader */ private static ClassLoader buildClassLoader(Context context, String src,String internalPath) { return new DexClassLoader(src, internalPath, internalPath + "/" + "lib", context.getClassLoader()); }
b. 以上代码中,如果有jni的so文件,需要拷贝对应ABI的so文件到internalPath/lib/目录下。
so 从APK对应ABI下解压文件的解压方法如下
/** * copy suitable so files to destination dir * * @param apkPath apk 's path * @param cpuAbi cpu abi * @param dstDir destination dir */ private static void unZipSpecialJniLib(String apkPath, String cpuAbi, String dstDir) { FileInputStream fileInputStream = null; ZipInputStream zipInputStream = null; try { fileInputStream = new FileInputStream(apkPath); zipInputStream = new ZipInputStream(fileInputStream); ZipEntry zipEntry = null; String mark = "lib/" + cpuAbi + File.separatorChar; byte[] buffer = new byte[1024 * 5]; while ((zipEntry = zipInputStream.getNextEntry()) != null) { String zipEntryName = zipEntry.getName(); if (zipEntryName.startsWith(mark) && !zipEntry.isDirectory()) { String entryName = zipEntry.getName(); String zipFileName = entryName.substring(entryName.indexOf(mark) + mark.length()); String dstFiePath = dstDir + "/lib/" + zipFileName; unZipItem(zipInputStream, buffer, dstFiePath); } } } catch (Exception e) { e.printStackTrace(); } finally { try { if (fileInputStream != null) fileInputStream.close(); } catch (Exception e) { e.printStackTrace(); } try { if (zipInputStream != null) zipInputStream.close(); } catch (Exception e) { e.printStackTrace(); } } } private static void unZipItem(ZipInputStream zipInputStream, byte[] buffer, String dstFiePath) { File dstFile = new File(dstFiePath); if (!dstFile.getParentFile().exists()) { boolean r = dstFile.getParentFile().mkdirs(); if (!r) throw new RuntimeException(" create folder failed,during unzip "+dstFiePath); } if (dstFile.exists()) { boolean r = dstFile.delete(); if (!r) throw new RuntimeException(" create folder failed,during unzip"+dstFiePath); } int count; FileOutputStream fileOutputStream = null; try { fileOutputStream = new FileOutputStream(dstFile); while ((count = zipInputStream.read(buffer)) > 0) { fileOutputStream.write(buffer, 0, count); } fileOutputStream.flush(); } catch (Exception e) { e.printStackTrace(); } finally { try { if (fileOutputStream != null) fileOutputStream.close(); } catch (IOException e) { e.printStackTrace(); } } }
以上的cpu ABI 可以通过 android.os.Build.CPU_ABI,得到。
至此就Classloader创建完毕。现在可以通过反射去调用外部APK中的方法了 。值得注意的是 Classloader 是优先从系统以及当前应用的dex中找类。
2)AssetManager的创建
通过反射创建
代码如下
private static AssetManager buildAssetManager(String apkPluginPath) { Class<?> assetManagerClass = AssetManager.class; AssetManager assetManager = null; try { assetManager = (AssetManager) assetManagerClass.newInstance(); Method addPathMethod = assetManagerClass.getMethod("addAssetPath", String.class); addPathMethod.setAccessible(true); addPathMethod.invoke(assetManager, apkPluginPath); } catch (Exception e) { e.printStackTrace(); } return assetManager; }
3)ContextWrapper资源替换(重点)
看过开源dynamic-load-apk 源码的应该知道前两步,但是dynamic-load-apk的加载资源的话,一次只能加载一个,原理是每次切换到某一个插件的时候,就把相应的资源替换掉。如果每个插件new一个ContextWrapper 会怎样呢。实际上在加载资源还是会有问题,而问题的根源就是LayoutInflate中的它:
public static LayoutInflater from(Context context) { LayoutInflater LayoutInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); if (LayoutInflater == null) { throw new AssertionError("LayoutInflater not found."); } return LayoutInflater; }
和ContextImp中的它
registerService(LAYOUT_INFLATER_SERVICE, new ServiceFetcher() { public Object createService(ContextImpl ctx) { return PolicyManager.makeNewLayoutInflater(ctx.getOuterContext()); }});
解释一下,建立ContextWrapper替换资源后,虽然资源已经被替换了,但是
LayoutInflater却是用的旧的Context创建的LayoutInflater,因此需要创建一个新的LayoutInflater;创建方法如下
Class<?> clz = classLoader.loadClass("com.android.internal.policy.PolicyManager"); Method createMethod = clz.getDeclaredMethod("makeNewLayoutInflater", Context.class); mLayoutInflater = createMethod.invoke(null, this);
4)最终ContextWrapper
package jx.cy.csh.PluginContextLoader;import android.content.Context;import android.content.ContextWrapper;import android.content.res.AssetManager;import android.content.res.Resources;import java.lang.reflect.Method;/** * Created by biluo on 2016/8/25. */public class PluginContext extends ContextWrapper { private AssetManager mAssetManager; private ClassLoader mClassLoader; private Resources mResources; private Object mLayoutInflater; public PluginContext(Context base, AssetManager assetManager , ClassLoader classLoader) { super(base); mClassLoader = classLoader; mAssetManager = assetManager; mResources = new Resources(assetManager, base.getResources().getDisplayMetrics(), base.getResources().getConfiguration()); createNewLayoutInflater(classLoader); } private void createNewLayoutInflater(ClassLoader classLoader) { try { Class<?> clz = classLoader.loadClass("com.android.internal.policy.PolicyManager"); Method createMethod = clz.getDeclaredMethod("makeNewLayoutInflater", Context.class); mLayoutInflater = createMethod.invoke(null, this); } catch (Exception e) { e.printStackTrace(); } } @Override public Resources getResources() { return mResources; } @Override public ClassLoader getClassLoader() { return mClassLoader; } @Override public AssetManager getAssets() { return mAssetManager; } @Override public Object getSystemService(String name) { if (name != null && name.equals(LAYOUT_INFLATER_SERVICE)) { return mLayoutInflater; } return super.getSystemService(name); }}
5)其他
a 插件入口实例
package jx.cy.csh.pluginsample;import android.content.Context;import android.view.LayoutInflater;import android.view.View;/** * Created by biluo on 2016/8/30. */public class Main { private View mView; static { System.loadLibrary("jnitest"); } public void main(Context context) { mView = LayoutInflater.from(context).inflate(R.layout.main, null); } public native int plus(int a,int b); public View getView() { return mView; }}
说明: layout里面可以使用自定义控件,因为解析xml是用的新的Context。父控件和子控件如果有相同包名的自定义控件(包括jar)并且写在XML里面,可能会出现强制转换异常(之前使用Context.createPackage遇见过,这里尚未验证).
- Android徒手打造一个超精简的插件加载工具(创建Context)
- 打造一个精简的BaseActivity
- 迅雷超精简 打造计时:
- 一个java开发的超精简计算器。
- 再论android四大组件(超精简)
- 手工打造迅雷9超精简版
- 打造支持apk下载和html5缓存的 IIS(配合一个超简单的android APP使用)详解
- android的context使用,工具类context
- 徒手创建一个webpack全自动生产与发布分离的环境
- 徒手打造一款PK 名片全能王 的名片识别应用
- 徒手打造一款PK 名片全能王 的名片识别应用
- 打造超精简绿色 迅雷 和 IDM附详细教程:
- 如何编译一个精简的Android系统
- 【菜鸟学Java】5:“徒手”创建一个Web应用
- Android--------从一个包中的Avtivity创建另外另外一个包的Context
- Android--------从一个包中的Avtivity创建另外另外一个包的Context
- Android--------从一个包中的Avtivity创建另外另外一个包的Context
- 徒手打造一款PK 名片全能王 的名片识别应用--名字篇之(如何100%准确提取名字)
- 面试题spring的ioc和aop的原理
- 安静下来,慢慢走, 回顾昨天,珍惜好今天!
- 【年终总结】冰,水为之而寒于水
- python configparser库
- 论文阅读:Synthetic Data for Text Localisation in Natural Images
- Android徒手打造一个超精简的插件加载工具(创建Context)
- java实现网络爬虫之链接初筛选策略
- WEB UI 组件介绍
- 获取sd卡容量
- SVN 服务端 和 客户端(转)
- java读写文件详解
- 产品为何总是做不好 (三): 会让产品走向失败的架构师
- 使用java语言的几种排序算法
- css中圣杯布局