• dynamic-load-apk插件原理整理


      因为当前项目功能越来越多,编译速度越来越慢(公司电脑配置也挺差的...),并且方法数已超出65535的限制了,虽然通过multidex暂时解决了,但是这并不是一个好的解决方式。所以通过插件来加快编译速度以及解决方法数的限制,算是一个越来越重要的任务了,工作中还有很多新需求,所以趁放假的2天研究了下现在比较流行的插件框架dynamic-load-apk,并整理了下。

    框架github地址:https://github.com/singwhatiwanna/dynamic-load-apk

    lib module的svn地址:https://github.com/singwhatiwanna/dynamic-load-apk/trunk/DynamicLoadApk/lib

    一、加载apk总流程:

    //插件文件
    File plugin = new File(apkPath);
    PluginItem item = new PluginItem();
    //插件文件路径
    item.pluginPath = plugin.getAbsolutePath();
    //PackageInfo = PackageManager.getPackageArchiveInfo
    item.packageInfo = DLUtils.getPackageInfo(this, item.pluginPath);
    //launcherActivity
    if (item.packageInfo.activities != null && item.packageInfo.activities.length > 0) {
       item.launcherActivityName = item.packageInfo.activities[0].name;
    }
    //launcherService
    if (item.packageInfo.services != null && item.packageInfo.services.length > 0) {
        item.launcherServiceName = item.packageInfo.services[0].name;
    }
    //加载apk信息
    DLPluginManager.getInstance(this).loadApk(item.pluginPath);

    二、loadApk信息过程:
    1、createDexClassLoader:

    private DexClassLoader createDexClassLoader(String dexPath) {
        dexOutputPath = mContext.getDir("dex", Context.MODE_PRIVATE).getAbsolutePath();
        DexClassLoader loader = new DexClassLoader(dexPath,
                dexOutputPath,  //getDir("dex", Context.MODE_PRIVATE)
                mNativeLibDir,  //optimizedDirectory=getDir("pluginlib", Context.MODE_PRIVATE)
                mContext.getClassLoader());  //host.Appliceation.getClassLoader()
        return loader;
    }

    2、createAssetManager:

    private AssetManager createAssetManager(String dexPath) {
        try {
            AssetManager assetManager = AssetManager.class.newInstance();
            //通过反射调用addAssetPath方法,将apk资源加载到AssetManager
            Method addAssetPath = assetManager.getClass().getMethod("addAssetPath", String.class);
            addAssetPath.invoke(assetManager, dexPath);
            return assetManager;
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    后面会重写DLProxyActivity的getAssets()方法,返回此处生成的AssetManager,从而实现从插件apk加载资源:

    @Override
    public AssetManager getAssets() {
        return impl.getAssets() == null ? super.getAssets() : impl.getAssets();
    }

    3、createResources:

    private Resources createResources(AssetManager assetManager) {
        //通过刚创建的assetManager以及宿主程序的Resources创建Plugin的Resources
        Resources superRes = mContext.getResources();
        Resources resources = new Resources(assetManager,
                superRes.getDisplayMetrics(),
                superRes.getConfiguration());
        return resources;
    }

    后面会重写DLProxyActivity的getResources()方法,返回此处生成的Resources,从而实现从插件apk加载资源:

    @Override
    public Resources getResources() {
        return impl.getResources() == null ? super.getResources() : impl.getResources();
    }

    4、创建pluginPackage并通过插件的packageName保存插件信息:
    pluginPackage = new DLPluginPackage(dexClassLoader, resources, packageInfo);
    mPackagesHolder.put(packageInfo.packageName, pluginPackage);

    5、copySoLib(拷贝so文件到应用的pluginlib目录下):
    SoLibManager.getSoLoader().copyPluginSoLib(mContext, dexPath, mNativeLibDir);

    三、调用插件
    1、要向插件Intent传递可序列化对象,必须通过DLIntent,设置Bundle的ClassLoader:

    @Override
    public Intent putExtra(String name, Parcelable value) {
        setupExtraClassLoader(value);
        return super.putExtra(name, value);
    }
    @Override
    public Intent putExtra(String name, Serializable value) {
        setupExtraClassLoader(value);
        return super.putExtra(name, value);
    }
    private void setupExtraClassLoader(Object value) {
        ClassLoader pluginLoader = value.getClass().getClassLoader();
        DLConfigs.sPluginClassloader = pluginLoader;
        setExtrasClassLoader(pluginLoader); //设置Bundle的ClassLoader
    }

    2、startPluginActivity:
    插件內部的activity之间相互调用,需要使用此方法。

    public int startPluginActivityForResult(Context context, DLIntent dlIntent, int requestCode) {
        if (mFrom == DLConstants.FROM_INTERNAL) {
            dlIntent.setClassName(context, dlIntent.getPluginClass());
            performStartActivityForResult(context, dlIntent, requestCode);
            return DLPluginManager.START_RESULT_SUCCESS;
        }
        String packageName = dlIntent.getPluginPackage();
        //验证intent的包名
        if (TextUtils.isEmpty(packageName)) {
            throw new NullPointerException("disallow null packageName.");
        }
        //检测插件是否加载
        DLPluginPackage pluginPackage = mPackagesHolder.get(packageName);
        if (pluginPackage == null) {
            return START_RESULT_NO_PKG;
        }
        //要调用的插件Activity的class完整路径
        final String className = getPluginActivityFullPath(dlIntent, pluginPackage);
        //Class.forName
        Class<?> clazz = loadPluginClass(pluginPackage.classLoader, className);
        if (clazz == null) {
            return START_RESULT_NO_CLASS;
        }
        //获取代理Activity的class,DLProxyActivity/DLProxyFragmentActivity
        Class<? extends Activity> proxyActivityClass = getProxyActivityClass(clazz);
        if (proxyActivityClass == null) {
            return START_RESULT_TYPE_ERROR;
        }
        //put extra data
        dlIntent.putExtra(DLConstants.EXTRA_CLASS, className);
        dlIntent.putExtra(DLConstants.EXTRA_PACKAGE, packageName);
        dlIntent.setClass(mContext, proxyActivityClass);
        //通过context启动宿主Activity
        performStartActivityForResult(context, dlIntent, requestCode);
        return START_RESULT_SUCCESS;
    }

    四、Activity生命周期的管理
    插件apk中的activity其实就是一个普通的对象,不是真正意义上的activity(没有在宿主程序中注册且没有完全初始化),不具有activity的性质,因为系统启动activity是要做很多初始化工作的,而我们在应用层通过反射去启动activity是很难完成系统所做的初始化工作的,所以activity的大部分特性都无法使用包括activity的生命周期管理,这就需要我们自己去管理。
    DL采用了接口机制,将activity的大部分生命周期方法提取出来作为一个接口(DLPlugin),然后通过代理activity(DLProxyActivity)去调用插件activity实现的生命周期方法,这样就完成了插件activity的生命周期管理,并且没有采用反射,当我们想增加一个新的生命周期方法的时候,只需要在接口中声明一下同时在代理activity中实现一下即可。

    public interface DLPlugin {
        public void onCreate(Bundle savedInstanceState);
        public void onStart();
        public void onRestart();
        public void onActivityResult(int requestCode, int resultCode, Intent data);
        public void onResume();
        public void onPause();
        public void onStop();
        public void onDestroy();
        public void attach(Activity proxyActivity, DLPluginPackage pluginPackage);
        public void onSaveInstanceState(Bundle outState);
        public void onNewIntent(Intent intent);
        public void onRestoreInstanceState(Bundle savedInstanceState);
        public boolean onTouchEvent(MotionEvent event);
        public boolean onKeyUp(int keyCode, KeyEvent event);
        public void onWindowAttributesChanged(LayoutParams params);
        public void onWindowFocusChanged(boolean hasFocus);
        public void onBackPressed();
        public boolean onCreateOptionsMenu(Menu menu);
        public boolean onOptionsItemSelected(MenuItem item);
    }

    DLBasePluginActivity的部分实现:

    public class DLBasePluginActivity extends Activity implements DLPlugin {
        /**
         * 代理activity,可以当作Context来使用,会根据需要来决定是否指向this
         */
        protected Activity mProxyActivity;
        /**
         * 等同于mProxyActivity,可以当作Context来使用,会根据需要来决定是否指向this<br/>
         * 替代this来使用(应为this指向的是插件中的Activity,已经不是常规意义上的activity,所以this是没有意义的)
         * 如果是DLPlugin中已经覆盖的Activity的方法,就不需使用that了,直接调用this即可
         */
        protected Activity that;
        protected DLPluginManager mPluginManager;
        protected DLPluginPackage mPluginPackage;
        protected int mFrom = DLConstants.FROM_INTERNAL;
        @Override
        public void attach(Activity proxyActivity, DLPluginPackage pluginPackage) {
            mProxyActivity = (Activity) proxyActivity;
            that = mProxyActivity;
            mPluginPackage = pluginPackage;
        }
        @Override
        public void onCreate(Bundle savedInstanceState) {
            if (savedInstanceState != null) {
                mFrom = savedInstanceState.getInt(DLConstants.FROM, DLConstants.FROM_INTERNAL);
            }
            if (mFrom == DLConstants.FROM_INTERNAL) {
                super.onCreate(savedInstanceState);
                mProxyActivity = this;
                that = mProxyActivity;
            }
            mPluginManager = DLPluginManager.getInstance(that);
        }
        @Override
        public void setContentView(View view) {
            if (mFrom == DLConstants.FROM_INTERNAL) {
                super.setContentView(view);
            } else {
                mProxyActivity.setContentView(view);
            }
        }
        ......
    }

    在代理类DLProxyActivity中的实现:

    public class DLProxyActivity extends Activity implements DLAttachable {
        protected DLPlugin mRemoteActivity;
        private DLProxyImpl impl = new DLProxyImpl(this);
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            impl.onCreate(getIntent());
        }
        @Override
        public void attach(DLPlugin remoteActivity, DLPluginManager pluginManager) {
            mRemoteActivity = remoteActivity;
        }
        @Override
        public AssetManager getAssets() {
            return impl.getAssets() == null ? super.getAssets() : impl.getAssets();
        }
        @Override
        public Resources getResources() {
            return impl.getResources() == null ? super.getResources() : impl.getResources();
        }
        @Override
        public Theme getTheme() {
            return impl.getTheme() == null ? super.getTheme() : impl.getTheme();
        }
        @Override
        public ClassLoader getClassLoader() {
            return impl.getClassLoader();
        }
        @Override
        protected void onActivityResult(int requestCode, int resultCode, Intent data) {
            mRemoteActivity.onActivityResult(requestCode, resultCode, data);
            super.onActivityResult(requestCode, resultCode, data);
        }
        @Override
        protected void onStart() {
            mRemoteActivity.onStart();
            super.onStart();
        }
        ......
    }

    总结:

    插件主要的2个问题就是资源加载以及Activity生命周期的管理。

    资源加载

    通过反射调用AssetManager的addAssetPath方法,我们可以将一个插件apk中的资源加载到AssetManager中,然后再通过AssetManager来创建一个新的Resources对象,然后就可以通过这个Resources对象来访问插件apk中的资源了。

    Activity生命周期管理

    采用接口机制,将activity的大部分生命周期方法提取出来作为一个接口(DLPlugin),然后通过代理activity(DLProxyActivity)去调用插件activity实现的生命周期方法,这样就完成了插件activity的生命周期管理。

    另外,一个需要注意的地方:

    插件项目引用 android-support-v4.jar、lib.jar等libs,生成apk时不能将这些打包到apk,只在编译时引用,只有host项目里才编译并打包,保证host以及插件中的代码只有一份。

    在studio里面使用provided而非compile:

    dependencies {
      provided files('provide-jars/android-support-v4.jar')
      provided files('provide-jars/lib.jar')
    }

  • 相关阅读:
    Qt5中QMessageBox::warning()的第一个参数写this时出错
    Qt5如何设置静态编译,解决生成的可执行文件打开出错问题
    SpringCloud OAuth2实现单点登录以及OAuth2源码原理解析
    Redis分区
    Redis持久化
    如何合理地估算线程池大小?
    并发减库存
    Java导出Excel
    电商促销后台逻辑
    Java8虚拟机内存模型
  • 原文地址:https://www.cnblogs.com/John-Chen/p/4475025.html
Copyright © 2020-2023  润新知