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
- Binder
- APP packaging process
- APP Installation Process
- APP Startup Process
- Resource loading mechanism
- Reflection, this
- .
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
-
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
-
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
-
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
-
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
-
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
-
The received message is returned to the plug-in
@Override public void onReceive(Context context, Intent intent) { iBroadcast.onReceive(context, intent); } Copy the code
-
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
-
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
-
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
-
Plug-in services implement IService
public class BaseServiceImp extends Service implements IService {... }Copy the code
-
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