Proxy-based Android plug-in framework

Source: Internet
Author: User

Proxy-based Android plug-in framework
Meaning

The significance of the plug-in framework is as follows:

  • Reduce the volume of the installation package, and deliver modular upgrades through the network to reduce silent network traffic upgrades, upgrade without perception to solve the problem that the number of models in earlier versions exceeds the threshold, leading to uninstallation. Code decoupling status

    The plug-in framework technology in Android has been discussed and implemented many times. Plug-ins are usually packaged in the form of apk or dex.

    Dex plug-ins often provide some functional interfaces. This method is similar to the jar form in java, but because the Dalvik VM of Android cannot directly and dynamically load the Byte Code of Java, therefore, we need to provide Dalvik Byte Code, while dex is a jar in the form of Dalvik Byte Code.

    Plug-ins in apk form provide more functions than dex form. For example, you can package resources into apk, or implement system components such as Activity or Service in the plug-in.

    This article mainly discusses the plug-in framework in the apk form. There are two ways to install and not install the apk form.

    The installation of apk is relatively simple. The main principle is to share the plug-in apk with the main program with a UserId.createPackageContextConstruct the context of the plug-in to access the resources in the plug-in apk through context. Many app theme frameworks are implemented by installing the plug-in apk, such as the Go topic. The disadvantage of this method is that you need to install it manually, and the experience is not very good.

    The method of not installing apk solves the disadvantages of manual installation, but the implementation is complicated, mainly throughDexClassloaderTo ensure the flexibility of the plug-in framework, these system components are not well declared in the main program in advance, the real difficulty in implementing the plug-in framework lies in this.

    DexClassloader

    Here is a description of the java class loader in the second edition of "deep understanding of java Virtual Machine: JVM advanced features and Best Practices:

    The virtual machine design team put the "get and describe the binary byte stream of this class through the full qualified name of a class" action in the class loading stage outside the Java Virtual Machine for implementation, so that the application can decide how to obtain the required classes. The Code module that implements this action is called the class loader ".

    The implementation of the Android Virtual Machine refers to the java JVM. Therefore, the class Loader concept is also used for loading classes in Android, but compared with the class file loaded by the JVM loader, the Dalvik Virtual Machine of Android loads the Dex format.PathClassloaderAndDexclassloader.

    PathClassloaderRead by default/data/dalvik-cacheThe cached dex file. If an uninstalled apk is usedPathClassloaderIn/data/dalvik-cacheThe corresponding dex cannot be found in the directory, so it will throwClassNotFoundException.

    DexClassloaderYou can load dex and apk files in any path. by specifying the path generated by odex, you can load uninstalled apk files. The following code showsDexClassloaderUsage:

    final File optimizedDexOutputPath = context.getDir(odex, Context.MODE_PRIVATE);try{    DexClassLoader classloader = new DexClassLoader(apkPath,            optimizedDexOutputPath.getAbsolutePath(),            null, context.getClassLoader());    Class
         clazz = classloader.loadClass(com.plugindemo.test);    Object obj = clazz.newInstance();    Class[] param = new Class[2];    param[0] = Integer.TYPE;    param[1] = Integer.TYPE;    Method method = clazz.getMethod(add, param);    method.invoke(obj, 1, 2);}catch(InvocationTargetException e){    e.printStackTrace();}catch(NoSuchMethodException e){    e.printStackTrace();}catch(IllegalAccessException e){    e.printStackTrace();}catch(ClassNotFoundException e){    e.printStackTrace();}catch (InstantiationException e){    e.printStackTrace();}

    DexClassloaderSolved the class loading problem. If the plug-in apk only contains some simple API calls, the above Code can meet the requirements, however, the plug-in framework discussed here also needs to address resource access and Android system component calls.

    Call of system components in the plug-in

    Android Framework containsActivity,Service,Content ProviderAndBroadcastReceiverThe following describes how to start the Activity in the plug-in the main program. The call method for the other three components is similar.

    We all know that the Activity needs to be declared in AndroidManifest. xml. When apk is installedPackageManagerServiceThe AndroidManifest. xml file in the apk will be parsed. At this time, the activities contained in the program will be determined.ActivityNotFoundException: I believe most Android Developers have encountered this exception.

    Starting the Activity in the plug-in will inevitably face how AndroidManifest in the main program. this Activity is declared in xml. However, to ensure the flexibility of the plug-in framework, we cannot predict which activities are included in the plug-in, so we cannot declare them in advance.

    To solve the above problem, we will introduce a Proxy-based solution. The general principle is to declare someProxyActivityTo start the Activity in the plug-in.ProxyActivity,ProxyActivityAll system callbacks in will call the corresponding implementation in the plug-in Activity. The final effect is that the started Activity is actually a declared Activity in the main program, however, the code is executed in the plug-in Activity. This solves the problem that the plug-in Activity cannot be started without being declared. In the upper layer, the Activity in the plug-in is started. The following describes the entire process.

    PluginSDK

    All plug-ins and main programs must rely on PluginSDK for development. The activities in all plug-ins inherit fromBasePluginActivity,BasePluginActivityInherited fromActivityAnd implementsIPluginActivityInterface.

    public interface IPluginActivity {    public void IOnCreate(Bundle savedInstanceState);    public void IOnResume();    public void IOnStart();    public void IOnPause();    public void IOnStop();    public void IOnDestroy();    public void IOnRestart();    public void IInit(String path, Activity context, ClassLoader classLoader, PackageInfo packageInfo);}
    public class BasePluginActivity extends Activity implements IPluginActivity {    ...    private ClassLoader mDexClassLoader;    private Activity mActivity;    ...        @Override    public void IInit(String path, Activity context, ClassLoader classLoader, PackageInfo packageInfo) {        mIsRunInPlugin = true;        mDexClassLoader = classLoader;        mOutActivity = context;        mApkFilePath = path;        mPackageInfo = packageInfo;        mContext = new PluginContext(context, 0, mApkFilePath, mDexClassLoader);        attachBaseContext(mContext);    }        @Override    protected void onCreate(Bundle savedInstanceState) {        if (mIsRunInPlugin) {            mActivity = mOutActivity;        } else {            super.onCreate(savedInstanceState);            mActivity = this;        }    }    @Override    public void setContentView(int layoutResID) {        if (mIsRunInPlugin) {            mContentView = LayoutInflater.from(mContext).inflate(layoutResID, null);            mActivity.setContentView(mContentView);        } else {            super.setContentView(layoutResID);        }    }    ...    @Override    public void IOnCreate(Bundle savedInstanceState) {        onCreate(savedInstanceState);    }    @Override    public void IOnResume() {        onResume();    }    @Override    public void IOnStart() {        onStart();    }    @Override    public void IOnPause() {        onPause();    }    @Override    public void IOnStop() {        onStop();    }    @Override    public void IOnDestroy() {        onDestroy();    }    @Override    public void IOnRestart() {        onRestart();    }}
    public class PluginProxyActivity extends Activity {    IPluginActivity mPluginActivity;    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        Bundle bundle = getIntent().getExtras();        if(bundle == null){            return;        }        mPluginName = bundle.getString(PluginStatic.PARAM_PLUGIN_NAME);        mLaunchActivity = bundle.getString(PluginStatic.PARAM_LAUNCH_ACTIVITY);        File pluginFile = PluginUtils.getInstallPath(PluginProxyActivity.this, mPluginName);        if(!pluginFile.exists()){            return;        }        mPluginApkFilePath = pluginFile.getAbsolutePath();        try {            initPlugin();            super.onCreate(savedInstanceState);            mPluginActivity.IOnCreate(savedInstanceState);        } catch (Exception e) {            mPluginActivity = null;            e.printStackTrace();        }    }    @Override    protected void onResume() {        super.onResume();        if(mPluginActivity != null){            mPluginActivity.IOnResume();        }    }    @Override    protected void onStart() {        super.onStart();        if(mPluginActivity != null) {            mPluginActivity.IOnStart();        }    }        ...            private void initPlugin() throws Exception {        PackageInfo packageInfo;        try {            PackageManager pm = getPackageManager();            packageInfo = pm.getPackageArchiveInfo(mPluginApkFilePath, PackageManager.GET_ACTIVITIES);        } catch (Exception e) {            throw e;        }        if (mLaunchActivity == null || mLaunchActivity.length() == 0) {            mLaunchActivity = packageInfo.activities[0].name;        }//        String optimizedDexOutputPath = getDir(odex, Context.MODE_PRIVATE).getAbsolutePath();        ClassLoader classLoader = PluginStatic.getOrCreateClassLoaderByPath(this, mPluginName, mPluginApkFilePath);        if (mLaunchActivity == null || mLaunchActivity.length() == 0) {            if (packageInfo == null || (packageInfo.activities == null) || (packageInfo.activities.length == 0)) {                throw new ClassNotFoundException(Launch Activity not found);            }            mLaunchActivity = packageInfo.activities[0].name;        }        Class
         mClassLaunchActivity = (Class
        ) classLoader.loadClass(mLaunchActivity);        getIntent().setExtrasClassLoader(classLoader);        mPluginActivity = (IPluginActivity) mClassLaunchActivity.newInstance();        mPluginActivity.IInit(mPluginApkFilePath, this, classLoader, packageInfo);    }        ...        @Override    public void startActivityForResult(Intent intent, int requestCode) {        boolean pluginActivity = intent.getBooleanExtra(PluginStatic.PARAM_IS_IN_PLUGIN, false);        if (pluginActivity) {            String launchActivity = null;            ComponentName componentName = intent.getComponent();            if(null != componentName) {                launchActivity = componentName.getClassName();            }            intent.putExtra(PluginStatic.PARAM_IS_IN_PLUGIN, false);            if (launchActivity != null && launchActivity.length() > 0) {                Intent pluginIntent = new Intent(this, getProxyActivity(launchActivity));                pluginIntent.putExtra(PluginStatic.PARAM_PLUGIN_NAME, mPluginName);                pluginIntent.putExtra(PluginStatic.PARAM_PLUGIN_PATH, mPluginApkFilePath);                pluginIntent.putExtra(PluginStatic.PARAM_LAUNCH_ACTIVITY, launchActivity);                startActivityForResult(pluginIntent, requestCode);            }        } else {            super.startActivityForResult(intent, requestCode);        }    }}

    BasePluginActivityAndPluginProxyActivityAt the core of the entire plug-in framework, the following code is analyzed:

    First, let's take a look.PluginProxyActivity#onResume:

    @Overrideprotected void onResume() {    super.onResume();    if(mPluginActivity != null){        mPluginActivity.IOnResume();    }}

    VariablemPluginActivityIsIPluginActivityBecause the plug-in Activity implementsIPluginActivityInterface, so you can guessmPluginActivity.IOnResume()The final execution of the plug-in ActivityonResumeIn the code, we will confirm this speculation.

    BasePluginActivityImplementedIPluginActivityInterfaces, how are these interfaces implemented? Check the Code:

    @Overridepublic void IOnCreate(Bundle savedInstanceState) {    onCreate(savedInstanceState);}@Overridepublic void IOnResume() {    onResume();}@Overridepublic void IOnStart() {    onStart();}@Overridepublic void IOnPause() {    onPause();}...

    The interface implementation is very simple, but the callback function corresponding to the interface is called. Where will the callback function be adjusted? As mentioned above, all plug-in activities will inherit fromBasePluginActivityThat is to say, the callback function will be eventually adjusted to the corresponding callback in the plug-in Activity, suchIOnResumeThe execution is in the plug-in Activity.onResumeCode, which also proves the previous speculation.

    Some of the above code snippets reveal the core logic of the plug-in framework. Other code is more about implementing this logic service, and the source code of the entire project will be provided later, you can analyze and understand it on your own.

    Obtain resources in the plug-in

    One way to load Resources in the plug-in apk is to add the path of the plug-in apk to the path of the main program resource search. The following code shows this method:

    private AssetManager getSelfAssets(String apkPath) {    AssetManager instance = null;    try {        instance = AssetManager.class.newInstance();        Method addAssetPathMethod = AssetManager.class.getDeclaredMethod(addAssetPath, String.class);        addAssetPathMethod.invoke(instance, apkPath);    } catch (Throwable e) {        e.printStackTrace();    }    return instance;}

    To allow the plug-in Activity to use our custom Context when accessing resources, we needBasePluginActivityIn initialization:

    public void IInit(String path, Activity context, ClassLoader classLoader, PackageInfo packageInfo) {    mIsRunInPlugin = true;    mDexClassLoader = classLoader;    mOutActivity = context;    mApkFilePath = path;    mPackageInfo = packageInfo;    mContext = new PluginContext(context, 0, mApkFilePath, mDexClassLoader);    attachBaseContext(mContext);}

    PluginContextBy reloadinggetAssetsTo implement the Context that contains the apk search path of the plug-in:

    public PluginContext(Context base, int themeres, String apkPath, ClassLoader classLoader) {    super(base, themeres);    mClassLoader = classLoader;    mAsset = getSelfAssets(apkPath);    mResources = getSelfRes(base, mAsset);    mTheme = getSelfTheme(mResources);    mOutContext = base;}private AssetManager getSelfAssets(String apkPath) {    AssetManager instance = null;    try {        instance = AssetManager.class.newInstance();        Method addAssetPathMethod = AssetManager.class.getDeclaredMethod(addAssetPath, String.class);        addAssetPathMethod.invoke(instance, apkPath);    } catch (Throwable e) {        e.printStackTrace();    }    return instance;}private Resources getSelfRes(Context ctx, AssetManager selfAsset)   {    DisplayMetrics metrics = ctx.getResources().getDisplayMetrics();    Configuration con = ctx.getResources().getConfiguration();    return new Resources(selfAsset, metrics, con);}private Theme getSelfTheme(Resources selfResources) {    Theme theme = selfResources.newTheme();    mThemeResId = getInnerRIdValue(com.android.internal.R.style.Theme);    theme.applyStyle(mThemeResId, true);    return theme;}@Overridepublic Resources getResources() {    return mResources;}@Overridepublic AssetManager getAssets() {    return mAsset;}...
    Summary

    This article describes a Proxy-based plug-in framework. All the code is in Github, and the code only extracts the core part of the entire framework. If it needs to be improved in the production environment, for exampleContent ProviderAndBroadcastReceiverThe Proxy class of the component is not implemented, and the Proxy implementation of the Activity is incomplete, including many callbacks that are not processed. At the same time, I cannot guarantee that this framework does not have any fatal defects. This article aims to summarize and learn. We welcome you to discuss it together.

    Related Article

    Contact Us

    The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

    If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

    A Free Trial That Lets You Build Big!

    Start building with 50+ products and up to 12 months usage for Elastic Compute Service

    • Sales Support

      1 on 1 presale consultation

    • After-Sales Support

      24/7 Technical Support 6 Free Tickets per Quarter Faster Response

    • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.