美文网首页Android程序员的核心竞争力android技术专栏Android架构师
自我提升之四 动态加载第三方应用-插件化详解

自我提升之四 动态加载第三方应用-插件化详解

作者: 枫羽望空 | 来源:发表于2019-05-09 15:10 被阅读2次

    什么插件化

    每一个业务组件都是一个独立的apk,然后通过主app动态加载部署业务组件apk。

    插件化好处

    1. 业务组件解耦,能够实现业务组件热插拔
    2. 更改产品迭代模式,可分为主app以及次业务app
    3. 改善产品更新过程,可以在不影响用户的情况下实现业务组件更新以及bug修复

    插件化 “思想”

    主App被系统 “安装” 调用,这个过程由系统提高,而插件apk并非被系统安装,简而言之,需要将插件apk看成一个 “非apk” 文件,只是一个结构复杂的文件,在主app需要时会调用这个文件的一些资源。调用插件即用某种特殊的方式打开这个文件。

    插件化步骤

    分析主app

    1. 主App打包完成后,会形成dex,images,xml资源
    2. dex靠PathClassLoader加载
    3. 图片以及xml资源靠Resource加载

    代码实现

    1. 创建DexClassLoader加载插件代码
    2. 创建Resource加载资源文件
    3. 管理插件Activity生命周期

    项目结构

    项目结构

    注:主APP项目运行在手机上,插件APP项目打包成APK,保存到主项目私有目录下,他们都引用连接的library

    插件实体对象

    package com.shangyi.android.pluginlibrary;
    
    import android.content.pm.PackageInfo;
    import android.content.res.AssetManager;
    import android.content.res.Resources;
    
    import dalvik.system.DexClassLoader;
    
    /**
     * <pre>
     *           .----.
     *        _.'__    `.
     *    .--(Q)(OK)---/$\
     *  .' @          /$$$\
     *  :         ,   $$$$$
     *   `-..__.-' _.-\$/
     *         `;_:    `"'
     *       .'"""""`.
     *      /,  FLY  ,\
     *     //         \\
     *     `-._______.-'
     *     ___`. | .'___
     *    (______|______)
     * </pre>
     * 包    名 : com.shangyi.android.pluginlibrary
     * 作    者 : FLY
     * 创建时间 : 2019/5/8
     * 描述: 插件apk信息的实体对象
     */
    public class PluginApk {
    
        public PackageInfo mPackageInfo;//apk的解析
    
        public DexClassLoader mDexClassLoader;//dex靠PathClassLoader加载
    
        public Resources mResources;// 图片以及xml资源靠Resource加载
    
        public AssetManager mAssetManager;//用于支持创建Resources
    
        public PluginApk(PackageInfo mPackageInfo, DexClassLoader mDexClassLoader, Resources mResources) {
            this.mPackageInfo = mPackageInfo;
            this.mDexClassLoader = mDexClassLoader;
            this.mResources = mResources;
            this.mAssetManager = mResources.getAssets();
        }
    }
    
    

    插件apk的管理类

    package com.shangyi.android.pluginlibrary;
    
    import android.content.Context;
    import android.content.pm.PackageInfo;
    import android.content.pm.PackageManager;
    import android.content.res.AssetManager;
    import android.content.res.Resources;
    import android.util.Log;
    
    import java.io.File;
    import java.lang.reflect.InvocationTargetException;
    import java.lang.reflect.Method;
    
    import dalvik.system.DexClassLoader;
    
    /**
     * <pre>
     *           .----.
     *        _.'__    `.
     *    .--(Q)(OK)---/$\
     *  .' @          /$$$\
     *  :         ,   $$$$$
     *   `-..__.-' _.-\$/
     *         `;_:    `"'
     *       .'"""""`.
     *      /,  FLY  ,\
     *     //         \\
     *     `-._______.-'
     *     ___`. | .'___
     *    (______|______)
     * </pre>
     * 包    名 : com.shangyi.android.pluginlibrary
     * 作    者 : FLY
     * 创建时间 : 2019/5/8
     * 描述: 插件apk的管理
     */
    public class PluginManager {
    
        private static PluginManager instance;
    
        private PluginManager() {
        }
    
        public static PluginManager getInstance() {
            if (instance == null) {
                instance = new PluginManager();
            }
            return instance;
        }
    
        private Context mContext;
    
        private PluginApk mPluginApk;
    
        public void init(Context context) {
            this.mContext = context;
        }
    
        /**
         * 检测是否调用初始化方法
         */
        private void checkInitialize() {
            if (mContext == null) {
                throw new ExceptionInInitializerError("请先调用 PluginManager.getInstance().init(this) 初始化!");
            }
        }
    
        //加载插件apk,服务器下载保存路径
        public void loadApk(String apkPath) {
            checkInitialize();
            PackageInfo packageInfo = mContext.getPackageManager().getPackageArchiveInfo(apkPath,
                    PackageManager.GET_ACTIVITIES | PackageManager.GET_SERVICES);
    
            if (packageInfo == null) {
                Log.d("PluginManager", "loadApk: 加载插件apk失败");
                return;
            }
    
            DexClassLoader dexClassLoader = createDexClassLoader(apkPath);
            AssetManager assetManager = createAssetManager(apkPath);
            Resources resources = createResources(assetManager);
            mPluginApk = new PluginApk(packageInfo, dexClassLoader, resources);
        }
    
        public PluginApk getPluginApk() {
            return mPluginApk;
        }
    
        //创建访问插件apk的DexClassLoader对象加载插件代码
        private DexClassLoader createDexClassLoader(String apkPath) {
            File file = mContext.getDir("dex", Context.MODE_PRIVATE);
            return new DexClassLoader(apkPath, file.getAbsolutePath(), null, mContext.getClassLoader());
        }
    
        //访问插件apk的Aseetmanger对象
        private AssetManager createAssetManager(String apkPath) {
            try {
                AssetManager assetManager = AssetManager.class.newInstance();
                Method method = AssetManager.class.getDeclaredMethod("addAssetPath", String.class);
                method.invoke(assetManager, apkPath);
                return assetManager;
            } catch (Exception e) {
                e.printStackTrace();
            }
            return null;
        }
    
        //创建访问插件apk的Resource对象加载资源文件
        private Resources createResources(AssetManager assetManager) {
            Resources resources = mContext.getResources();
            return new Resources(assetManager, resources.getDisplayMetrics(), resources.getConfiguration());
        }
    
    }
    
    

    管理Activity生命周期

    package com.shangyi.android.pluginlibrary;
    
    import android.app.Activity;
    import android.content.Intent;
    import android.os.Bundle;
    
    /**
     * <pre>
     *           .----.
     *        _.'__    `.
     *    .--(Q)(OK)---/$\
     *  .' @          /$$$\
     *  :         ,   $$$$$
     *   `-..__.-' _.-\$/
     *         `;_:    `"'
     *       .'"""""`.
     *      /,  FLY  ,\
     *     //         \\
     *     `-._______.-'
     *     ___`. | .'___
     *    (______|______)
     * </pre>
     * 包    名 : com.shangyi.android.pluginlibrary
     * 作    者 : FLY
     * 创建时间 : 2019/5/8
     * 描述: 判断调用方式 管理Activity生命周期
     */
    public interface IPlugin {
    
        String FROM_KEY = "FromKey"; //判断调用的传参key
    
        int FROM_INTERNAL = 0; // 从内部调用 手机系统调用
        int FROM_EXTERNAL = 1; // 从外边调用 主app调用
    
        //绑定,代理Activity,传入插件上下文
        void attach(Activity proxyActivity);
    
        void onCreate(Bundle savedInstanceState);
    
        void onStart();
    
        void onRestart();
    
        void onResume();
    
        void onActivityResult(int requestCode, int resultCode, Intent data);
    
        void onPause();
    
        void onStop();
    
        void onDestroy();
    }
    
    

    连接插件的activity

    package com.shangyi.android.pluginlibrary;
    
    import android.app.Activity;
    import android.content.Intent;
    import android.os.Bundle;
    import android.util.Log;
    
    /**
     * <pre>
     *           .----.
     *        _.'__    `.
     *    .--(Q)(OK)---/$\
     *  .' @          /$$$\
     *  :         ,   $$$$$
     *   `-..__.-' _.-\$/
     *         `;_:    `"'
     *       .'"""""`.
     *      /,  FLY  ,\
     *     //         \\
     *     `-._______.-'
     *     ___`. | .'___
     *    (______|______)
     * </pre>
     * 包    名 : com.shangyi.android.pluginlibrary
     * 作    者 : FLY
     * 创建时间 : 2019/5/9
     * 描述: 连接插件的activity
     */
    public class PuglinActivity extends Activity implements IPlugin {
    
        //判断是否是从主APP调用的,如果是不做任何操作
        //如果是系统调用的不带次参数,需要调用父类方法
        private int mFrom = FROM_INTERNAL;
    
        //插件的上下文,代理用
        private Activity mProxyActivity;
    
        @Override
        public void attach(Activity proxyActivity) {
            mProxyActivity = proxyActivity;
        }
    
        @Override
        public void onCreate(Bundle savedInstanceState) {
            if (savedInstanceState != null) {
                mFrom = savedInstanceState.getInt(FROM_KEY);
            }
    
            if (mFrom == FROM_INTERNAL) super.onCreate(savedInstanceState);
    
        }
    
        public Activity getProxyActivity() {
            return mProxyActivity;
        }
    
        @Override
        public void setContentView(int layoutResID) {
            if (mFrom == FROM_INTERNAL) {
                super.setContentView(layoutResID);
            } else if (mProxyActivity != null) {
                mProxyActivity.setContentView(layoutResID);
            } else {
                Log.e("PuglinActivity", "插件的上下文为空");
            }
        }
    
        @Override
        public void onStart() {
            if (mFrom == FROM_INTERNAL) super.onStart();
        }
    
        @Override
        public void onRestart() {
            if (mFrom == FROM_INTERNAL) super.onRestart();
        }
    
        @Override
        public void onResume() {
            if (mFrom == FROM_INTERNAL) super.onResume();
        }
    
        @Override
        public void onActivityResult(int requestCode, int resultCode, Intent data) {
            if (mFrom == FROM_INTERNAL) super.onActivityResult(requestCode, resultCode, data);
        }
    
        @Override
        public void onPause() {
            if (mFrom == FROM_INTERNAL) super.onPause();
        }
    
        @Override
        public void onStop() {
            if (mFrom == FROM_INTERNAL) super.onStop();
        }
    
        @Override
        public void onDestroy() {
            if (mFrom == FROM_INTERNAL) super.onDestroy();
        }
    
    }
    
    

    代理activity - 实质调用需要manifest注册

    package com.shangyi.android.pluginlibrary;
    
    import android.app.Activity;
    import android.content.res.AssetManager;
    import android.content.res.Resources;
    import android.os.Bundle;
    import android.support.annotation.Nullable;
    import android.util.Log;
    
    /**
     * <pre>
     *           .----.
     *        _.'__    `.
     *    .--(Q)(OK)---/$\
     *  .' @          /$$$\
     *  :         ,   $$$$$
     *   `-..__.-' _.-\$/
     *         `;_:    `"'
     *       .'"""""`.
     *      /,  FLY  ,\
     *     //         \\
     *     `-._______.-'
     *     ___`. | .'___
     *    (______|______)
     * </pre>
     * 包    名 : com.shangyi.android.pluginlibrary
     * 作    者 : FLY
     * 创建时间 : 2019/5/9
     * 描述: 代理activity
     */
    public class ProxyActivity extends Activity {
    
        public static final String CLASS_NAME = "className";
    
        private String mClassName;
    
        private PluginApk mPluginApk;
    
        private IPlugin mIPlugin;
    
        @Override
        public void onCreate(@Nullable Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            mClassName = getIntent().getStringExtra(CLASS_NAME);
            mPluginApk = PluginManager.getInstance().getPluginApk();
    
            launchPlaginActivity();
    
        }
    
        //启动activity
        private void launchPlaginActivity() {
            if (mPluginApk == null) {
                Log.e("ProxyActivity: ", "加载不了插件apk文件");
                return;
            }
    
            try {
                Class<?> clazz = mPluginApk.mDexClassLoader.loadClass(mClassName);
                // 实例化Activity 注意:这里的activity是没有生命周期,也没有上下文环境的
                Object object = clazz.newInstance();
                if (object instanceof IPlugin) {
                    mIPlugin = (IPlugin) object;
                    mIPlugin.attach(this);
                    Bundle bundle = new Bundle();
                    bundle.putInt(IPlugin.FROM_KEY, IPlugin.FROM_EXTERNAL);
                    mIPlugin.onCreate(bundle);
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    
        @Override
        public Resources getResources() {
            return mPluginApk != null ? mPluginApk.mResources : super.getResources();
        }
    
        @Override
        public AssetManager getAssets() {
            return mPluginApk != null ? mPluginApk.mAssetManager : super.getAssets();
        }
    
        @Override
        public ClassLoader getClassLoader() {
            return mPluginApk != null ? mPluginApk.mDexClassLoader : super.getClassLoader();
        }
    }
    
    

    主界面activity

    package com.fly.newstart.plugin;
    
    import android.content.Intent;
    import android.os.Bundle;
    import android.view.View;
    
    import com.fly.newstart.R;
    import com.fly.newstart.common.base.BaseActivity;
    import com.fly.newstart.utils.FileUtils;
    import com.shangyi.android.pluginlibrary.PluginManager;
    import com.shangyi.android.pluginlibrary.ProxyActivity;
    
    public class MainPluginActivity extends BaseActivity {
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main_plugin);
            PluginManager.getInstance().init(this);
            findViewById(R.id.btnLoad).setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    //模拟服务器下载插件apk
                    String apkPath = FileUtils.copyAssetAndWrite(MainPluginActivity.this, "pluginapk-debug.apk");
                    //加载apk
                    PluginManager.getInstance().loadApk(apkPath);
                }
            });
    
            findViewById(R.id.btnSkip).setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    //指定跳转的类名
                    Intent intent = new Intent();
                    intent.setClass(MainPluginActivity.this, ProxyActivity.class);
                    intent.putExtra(ProxyActivity.CLASS_NAME, "com.shangyi.android.pluginapk.PluginActivity");
                    startActivity(intent);
                }
            });
        }
    
        @Override
        protected void onStart() {
            super.onStart();
        }
    
        @Override
        protected void onResume() {
            super.onResume();
        }
    
        @Override
        protected void onRestart() {
            super.onRestart();
        }
    
        @Override
        protected void onActivityResult(int requestCode, int resultCode, Intent data) {
            super.onActivityResult(requestCode, resultCode, data);
        }
    
        @Override
        protected void onPause() {
            super.onPause();
        }
    
        @Override
        protected void onStop() {
            super.onStop();
        }
    
        @Override
        protected void onDestroy() {
            super.onDestroy();
        }
    }
    
    

    MainPluginActivity界面XML

    <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        xmlns:tools="http://schemas.android.com/tools"
        style="@style/MatchMatch"
        android:gravity="center"
        android:orientation="vertical"
        tools:context="com.fly.newstart.plugin.MainPluginActivity">
    
        <TextView style="@style/WrapWrap"
            android:text="这里是主App"/>
    
        <Button
            android:id="@+id/btnLoad"
            style="@style/WrapWrap"
            android:text="加载插件app" />
    
        <Button
            android:id="@+id/btnSkip"
            style="@style/WrapWrap"
            android:text="跳转到插件app" />
    
    </LinearLayout>
    
    

    文件处理工具

    package com.fly.newstart.utils;
    
    import android.content.Context;
    import android.widget.Toast;
    
    import java.io.File;
    import java.io.FileOutputStream;
    import java.io.InputStream;
    
    /**
     * <pre>
     *           .----.
     *        _.'__    `.
     *    .--(Q)(OK)---/$\
     *  .' @          /$$$\
     *  :         ,   $$$$$
     *   `-..__.-' _.-\$/
     *         `;_:    `"'
     *       .'"""""`.
     *      /,  FLY  ,\
     *     //         \\
     *     `-._______.-'
     *     ___`. | .'___
     *    (______|______)
     * </pre>
     * 包    名 : com.fly.newstart.utils
     * 作    者 : FLY
     * 创建时间 : 2019/5/9
     * 描述: 文件处理工具类
     */
    public class FileUtils {
    
        /**
         * 将Assets目录的fileName文件拷贝到app缓存目录
         *
         * @param context
         * @param fileName
         * @return
         */
        public static String copyAssetAndWrite(Context context, String fileName) {
            try {
                File cacheDir = context.getCacheDir();
                if (!cacheDir.exists()) {
                    cacheDir.mkdir();
                }
                File outFile = new File(cacheDir, fileName);
                if (!outFile.exists()) {
                    boolean res = outFile.createNewFile();
                    if (res) {
                        InputStream is = context.getAssets().open(fileName);
                        FileOutputStream fos = new FileOutputStream(outFile);
                        byte[] buffer = new byte[is.available()];
                        int byteCount;
                        while ((byteCount = is.read(buffer)) != -1) {
                            fos.write(buffer, 0, byteCount);
                        }
                        fos.flush();
                        is.close();
                        fos.close();
                        Toast.makeText(context, "下载成功", Toast.LENGTH_LONG).show();
                    }
                } else {
                    Toast.makeText(context, "文件以存在", Toast.LENGTH_LONG).show();
                }
                return outFile.getAbsolutePath();
            } catch (Exception e) {
                e.printStackTrace();
            }
            return "";
        }
    }
    
    

    插件activity

    package com.shangyi.android.pluginapk;
    
    import android.os.Bundle;
    
    import com.shangyi.android.pluginlibrary.PuglinActivity;
    
    public class PluginActivity extends PuglinActivity {
    
        @Override
        public void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_plugin);
    
        }
    }
    
    

    PluginActivity 的XML

    <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        xmlns:tools="http://schemas.android.com/tools"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical"
        android:gravity="center"
        tools:context="com.shangyi.android.pluginapk.PluginActivity">
    
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="我是插件app界面"/>
    
    </LinearLayout>
    
    

    相关文章

      网友评论

        本文标题:自我提升之四 动态加载第三方应用-插件化详解

        本文链接:https://www.haomeiwen.com/subject/ctbwoqtx.html