preface

Android technology is now very mature, modular, plug-in framework emerge in endlessly, hot fix, and so on, if only on the framework, pure will use technology never grow, only know its principle, che wan wan to to, can write manually, technology will grow, instead of anxiety in the future, grasp now. This article will teach you to write a plug-in framework, plug-in technology is one of the Android senior engineers necessary technology, understand its ideas, know its principle. This special topic will be divided into 10 articles to explain the plug-in technology in detail. Only by digging deeply into a technical field can we understand how to develop in a broader and horizontal way.

This topic code address

What is plug-in?

Pluggable technology originated from the idea of running APK installation, the installation of the APK is a plugin that supports pluggable app can be loaded at run time and run the plug-in, so some not commonly used in the app can be functional modules into a plug-in, on the one hand, reduce the size of the installation package, on the other hand can realize dynamic app function extension.

There’s an app called 23Code that’s been around for the last couple of years, and I don’t know if you know it, but it’s really popular because you can download a demo and run it, and it’s kind of the first plug-in app.

There are no new plug-in projects, but popular frameworks are as follows:

The first generation: dynamic-load-APK was the first to use ProxyActivity, a static proxy technology, to control the life cycle of PluginActivity in plug-ins by ProxyActivity. The disadvantages of this approach are obvious. Activities in plug-ins must inherit from pluginActivities, and the context should be handled carefully during development. DroidPlugin starts the Activity in the plug-in by Hook system service, making the process of plug-in development no different from that of ordinary APP development. However, due to too many Hook system services, it is extremely complicated and not stable.

The second generation: In order to achieve both the low invasions of plug-in development (developing plug-ins as common APP development) and the stability of the framework, the implementation principle tends to select as few hooks as possible, and realize the plug-in of the four major components by embedding some components in the manifest. In addition, various frameworks have been expanded to different degrees according to their design ideas, among which Small is made into a cross-platform, componentized development framework.

The third generation: VirtualApp is more powerful, can completely simulate the running environment of app, can realize the app free installation and double open technology. Atlas is an app basic framework which combines componentization and hot repair technology, and is widely used in various apps of Ali department. It claims to be a container framework.

The future of plug-ins: ReactNative was popular last year. Although it is unlikely to become the final solution, the Web of mobile applications is an inevitable trend, just like the change from C/S to B/S of desktop applications. From Google’s launch of Flutter this year, mobile apps are increasingly web-based.

What’s the difference between pluginization and componentization?

As for what is componentization, you can read my topic on componentization, right

Componentization is the division of modules into independent components, each component into the APK package.

Each plug-in component can be separately into apK package, download the APK package when necessary, and load and run.

Principle of plug-in

1. Design the acceptance standard, which is related to the life cycle.

Design the plug-in to comply with this standard, because the plug-in is not installed (context is not available), you need to pass a context to the plug-in.

3 Use an empty shell Activity to put the content of the plug-in.

4 Reflection gets the full class name of MainActivity in Apk.

Five loading

As you can see from the figure above, the core code to start a price difference is ProxyActivity and PluginInterfaceActivity. Let’s focus on the core idea of the code.

The code is actually very simple, the process of launching the plug-in:

  1. You first need an empty shell of the ProxyActivity to start the plug-in’s Activity.
  2. ProxyActivity takes the ClassLoader and Resource in the plug-in according to the apK package information, and then converts the MainActivity into PluginInterfaceActivity through reflection and creation, and calls its onCreate method.
  3. The setContentView that the plugin calls has been overwritten, so it calls the setContentView of ProxyActivity. Since the getClassLoader and gerResource of ProxyActivity have been overwritten, they are resources in the plugin. So the setContentView of ProxyActivity can access resources in the plug-in, and findViewById also calls ProxyActivity.
  4. The life cycle of the corresponding PluginActivity is called in other life cycle callback functions in ProxyActivity.

Go straight to code

The following code defines an interface that the plug-in Activity must implement, or a standard to define. Since the plug-in is not installed on the phone, the context cannot be obtained, and the life cycle cannot be called naturally. We need to pass an empty shell of the host ProxyActivity to the plug-in to pass the life cycle. Any method that a plug-in needs to use context needs to be overridden.

Public interface PluginInterfaceActivity {/** * start the Activity ** @param Activity */ void attach(Activity) activity); / / -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- to the plug-in life cycle -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- / / void onCreate (@ Nullable Bundle savedInstanceState); void onStart(); void onResume(); void onPause(); void onStop(); void onRestart(); void onDestroy(); void onSaveInstanceState(Bundle outState); boolean onTouchEvent(MotionEvent event); void onBackPressed(); }Copy the code

Override the methods that must be used. The plugin’s activities need to inherit BaseActivity

public class BaseActivity extends AppCompatActivity implements PluginInterfaceActivity {

    protected Activity mActivity;

    @Override
    public void attach(Activity proxyActivity) {
        this.mActivity = proxyActivity;
    }


    @Override
    public void onCreate(@Nullable Bundle savedInstanceState) {

    }

    @Override
    public void onStart() {

    }

    @Override
    public void onResume() {

    }

    @Override
    public void onPause() {

    }

    @Override
    public void onStop() {

    }

    @Override
    public void onRestart() {

    }

    @Override
    public void onDestroy() {} @ Override public void onSaveInstanceState (Bundle outState) {} / / -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- - all the places of use context needs to be rewritten, Need to rewrite the method -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- - / / / * * * here we will use the context of the host passed to load the view * * @ param view * / @ Override public voidsetContentView(View view) {
        if(mActivity ! = null) { mActivity.setContentView(view); }else {
            super.setContentView(view);
        }
    }

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

    @Override
    public <T extends View> T findViewById(int id) {
        if(mActivity ! = null) {return mActivity.findViewById(id);
        } else {
            return super.findViewById(id);
        }
    }

    @Override
    public Intent getIntent() {
        if(mActivity ! = null) {return mActivity.getIntent();
        }
        return super.getIntent();
    }

    @Override
    public ClassLoader getClassLoader() {
        if(mActivity ! = null) {return mActivity.getClassLoader();
        }
        return super.getClassLoader();
    }

    @Override
    public LayoutInflater getLayoutInflater() {
        if(mActivity ! = null) {return mActivity.getLayoutInflater();
        }
        return super.getLayoutInflater();
    }

    @Override
    public ApplicationInfo getApplicationInfo() {
        if(mActivity ! = null) {return mActivity.getApplicationInfo();
        }
        return super.getApplicationInfo();
    }

    @Override
    public Window getWindow() {
        return mActivity.getWindow();
    }


    @Override
    public WindowManager getWindowManager() {
        return mActivity.getWindowManager();
    }

    @Override
    public void startActivity(Intent intent) {
        if(mActivity ! Intent1 = new Intent(); intent1.putExtra("className", intent.getComponent().getClassName());
            mActivity.startActivity(intent1);
        } else {
            super.startActivity(intent);
        }
    }

    @Override
    public ComponentName startService(Intent service) {
        if(mActivity ! = null) { Intent m = new Intent(); m.putExtra("serviceName", service.getComponent().getClassName());
            return mActivity.startService(m);
        } else {
            returnsuper.startService(service); }}}Copy the code

Now, how does the host launch the plug-in? The method of using an empty shell of Activity ProxyActivity proxy was first proposed by dynamic-load-APK. Its idea is simple: Put a ProxyActivy in the main project, and the Activity in the plug-in will be started first. Create the plug-in Activity in the ProxyActivity and synchronize the lifecycle.

Public class ProxyActivity extends AppCompatActivity {private String className; private PluginInterfaceActivity pluginInterfaceActivity; @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); className = getIntent().getStringExtra("className"); // Class.forname () // The plugin is not installed on the phone. Try {//className represents the activity's full Class name Class activityClass = getClassLoader().loadClass(className); / / calls the Constructor Constructor constructors. = activityClass getConstructor (new Class [] {}); NewInstance = constructors. NewInstance (new Object[]{}); PluginInterfaceActivity = (pluginInterfaceActivity) newInstance; / / into context pluginInterfaceActivity. Attach (this); Bundle bundle = new Bundle(); // Pass some information to bundle.putString("test"."I'm the host delivering data to you.");
            pluginInterfaceActivity.onCreate(bundle);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }
    }

    @Override
    public void startActivity(Intent intent) {
        String className = intent.getStringExtra("className");
        Intent intent1 = new Intent(this, ProxyActivity.class);
        intent1.putExtra("className", className);
        super.startActivity(intent1);
    }

    @Override
    public ComponentName startService(Intent service) {
        String serviceName = service.getStringExtra("serviceName");
        Intent intent = new Intent(this, ProxyService.class);
        intent.putExtra("serviceName", serviceName);
        returnsuper.startService(intent); } // Override public ClassLoadergetClassLoader() {
        return PluginManager.getInstance().getClassLoader();
    }

    @Override
    public Resources getResources() {
        return PluginManager.getInstance().getResources();
    }

    @Override
    protected void onStart() {
        super.onStart();
        pluginInterfaceActivity.onStart();
    }

    @Override
    protected void onResume() {
        super.onResume();
        pluginInterfaceActivity.onResume();
    }

    @Override
    protected void onPause() {
        super.onPause();
        pluginInterfaceActivity.onPause();
    }

    @Override
    protected void onStop() {
        super.onStop();
        pluginInterfaceActivity.onStop();
    }

    @Override
    protected void onDestroy() { super.onDestroy(); pluginInterfaceActivity.onDestroy(); }}Copy the code

The code to start the plug-in is as follows:

public void loadPlugin(View view) { loadDownPlugin(); Intent intent = new Intent(this, ProxyActivity.class); Intent.putextra (intent.putextra)"className", PluginManager.getInstance().getPackageInfo().activities[0].name);
        startActivity(intent);
    }
Copy the code

In fact, to start a plug-in is to start an empty shell of the host Activity, passing the previous shell of the Activity and its life cycle to the plug-in Activity.

This is the core code for launching the plug-in:

//className represents the full Class name of the activity. Class activityClass = getClassLoader().loadClass(className); / / calls the Constructor Constructor constructors. = activityClass getConstructor (new Class [] {}); NewInstance = constructors. NewInstance (new Object[]{}); PluginInterfaceActivity = (pluginInterfaceActivity) newInstance; / / into context pluginInterfaceActivity. Attach (this); Bundle bundle = new Bundle(); // Pass some information to bundle.putString("test"."I'm the host delivering data to you.");
            pluginInterfaceActivity.onCreate(bundle);
Copy the code

We start an empty shell Activity in the host, load resources in the APK package, and pass the life cycle.

How does MainActivity in a plug-in call OtherActivity in a plug-in? Do I need to rewrite startActivity?

The answer is yes, enabling other activities in the plug-in is essentially creating a new empty shell of Activity.

Public void startActivity(Intent Intent) {public void startActivity(Intent Intent) {if(mActivity ! Intent1 = new Intent(); intent1.putExtra("className", intent.getComponent().getClassName());
            mActivity.startActivity(intent1);
        } else{ super.startActivity(intent); }} // Overwrite the startActivity of ProxyActivity, jump to a new ProxyActivity, and pass the full class name of the jump Activity, @override public void startActivity(Intent Intent) {String className = intent.getStringExtra("className");
        Intent intent1 = new Intent(this, ProxyActivity.class);
        intent1.putExtra("className", className);
        super.startActivity(intent1);
    }
Copy the code

ProxyActivity proxy:

  • ProxyActivity overrides getResouces, getAssets, getClassLoader to return the corresponding object of the plug-in. Lifecycle functions and user-related functions such as onResume, onStop, onBackPressedon, KeyUponWindow, FocusChanged, and so on need to be forwarded to the plug-in.
  • All context related methods in PluginActivity, such as setContentView, getLayoutInflater, getSystemService, etc., need to call the corresponding methods of ProxyActivity.

Invoke the Service in the plug-in

Calling the Activity in the plug-in is actually creating an empty shell of Vity in the host, and then loading the resources in the plug-in and passing the context.

What about calling the Service in the plug-in? On the same principle, on the same principle or create a shell in the host Service ProxyService, ProxyService pass life cycle to the Service of the plugin

You can go to the implementation, here I only give the core code

public class ProxyService extends Service {
    private String serviceName;

    private PluginInterfaceService pluginInterfaceService;

    @Override
    public IBinder onBind(Intent intent) {
        returnnull; } private void init(intentIntent Intent) {// Intent.getStringExtra (intent.getStringExtra)"serviceName"); / / generates a class DexClassLoader this. = PluginManager getInstance (). GetClassLoader (); try { Class<? > aClass = classLoader.loadClass(serviceName); Constructor<? > constructor = aClass.getConstructor(new Class[]{}); NewInstance = constructive.newinstance (new Object[]{}); pluginInterfaceService = (PluginInterfaceService) newInstance; pluginInterfaceService.attach(this); Bundle bundle = new Bundle(); pluginInterfaceService.onCreate(); } catch (ClassNotFoundException e) { e.printStackTrace(); } catch (NoSuchMethodException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (InstantiationException e) { e.printStackTrace(); } catch (InvocationTargetException e) { e.printStackTrace(); } } @Override public int onStartCommand(Intent intent, int flags, int startId) {if (pluginInterfaceService == null) {
            init(intent);
        }
        return pluginInterfaceService.onStartCommand(intent, flags, startId);
    }

    @Override
    public void onStart(Intent intent, int startId) {
        super.onStart(intent, startId);
        pluginInterfaceService.onStart(intent, startId);
    }

    @Override
    public void onConfigurationChanged(Configuration newConfig) {
        super.onConfigurationChanged(newConfig);
        pluginInterfaceService.onConfigurationChanged(newConfig);
    }

    @Override
    public void onLowMemory() {
        super.onLowMemory();
        pluginInterfaceService.onLowMemory();
    }

    @Override
    public void onTrimMemory(int level) {
        super.onTrimMemory(level);
        pluginInterfaceService.onTrimMemory(level);
    }

    @Override
    public boolean onUnbind(Intent intent) {
        pluginInterfaceService.onUnbind(intent);
        return super.onUnbind(intent);
    }

    @Override
    public void onRebind(Intent intent) {
        super.onRebind(intent);
        pluginInterfaceService.onRebind(intent);
    }

    @Override
    public void onTaskRemoved(Intent rootIntent) {
        super.onTaskRemoved(rootIntent);
        pluginInterfaceService.onTaskRemoved(rootIntent);
    }

    @Override
    public void onDestroy() { super.onDestroy(); pluginInterfaceService.onDestroy(); }}Copy the code

ProxyActivity and ProxyService must be registered in the host Manifest.

OK, this article explains the design principles of plugins from the beginning, and it will be further explained step by step.