Android 使用代理加载插件
来源:互联网 发布:7级防空火箭升级数据 编辑:程序博客网 时间:2024/06/07 09:06
最近一直在学习Android的插件化开发,看了很多大神的博客,醍醐灌顶。但是纸上得来终觉浅,自己便寻思做了个demo,加深学习理解。在实现的过程中,也发现了很多问题,因此写下这篇博客,记录这个学习过程。
背景
当项目越来越大的时候,需要通过插件化来减轻应用的内存和CPU占用;
可以实现热插拔,在不发布新版本的情况下更新某些模块。
插件化的有几种模式:简单的加载、使用代理、根据apk动态创建Activity,社区也有很多开源的插件化库,大家可以去找一找。
本文浅谈了使用Activity代理实现插件化。实现步骤
在宿主app中可以加载手机上已安装和未安装的apk。若加载已安装的apk,就与使用插件更新app的思想相悖了,因此研究如何加载手机上未安装的apk;
将插件apk放在手机sdcard中,通过相关api即可在宿主app中获取插件apk;
获取插件apk中应用的包结构,提取包信息;
使用DexClassLoader,加载插件apk中指定的插件Activity;
获取apk包中的Resources资源,在宿主apk中使用;
使用反射的方式得到插件Activity的实例,并将其与代理Activity关联;
以接口方式管理插件Activity的生命周期。待实现点
1 内存存储和外存存储
●内部存储;
内部存储不是内存,它位于系统中很特殊的一个位置,默认只能被你的应用访问。应用安装、运行后所创建的文件存在于内部存储中以应用包名为名的目录下。
当应用卸载后,其对应的内部存储文件也随之删除。
内部存储也是系统本身和系统应用程序主要的数据存储所在地。
●外部存储;
外部存储,即手机可移动的sdcard或手机自带的固定的sdcard,且都是用相同的api访问得到其根目录。
外部存储的public文件是可以被自由访问的,且应用删除后,仍保留;外部存储的private文件由于位于外部存储中,也可被访问,但是其对于其他应用没有访问价值。应用删除后,private文件也删除。
可以使用getExternalStoragePublicDirectory()来获取外部存储的根目录。
2 获取apk中的包信息
/* *params*archiveFilePath: 未安装的apk所在位置,可通过*getExternalStoragePublicDirectory()+File.separator+fileName获取;*return : 包含包信息的PackageInfo对象*/private PackageInfo getUninstalledApkInfo(String archiveFilePath){ PackageManager pm=mSelf.getPackageManager(); PackageInfo pkgInfo=pm.getPackageArchiveInfo(archiveFilePath,PackageManager.GET_ACTIVITIES); return pkgInfo;}
上述的mSelf,即代表宿主app的Context对象。
3 加载插件Activity
File optimizedDirectoryFile=mSelf.getDir("dex", Context.MODE_PRIVATE);DexClassLoader dexClassLoader=new DexClassLoader(mApkPath,optimizedDirectoryFile.toString(),null,mSelf.getClassLoader());try { mPluginClass=dexClassLoader.loadClass(packageName+".PluginActivity");} catch (ClassNotFoundException e) { Log.d(TAG, "loadApk: loading class errors."); e.printStackTrace(); return false;}
DexClassLoader中要使用的数据必须位于内部存储中。因此,创建DexClassLoader时会将外部获得的数据拷贝、优化,存放在内部存储的指定目录中。再从其中获取数据创建类加载器。
Context.getDir()会在内部存储的根目录下创建指定文件夹;
第一个参数为待读取apk路径;
第二个参数为内部存储下,数据的优化存储路径;
第三个参数为原生库的搜索路径,一般为null;
第四个参数指定了父类加载器。
获得DexClassLoader后就可以通过loadClass方法,加载指定的插件Activity。4 获得apk包中的Resources资源,并使用
private void loadResources(){ try { AssetManager assetManager = AssetManager.class.newInstance(); Method addAssetPath = assetManager.getClass().getMethod("addAssetPath" , String.class); addAssetPath.invoke(assetManager,mApkPath); mAssetManager=assetManager; } catch (Exception e) { e.printStackTrace(); } Resources superRes=super.getResources(); mResources=new Resources(mAssetManager,superRes.getDisplayMetrics(),superRes.getConfiguration());}
通过反射,调用AssetManager的addAssetPath方法,将apk资源加入至AssetManager中。
最后通过AssetManager和父类获得的Resources创建Resources对象。
注意,此时得到的mResources对象包含了插件apk中的资源。宿主app中的资源仍在宿主的Resources对象中,二者没有混淆。
宿主app的资源可通过Context.getResources()来获得。
5 覆写getResources(),让代理Activity使用插件中的资源
@Overridepublic Resources getResources() { return mResources==null ? super.getResources():mResources;}
代理Activity只是一个空壳。它的作用是载入插件Activity,管理插件Activity,并让插件Activity发挥功能。
因此,如果插件能够成功导入,且资源能够成功获取,那么mResources(插件的资源)就不应该为null。代理Activity就需要使用插件Activity里的资源让其发挥效用。
若插件无法成功导入或资源不能成功获取,则应该手动将mResources置为null。让Activity使用宿主app中的资源。
6 获取插件Activity实例,并与宿主app关联
private boolean initPlugin(){ try { //创建插件对象 Constructor constructor=mPluginClass.getConstructor(new Class[]{}); Object obj=constructor.newInstance(new Object[]{}); mPluginActivity=(Attachable) obj; mPluginActivity.attach(mSelf); mPluginActivity.onCreate(null); } catch (Exception e) { e.printStackTrace(); mResources=null; return false; } return true;}
根据反射的方式,通过获取的Class对象获取相关的构造器,并创建插件Activity对象。
获得插件Activity后,将其转化为Attachable接口对象,并调用attach方法,将它与宿主代理Activity关联。
调用插件Activity的onCreate方法,在代理中初始化并使用。
Attachable接口应该作为公共类资源放在宿主app和插件app中作为支撑。否则,当获取插件Activity后,将无法把插件和代理关联。
7 Attachable接口
public interface Attachable { void attach(Activity activity, Resources resources , PackageInfo packageInfo); void onCreate(Bundle savedInstanceState); void onRestart(); void onStart(); void onResume(); void onPause(); void onStop(); void onDestroy(); /***************根据需要,还可声明更多的生命周期方法*************/}
8 插件Activity的实现
public class PluginActivity extends Activity implements Attachable { public static final String TAG=new String("PluginActivity"); private Activity mProxyActivity;@Overridepublic void attach(Activity activity) {this.mProxyActivity=activity;}@Override public View findViewById(int id) { return mProxyActivity.findViewById(id); }public void onCreate(Bundle paramBundle){ mProxyActivity.setContentView(R.layout.plugin_layout); mImgView=(ImageView) mProxyActivity.findViewById(R.id.pic); mImgView.setImageResource(R.drawable.imgd); mButton1=(Button) mProxyActivity.findViewById(R.id.button1); mButton2=(Button) mProxyActivity.findViewById(R.id.button2); mButtond=(Button) mProxyActivity.findViewById(R.id.buttond); mButton1.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { mImgView.setImageResource(R.drawable.img1); } }); mButton2.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { mImgView.setImageResource(R.drawable.img2); } }); mButtond.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { mImgView.setImageResource(R.drawable.imgd); } }); } @Override public void setContentView(int layoutResID){ mProxyActivity.setContentView(layoutResID); } @Override public void onDestroy() { } @Override public void onPause() { } @Override public void onRestart() { } @Override public void onResume() { } @Override public void onStart() {} @Override public void onStop() {}}
插件Activity只是简单的实现了点击Button,并切换图片的功能。可以看到,在其内部,所有的动作都是由代理Activity来替它完成的。这就是使用代理进行动态加载的基本思想。
9 代理Activity的实现,并管理插件的生命周期
public class ProxyActivity extends Activity{private ProxyActivity mSelf;private Attachable mPluginActivity;private AssetManager mAssetManager;private Resources mResources;//………//……其他的成员变量@Overridepublic Resources getResources() { return mResources==null ? super.getResources():mResources;}private PackageInfo getUninstalledApkInfo(String archiveFilePath){//上文中已实现………… }private boolean initPlugin(){ //上文中已实现………… }private void loadResources(){ //上文中已实现………… }@Overrideprotected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); mSelf=this; if (!loadApk("plugindemo.apk")){//载入apk出现问题时,调用setContentView,将页面转置出错状态 setContentView(R.layout.load_plugin_error); return; } if (!initPlugin()){//初始化插件出问题时,调用setContentView,将页面转置出错状态 setContentView(R.layout.load_plugin_error); return; }}private boolean loadApk(String apkFile){//获取sdcard上apk包的完整路径mApkPath=Environment.getExternalStorageDirectory().getAbsolutePath()+ File.separator+apkFile;mPkgInfo=getUninstalledApkInfo(mApkPath);if (mPkgInfo==null){ Log.d(TAG, "loadApk: can't get package information"); return false;}//获取apk包中package的名称String packageName=mPkgInfo.packageName;//创建DexClassLoader的优化目录,在机器的内部存储中File optimizedDirectoryFile=mSelf.getDir("dex", Context.MODE_PRIVATE);DexClassLoader dexClassLoader=new DexClassLoader(mApkPath,optimizedDirectoryFile.toString(),null,mSelf.getClassLoader());try { mPluginClass=dexClassLoader.loadClass(packageName+".PluginActivity");} catch (ClassNotFoundException e) { Log.d(TAG, "loadApk: loading class errors."); e.printStackTrace(); return false;}//获取apk资源loadResources();if (mResources==null){ return false;}Log.d(TAG, "loadApk: load apk successfully.");return true;}@Overrideprotected void onDestroy() { super.onDestroy(); if (mPluginActivity!=null) mPluginActivity.onDestroy();}@Overrideprotected void onStart() { super.onStart(); if (mPluginActivity!=null) mPluginActivity.onStart();}// 其余的生命周期方法不再一一列出……………//……………………………
上述即为代理Activity的实现。在onCreate中进行了apk的加载和插件的初始化。若出错则将Activity显示为出错界面。
可以看到,通过代理Activity的生命周期方法间接的调用了插件Activity的生命周期方法。从而实现了代理“套壳”的作用。
效果
执行效果:
结语
本篇博客实现了一个简单的通过代理Activity管理和展示动态加载的插件的功能。
文章中代理Activity只是实现了功能,并没有做优化和高效的封装。
若各位想深入学习,可以看看社区里很多开源的插件化框架,这些框架对插件的管理、类加载器的管理都实现了很好的封装,不仅实现了Activity的代理,还有Service等其他组件的代理。
若大家发现了什么问题或错误可以留言多多交流。
- Android 使用代理加载插件
- Android知识体系梳理笔记三:动态代理模式---插件加载机制学习笔记
- android 插件加载机制
- Android插件-动态加载
- Eclipse svn插件使用代理
- Android 使用动态加载框架DL进行插件化开发
- Android--使用动态加载框架DL进行插件化开发
- Android 使用动态加载框架DL进行插件化开发
- Android 使用动态加载框架DL进行插件化开发
- Android 使用动态加载框架DL进行插件化开发
- Android 使用动态加载框架DL进行插件化开发
- Android 使用动态加载框架DL进行插件化开发
- Android 使用动态加载框架DL进行插件化开发
- Android 使用动态加载框架DL进行插件化开发
- Android 使用动态加载框架DL进行插件化开发
- Android 使用动态加载框架DL进行插件化开发
- Android 使用动态加载框架DL进行插件化开发
- Android 使用动态加载框架DL进行插件化开发
- 别人电脑上的环境
- [机器学习:李宏毅]25、结构化学习
- : 高级语言程序设计实验6-8二维数组
- 为继承设计的类
- 安装ThinkPHP5.0.12遇到的小问题
- Android 使用代理加载插件
- linux 查看cpu
- java实现语法分析
- 171207之反编译插件jd-eclipse
- Java day 20-21
- [机器学习:李宏毅]24、SVM
- Virsh 虚拟机迁移
- 数据挖掘-处理缺失值
- MySql 外键约束 之CASCADE、SET NULL、RESTRICT、NO ACTION分析和作用