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接口

Attachable接口不仅要发挥关联代理和插件的作用,其也声明插件Activity的生命周期方法,供插件实现,并由代理管理,如下所示。

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的生命周期方法。从而实现了代理“套壳”的作用。

效果

内置sdcard里外部存储中存放插件apk的位置,sdcard的根目录下:


执行效果:


可以看到,点击加载插件button到代理Activity到主界面这个过程有一定的响应时间,这也是需要优化的地方。

结语

本篇博客实现了一个简单的通过代理Activity管理和展示动态加载的插件的功能。

文章中代理Activity只是实现了功能,并没有做优化和高效的封装。

若各位想深入学习,可以看看社区里很多开源的插件化框架,这些框架对插件的管理、类加载器的管理都实现了很好的封装,不仅实现了Activity的代理,还有Service等其他组件的代理。

若大家发现了什么问题或错误可以留言多多交流。




















原创粉丝点击