Android插件化

学习完组件化,紧接着又学习了插件化,这里写了一个小demo记录一下学习成果。首先我们来了解一下什么是插件化?每个业务组件就是一个独立的apk,然后通过主app动态加载部署业务组件apk。那么插件化的好处是是什么呢?首先可以实现业务组件解耦,能够实现业务组件热插拔;其次可以更改产品迭代模式,可分为主app和组件app业务;最后可以改善产品更新过程,可以在不影响用户的情况下实现业务组件的更新以及bug修复。

接下来我们来了解一下插件化需要的思想。我们知道主app的apk文件是被系统安装调用,这个工作由系统来完成,那我们的插件apk呢?这就需要我们开发者自己来实现加载的过程,简言之,需要将插件apk文件看成是一个非apk的文件,只是一个和apk文件相似的一个文件,调用插件即用某种特殊的方式打开这个文件。

接下来,我们来了解一下插件化的步骤,首先我们来分析一下一个apk文件的结构,我们将一个apk文件解压后,会形成dex、images、xml几个文件,dex文件靠DexClassLoader来加载,图片以及xml资源靠Resources来加载。当我们了解了这些以后,我们要来加载插件apk文件,就要创建DexClassLoader来加载dex文件,创建Resources来加载资源文件,当然我们还需要管理插件的activity生命周期。

好了,说了这么多,接下来我们就按照这个思路来上代码。

1,创建PluginApk,这里面主要包括PackageInfo、Resources、AssetManager、DexClassLoader

public class PluginApk {
    public PackageInfo mPackageInfo;
    public Resources mResources;
    public AssetManager mAssetManager;
    public DexClassLoader mDexClassLoader;

    public PluginApk(PackageInfo packageInfo, Resources resources, DexClassLoader dexClassLoader) {
        mPackageInfo = packageInfo;
        mResources = resources;
        mAssetManager = resources.getAssets();
        mDexClassLoader = dexClassLoader;
    }
}

2,创建PluginManager,这里面主要是来创建DexClassLoader、Resources等来加载apk

public class PluginManager {
    private static final PluginManager instance = new PluginManager();
    private              Context       mContext;
    private              PluginApk     mPluginApk;


    private PluginManager() {

    }

    public static PluginManager getInstance() {
        return instance;
    }

    public PluginApk getPluginApk() {
        return mPluginApk;
    }

    public PluginManager init(Context context) {
        mContext = context.getApplicationContext();
        return this;
    }

    /**
     * 加载apk文件
     * @param apkPath
     */
    public void loadApk(String apkPath) {
        PackageInfo packageInfo = mContext.getPackageManager().getPackageArchiveInfo(apkPath, PackageManager.GET_ACTIVITIES | PackageManager.GET_SERVICES
               | PackageManager.GET_PROVIDERS | PackageManager.GET_RECEIVERS);
        if (packageInfo == null) {
            return;
        }
        AssetManager manager = createAssetMangager(apkPath);
        Resources resources = createResources(manager);
        DexClassLoader loader = createClassLoader(apkPath);
        mPluginApk = new PluginApk(packageInfo, resources, loader);
    }

   
    private AssetManager createAssetMangager(String apkPath) {
        try {
            AssetManager am = AssetManager.class.newInstance();
            Method method = AssetManager.class.getDeclaredMethod("addAssetPath", String.class);
            method.invoke(am, apkPath);
            return am;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    private DexClassLoader createClassLoader(String apkPath) {

        File apkFile = new File(apkPath);

        return new DexClassLoader(apkPath, apkFile.getAbsolutePath(), null, mContext.getClassLoader());
    }

    private Resources createResources(AssetManager am) {
        Resources res = mContext.getResources();

        return new Resources(am, res.getDisplayMetrics(), res.getConfiguration());
    }
}

3,创建一个代理activity来管理插件activity的生命周期

public class ProxyActivity extends Activity {
    private String    mClassName;
    private PluginApk mPluginApk;
    private IPlugin   mIPlugin;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        mClassName = getIntent().getStringExtra(Constant.CLASS_NAME);
        mPluginApk = PluginManager.getInstance().getPluginApk();

        lauchPluginApk();
    }

    private void lauchPluginApk() {
        if (mPluginApk == null) {
            //            throw new RuntimeException("请先加载apk");
            Toast.makeText(this, "请先加载apk", Toast.LENGTH_SHORT).show();
        }
        try {
            Class loadClass = mPluginApk.mDexClassLoader.loadClass(mClassName);
            Log.e("====>", loadClass.getName() + "");
            Object instance = loadClass.newInstance();

            if (instance instanceof IPlugin) {
                mIPlugin = (IPlugin) instance;
                mIPlugin.onAttach(this);
                Bundle bundle = new Bundle();
                bundle.putInt(Constant.FROM, IPlugin.FROM_EXTERNAL);
                mIPlugin.onCreate(bundle);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

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

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

    @Override
    public ClassLoader getClassLoader() {
        return mPluginApk == null ? super.getClassLoader() : mPluginApk.mDexClassLoader;
    }
}

IPlugin中主要管理activity的生命周期的方法

public interface IPlugin {
    int FROM_INTERNAL = 0;//来自内部的跳转
    int FROM_EXTERNAL = 1;//来自外部的跳转

    void onAttach(Activity activity);

    void onCreate(Bundle savedInstanceState);

    void onStart();

    void onResume();

    void onRestart();

    void onStop();

    void onPause();

    void onDestroy();

    void onActivityResult(int requestCode, int resultCode, @Nullable Intent data);
}

4,创建PluginActivity实现IPlugin,插件中的activity需要继承

public class PluginActivity extends Activity implements IPlugin {
    private int      mFrom = FROM_INTERNAL;
    private Activity mActivity;
    public  Bundle   mBundle;

    @Override
    public void onAttach(Activity activity) {
        mActivity = activity;

    }

    @Override
    public void onCreate(Bundle savedInstanceState) {
        if (savedInstanceState != null) {
            mFrom = savedInstanceState.getInt(Constant.FROM);
        }
        if (mFrom == FROM_INTERNAL) {
            super.onCreate(savedInstanceState);
            mActivity = this;
        }


    }

    @Override
    public void setContentView(int layoutResID) {
        if (mFrom == FROM_INTERNAL) {
            super.setContentView(layoutResID);
        } else {
            mActivity.setContentView(layoutResID);

        }


    }

    @Override
    public void onStart() {
        if (mFrom == FROM_INTERNAL) {
            super.onStart();
        }
    }

    @Override
    public void onResume() {
        if (mFrom == FROM_INTERNAL) {
            super.onResume();
        }

    }

    @Override
    public void onRestart() {
        if (mFrom == FROM_INTERNAL) {
            super.onRestart();
        }
    }

    @Override
    public void onStop() {
        if (mFrom == FROM_INTERNAL) {
            super.onStop();
        }
    }

    @Override
    public void onPause() {
        if (mFrom == FROM_INTERNAL) {
            super.onPause();
        }

    }

    @Override
    public void onDestroy() {
        if (mFrom == FROM_INTERNAL) {
            super.onDestroy();
        }
    }

    @Override
    public void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
        if (mFrom == FROM_INTERNAL) {
            super.onActivityResult(requestCode, resultCode, data);
        }
    }
}

5,在插件的入口activity需要继承PluginActivity

public class MainActivity extends PluginActivity {

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

    }


    @Override
    public void onDestroy() {
        super.onDestroy();
    }
}

6,在主app中加载插件apk和跳转到插件apk

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        //加载插件apk文件
        if (PluginManager.getInstance().getPluginApk() == null) {
            String path = copyAssetsFileToAppFiles("moduleplugin.apk");
            PluginManager.getInstance().init(this).loadApk(path);
        }


        findViewById(R.id.btnLoad).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                String path = copyAssetsFileToAppFiles("moduleplugin.apk");
                PluginManager.getInstance().loadApk(path);

            }
        });
        findViewById(R.id.btnNext).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Intent intent = new Intent(MainActivity.this, ProxyActivity.class);
                intent.putExtra(Constant.CLASS_NAME, "com.carlt.moduleplugin.MainActivity");

                startActivity(intent);
            }
        });
    }

加载整个apk的过程,是仿照系统加载apk的过程。最后附上demo的源码,有兴趣的同学可以下载后运行验证。

https://github.com/allenlzhang/CarltPluginDemo

你可能感兴趣的:(Android插件化)