The basic concept

Plug-in is actually modular -> component evolution, belongs to the dynamic loading technology, mainly used to solve the increasingly large application and the decoupling of functional modules, small projects generally used not much.

Principle: In fact, the principle of plug-in is to dynamically load some executable files that do not exist in the program and run the code logic in these files during the running of the APP shell. Executable files are generally divided into two, one is the dynamic link library SO, the other is dex related files including JAR/APK files.

The development history

Plug-in technology has been studied by companies long ago, Taobao, Alipay is relatively early, but taobao this technology has always been secret, until 2015 or so on the market did not appear some plug-in framework, Android plug-in divided into many technical schools, the way of implementation are not quite the same. Here are a few examples of typical plug-in frameworks using a simple timeline:

time Name of the framework The author Introduction of framework
At the end of 2014 dynamic-load-apk Chairman Ren Yugang Dynamic loading technology + proxy implementation
In August 2015 DroidPlugin 360 Mobile Assistant You can run third-party independent APK files directly without modifying or installing APK at all. A new plug-in mechanism, an installation-free operation mechanism, is a sandbox (but not quite a sandbox). That is, the user doesn’t know what he will do with APK), is the foundation of modularity.
At the end of 2015 Small wequick Small is a lightweight cross-platform plug-in framework based on the concept of “lightweight, transparent, minimal and cross-platform”
In June 2017 VirtualAPK drops VirtualAPK has no additional constraints on plug-ins, and native APK can be used as plug-ins. After the plug-in project is compiled and generated, the APK can be loaded through the host App. After each plug-in APK is loaded, a separate LoadedPlugin object will be created in the host. With these LoadedPlugin objects, VirtualAPK can manage plug-ins and give them new meaning, making them behave like apps installed on your phone.
In July 2017 RePlgin 360 Mobile Guard RePlugin is a complete, stable, all-in-one, hole-in-the-box plug-in solution developed by the RePlugin Team at 360 Mobile Guard, and is the first in the industry to offer “all-in-one plug-in” solutions (full features, full compatibility, full use).
2019 Shadow tencent Shadow is an Android plug-in framework independently developed by Tencent, which has been tested by hundreds of millions of users online. Not only does Shadow open source share the key code for plug-in technology, but it also fully shares all of the design required for live deployment (zero reflection).

Necessary knowledge of plug-in

  1. Binder
  2. APP packaging process
  3. APP Installation Process
  4. APP Startup Process
  5. Resource loading mechanism
  6. Reflection, this
  7. .

Implement simple version plug-in framework

Today we here with dynamic loading technology, ClassLoader + reflection + proxy mode and other basic technologies to achieve dynamic loading APK (Activity, Broadcast, Service, resource) project address

So let’s take a look at one of the things that we finally did

Load the plug-in APK

Before loading APK, let’s take a look at the ClassLoader family, inheritance diagram

DexClassLoader loading process

From the above two figures, we know that dynamic loading of APK requires DexClassLoader. Now that we know how to load APK with DexClassLoader, how to load class when APK -> dex is resolved in Native? According to the DexClassLoader flow chart, loadClass(String classPath) can be directly called to load. Now we will formally proceed to today’s topic.

Code implementation load APK

    /**
     * 加载插件 APK 
     */
    public boolean loadPlugin(Context context, String filePath) {
        if (context == null || filePath == null || filePath.isEmpty())
            throw new NullPointerException("context or filePath is null ?");
        this.mContext = context.getApplicationContext();
        this.apkFilePath = filePath;
        // Get the package management
        packageManager = mContext.getPackageManager();

        if (getPluginPackageInfo(apkFilePath) == null) {
            return false;
        }
        // Get the Activity from the package
        pluginPackageInfo = getPluginPackageInfo(apkFilePath);

        // Save the DEX path
        mDexPath = new File(Constants.IPluginPath.PlugDexPath);
        if (mDexPath.exists())
            mDexPath.delete();
        else
            mDexPath.mkdirs();

        // Load the APK through the DexClassLoader and output the dex through the native layer
        // The second argument can be null
        if (getPluginClassLoader(apkFilePath, mDexPath.getAbsolutePath()) == null || getPluginResources(filePath) == null)
            return false;
        this.mDexClassLoader = getPluginClassLoader(apkFilePath, mDexPath.getAbsolutePath());
        this.mResources = getPluginResources(filePath);
        return true;

    }
Copy the code
*/ public Resources getPluginResources() {return getPluginResources(apkFilePath); } /** * get the loader of the plugin APK ** @param apkFile * @param dexPath * @return */ public DexClassLoader getPluginClassLoader(String apkFile, String dexPath) { return new DexClassLoader(apkFile, dexPath, null, mContext.getClassLoader()); } public DexClassLoader getPluginClassLoader() {return getPluginClassLoader(apkFilePath, mDexPath.getAbsolutePath()); } public PackageInfo getPluginPackageInfo(String apkFilePath) {if (packageManager! = null) return packageManager.getPackageArchiveInfo(apkFilePath, PackageManager.GET_ACTIVITIES); return null; } public PackageInfo getPluginPackageInfo() {return getPluginPackageInfo(apkFilePath); }Copy the code

Load the Activity in the plug-in

The implementation process

Code implementation process

  1. Proxy class ProxyActivity implementation

    public class ProxyActivity extends AppCompatActivity {
    
        /** * The full class name of the plug-in to load */
        protected String activityClassName;
    
        private String TAG = this.getClass().getSimpleName();
        private IActivity iActivity;
        private ProxyBroadcast receiver;
    
        @Override
        protected void onCreate(@Nullable Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
    
            activityClassName = getLoadClassName();
    
            // Get the full class name of the loaded plug-in instantiated by reflection
            try{ Class<? > pluginClassName = getClassLoader().loadClass(activityClassName);// Get the constructorConstructor<? > constructor = pluginClassName.getConstructor(new Class[]{});
                // instantiate the plugin UI
                Object pluginObj = constructor.newInstance(new Object[]{});
                if(pluginObj ! =null) {
                    iActivity = (IActivity) pluginObj;
                    iActivity.onActivityCreated(this, savedInstanceState); }}catch(Exception e) { Log.e(TAG, e.getMessage()); }}}Copy the code
  2. Override startActivity in the proxy class

        /** * where startActivity is invoked */ by the plug-in
        @Override
        public void startActivity(Intent intent) {
          	// The full class name of the Activity that needs to start the plug-in
            String className = getLoadClassName(intent);
            Intent proxyIntent = new Intent(this, ProxyActivity.class);
            proxyIntent.putExtra(Constants.ACTIVITY_CLASS_NAME, className);
            super.startActivity(proxyIntent);
        }
    Copy the code
  3. The plugin Activity implements the IActivity lifecycle and overwrites some important functions, which are handled in the plugin

    public class BaseActivityImp extends AppCompatActivity implements IActivity {
    
        private final String TAG = getClass().getSimpleName();
    
        /** * Proxy Activity */
        protected Activity that;
    
        @Override
        public void onActivityCreated(@NonNull Activity activity, @Nullable Bundle bundle) {
            this.that = activity;
    
            Log.i(TAG, " onActivityCreated");
            onCreate(bundle);
        }
    
        /** * Load ** from View mode@param view
         */
        @Override
        public void setContentView(View view) {
            Log.i(TAG, " setContentView --> view");
            if(that ! =null) {
                that.setContentView(view);
            } else {
                super.setContentView(view); }}/** * Load ** via layoutID@param layoutResID
         */
        @Override
        public void setContentView(int layoutResID) {
            Log.i(TAG, " setContentView --> layoutResID");
            if(that ! =null) {
                that.setContentView(layoutResID);
            } else {
                super.setContentView(layoutResID); }}/** * find the layout ID ** by proxy@param id
         * @param <T>
         * @return* /
        @Override
        public <T extends View> T findViewById(int id) {
            if(that ! =null)
                return that.findViewById(id);
            return super.findViewById(id);
        }
    
        /** * Start the Activity by proxy **@param intent
         */
        @Override
        public void startActivity(Intent intent) {
            if(that ! =null) {
                Intent tempIntent = new Intent();
                tempIntent.putExtra(Constants.ACTIVITY_CLASS_NAME, intent.getComponent().getClassName());
                that.startActivity(tempIntent);
            } else
                super.startActivity(intent);
        }
    
    
    
        @Override
        public String getPackageName(a) {
            return that.getPackageName();
        }
    
        @Override
        public void onActivityStarted(@NonNull Activity activity) {
            Log.i(TAG, " onActivityStarted");
            onStart();
    
    
        }
    
        @Override
        public void onActivityResumed(@NonNull Activity activity) {
            Log.i(TAG, " onActivityResumed");
            onResume();
        }
    
        @Override
        public void onActivityPaused(@NonNull Activity activity) {
            Log.i(TAG, " onActivityPaused");
            onPause();
        }
    
        @Override
        public void onActivityStopped(@NonNull Activity activity) {
            Log.i(TAG, " onActivityStopped");
            onStop();
        }
    
        @Override
        public void onActivitySaveInstanceState(@NonNull Activity activity, @NonNull Bundle bundle) {
            onSaveInstanceState(bundle);
            Log.i(TAG, " onActivitySaveInstanceState");
        }
    
        @Override
        public void onActivityDestroyed(@NonNull Activity activity) {
            Log.i(TAG, " onActivityDestroyed");
            onDestroy();
    
        }
    
    
        @Override
        protected void onCreate(@Nullable Bundle savedInstanceState) {}@Override
        protected void onStart(a) {}@Override
        protected void onResume(a) {}@Override
        protected void onStop(a) {}@Override
        protected void onPause(a) {}@Override
        protected void onSaveInstanceState(Bundle outState) {}@Override
        protected void onDestroy(a) {}@Override
        public void onBackPressed(a) {}}Copy the code

Load the Broadcast in the plug-in

The flow chart


Code implementation

  1. Proxy overrides registration broadcasts in ProxyActivity

        @Override
        public Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter) {
            IntentFilter proxyIntentFilter = new IntentFilter();
            for (int i = 0; i < filter.countActions(); i++) {
                // Inside is an array
                proxyIntentFilter.addAction(filter.getAction(i));
            }
            // Go to proxy broadcast to register
            this.receiver = new ProxyBroadcast(receiver.getClass().getName(), this);
            return super.registerReceiver(this.receiver, filter);
        }
    Copy the code
  2. Load the broadcast full path that needs to be registered in the plug-in

        public ProxyBroadcast(String broadcastClassName, Context context) {
            this.broadcastClassName = broadcastClassName;
            this.iBroadcast = iBroadcast;
    
            // Load the plug-in by DexClassLoader loadClass
            try{ Class<? > pluginBroadcastClassName = PluginManager.getInstance().getPluginClassLoader().loadClass(broadcastClassName); Constructor<? > constructor = pluginBroadcastClassName.getConstructor(new Class[]{});
                iBroadcast = (IBroadcast) constructor.newInstance(new Object[]{});
              // Return to the broadcast life cycle in the plug-in
                iBroadcast.attach(context);
            } catch(Exception e) { e.printStackTrace(); Log.e(TAG, e.getMessage()); }}Copy the code
  3. The received message is returned to the plug-in

        @Override
        public void onReceive(Context context, Intent intent) {
            iBroadcast.onReceive(context, intent);
        }
    Copy the code
  4. Plugin for broadcast registration

        /** * Dynamically register broadcast */
        public void register(a) {
            // Dynamically register broadcasts
            IntentFilter intentFilter = new IntentFilter();
            intentFilter.addAction("_DevYK");
            receiver = new PluginBroadReceiver();
            registerReceiver(receiver, intentFilter);
        }
    
        /** * Register broadcast ** via proxy@param receiver
         * @param filter
         * @return* /
        @Override
        public Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter) {
            if(that ! =null) {
                return that.registerReceiver(receiver, filter);
            } else
                return super.registerReceiver(receiver, filter);
        }
    Copy the code
  5. Plug-in to implement the proxy broadcast in the lifecycle and achieve the receiver function

    public class BaseBroadReceiverImp extends BroadcastReceiver implements IBroadcast {
      // Plug-in broadcast was successfully bound in proxy broadcast
        @Override
        public void attach(Context context) {}// The proxy broadcast receives the data and forwards it to the plug-in
        @Override
        public void onReceive(Context context, Intent intent) {}}Copy the code

Load the Service in the plug-in

The flow chart

Code implementation

  1. ProxyAcitivy Enables services in the plug-in

        /** * Start service in load plug-in *@param service
         * @return* /
        @Override
        public ComponentName startService(Intent service) {
            String className = getLoadServiceClassName(service);
            Intent intent = new Intent(this,ProxyService.class);
            intent.putExtra(Constants.SERVICE_CLASS_NAME,className);
            return super.startService(intent);
        }
    Copy the code

    ProxyService.java

    public class ProxyService extends Service {
    
        private IService iService;
    
        @Override
        public IBinder onBind(Intent intent) {
            return iService.onBind(intent);
        }
    
        @Override
        public void onCreate(a) {
            super.onCreate();
    
        }
    
        @Override
        public int onStartCommand(Intent intent, int flags, int startId) {
            if (iService == null)
                init(intent);
            return iService.onStartCommand(intent, flags, startId);
        }
    
        @Override
        public void onStart(Intent intent, int startId) {
            super.onStart(intent, startId);
           iService.onStart(intent,startId);
        }
    
        @Override
        public boolean onUnbind(Intent intent) {
            iService.onUnbind(intent);
            return super.onUnbind(intent);
        }
    
    
        @Override
        public void onDestroy(a) {
            super.onDestroy();
            iService.onDestroy();
        }
    
    
        / / initialization
        public void init(Intent proIntent) {
            // Get the full class name of the service to start
            String serviceClassName = getServiceClassName(proIntent);
            try{ Class<? > pluginService = PluginManager.getInstance().getPluginClassLoader().loadClass(serviceClassName); Constructor<? > constructor = pluginService.getConstructor(new Class[]{});
                iService = (IService) constructor.newInstance(new Object[]{});
                iService.onCreate(getApplicationContext());
            } catch (Exception e) {
                / / load the class}}@Override
        public ClassLoader getClassLoader(a) {
            return PluginManager.getInstance().getPluginClassLoader();
        }
    
        public String getServiceClassName(Intent intent) {
            returnintent.getStringExtra(Constants.SERVICE_CLASS_NAME); }}Copy the code
  2. Plug-in services implement IService

    public class BaseServiceImp extends Service implements IService {... }Copy the code
  3. Override startService in the plug-in and hand it over to the proxy

        /** * Load the service in the plug-in and hand it over to the agent@param service
         * @return* /
        @Override
        public ComponentName startService(Intent service) {
            String className = getLoadServiceClassName(service);
            Intent intent = new Intent(this,ProxyService.class);
            intent.putExtra(Constants.SERVICE_CLASS_NAME,className);
            return super.startService(intent);
        }
    Copy the code

conclusion

The basic principle of dynamic loading of activities, Broadcast, and Service is to tell the information of the four components that need to be started in the plug-in to the proxy class, and let the proxy class handle the logic in the plug-in. After processing in the proxy class, the plug-in is notified via IActivity, IBroadcast, and IService.

This is the end of this article. If you are interested, please refer to the project address. This implementation is not suitable for online commercial projects. This can be used selectively if only the plug-in lifecycle is used in the project.

Thanks for reading this article, thank you!

Refer to the article

Android Plug-in Development Guide

Summary of Android plug-in framework

Deep understanding of Android plug-in technology

DroidPlugin