Network Security Internet Technology Development Database Servers Mobile Phone Android Software Apple Software Computer Software News IT Information

In addition to Weibo, there is also WeChat

Please pay attention

WeChat public account

Shulou

What is the plug-in of Android

2025-04-15 Update From: SLTechnology News&Howtos shulou NAV: SLTechnology News&Howtos > Development >

Share

Shulou(Shulou.com)06/03 Report--

This article shows you what Android plug-in is like, the content is concise and easy to understand, it can definitely brighten your eyes. I hope you can get something through the detailed introduction of this article.

I. understanding the origin of plug-in 1.1

Plug-in technology originally originated from the idea of running Apk without installation. This installation-free Apk can be understood as a plug-in, and the app that supports plug-ins is generally called a host.

As you all know, in the Android system, applications exist in the form of Apk, and applications need to be installed before they can be used. But in fact, the way to install the application on the Android system is quite simple, which is to copy the application Apk to a different directory of the system, and then extract the so.

Common application installation directories are:

/ system/app: system application

/ system/priv-app: system application

/ data/app: user application

Then you may want to ask, since the installation process is so simple, how does Android run the code in the application? let's first look at the composition of Apk. A common Apk consists of the following parts:

Classes.dex:Java code bytecode

Res: resource fil

Lib:so file

Assets: static asset file

AndroidManifest.xml: manifest file

In fact, after opening the application, the Android system only opens up a process, and then uses ClassLoader to load classes.dex into the process and execute the corresponding components.

Then you may wonder, since Android itself loads code execution in a form similar to reflection, why can't we execute code in an Apk?

1.2 advantages of plug-in

Plug-in allows code in Apk (mainly Android components) to run without installation, which can bring a lot of benefits:

Reduce the size of installing Apk and download modules on demand

Dynamic update plug-in

Separate compilation of host and plug-in to improve development efficiency

Problems with more than 65535 solutions

Imagine that your application has the very high performance of Native applications and can reap the same benefits as Web applications.

Well, the ideal is beautiful, isn't it?

1.3 the difference between componentization and componentization

Componentization: an App is divided into multiple modules, each module is a component (module), the development process can make these components depend on each other or independently compile and debug some components, but these components will eventually be merged into a complete Apk to be released to the application market.

Plug-in: the entire App is divided into many modules, each module is an Apk (each module is a lib). The host Apk and the plug-in Apk are packaged separately when the final package is completed. You only need to publish the host Apk to the application market, and the plug-in Apk is dynamically distributed to the host Apk on demand.

Second, the technical difficulties of plug-in

To make the plug-in Apk really work, we must first find the location of the plug-in Apk, and then we need to be able to parse and load the code in the Apk.

But the light can execute Java code is meaningless, in the Android system, there are four major components need to be registered in the system, specifically in the Android system ActivityManagerService (AMS) and PackageManagerService (PMS) registration, and the parsing and startup of the four major components need to rely on AMS and PMS, how to deceive the system into admitting the components in an uninstalled Apk How to make the host dynamically load the Android components of the execution plug-in Apk (that is, Activity, Service, BroadcastReceiver, ContentProvider, Fragment) is the biggest difficulty of plug-in.

In addition, applying resource references (especially resources referenced in R, such as layout, values, etc.) is also a big problem. Imagine what happens if you use reflection to load a plug-in Apk in the host process, but the id corresponding to R in the code cannot reference the correct resource.

To sum up, there are only a few key points to achieve plug-in:

How to load and execute code in the plug-in Apk (ClassLoader Injection)

Enables the system to call components in the plug-in Apk (Runtime Container)

Correctly identify resources in the plug-in Apk (Resource Injection)

Of course, there are some other minor problems, but they may not be encountered in all scenarios, which we will talk about separately later.

III. ClassLoader Injection

ClassLoader must be mastered in plug-in, because we know that the Android application itself is based on the magic modified Java virtual machine, and because the plug-in is not installed apk, the system will not deal with the classes, so we need to use ClassLoader to load Apk, and then reflect the code inside.

3.1ClassLoader in java

BootstrapClassLoader is responsible for loading the core classes of the JVM runtime, such as JAVA_HOME/lib/rt.jar, etc.

ExtensionClassLoader is responsible for loading JVM extension classes, such as the jar package under JAVA_HOME/lib/ext.

AppClassLoader is responsible for loading jar packages and directories in classpath

3.2 ClassLoader in android

In Android system, ClassLoader is used to load dex files, including apk files containing dex and jar files. Dex file is a product of optimizing class files. When packaging is applied in Android, all class files will be merged and optimized (only one copy of different class files will be retained), and then a final class.dex file will be generated.

PathClassLoader is used to load system classes and application classes. You can load dex files in the installed apk directory.

Public class PathClassLoader extends BaseDexClassLoader {public PathClassLoader (String dexPath, ClassLoader parent) {super (dexPath, null, null, parent);} public PathClassLoader (String dexPath, String libraryPath, ClassLoader parent) {super (dexPath, null, libraryPath, parent);}}

DexClassLoader is used to load dex files, and dex files can be loaded from storage space.

Public class DexClassLoader extends BaseDexClassLoader {public DexClassLoader (String dexPath, String optimizedDirectory, String libraryPath, ClassLoader parent) {super (dexPath, new File (optimizedDirectory), libraryPath, parent);}}

We generally use DexClassLoader in plug-in.

3.3 Parental appointment mechanism

Every ClassLoader has a parent object, which represents the parent class loader. When loading a class, it will first use the parent class loader to load. If it is not found in the parent class loader, it will load itself. If the parent is empty, then use the system class loader to load. Through this mechanism, we can ensure that the system classes are loaded by the system class loader. The following is a concrete implementation of ClassLoader's loadClass method.

Protected Class loadClass (String name, boolean resolve) throws ClassNotFoundException {/ / First, check if the class has already been loaded Class c = findLoadedClass (name) If (c = = null) {try {if (parent! = null) {/ / load c = parent.loadClass (name, false) from the parent loader first;} else {c = findBootstrapClassOrNull (name) }} catch (ClassNotFoundException e) {/ / ClassNotFoundException thrown if class not found / / from the non-null parent class loader} if (c = = null) {/ / not found, then load c = findClass (name) yourself }} return c;} 3.4 how to load classes in a plug-in

To load the classes in the plug-in, we first create a DexClassLoader and look at the parameters required for the constructor of the DexClassLoader.

Public class DexClassLoader extends BaseDexClassLoader {public DexClassLoader (String dexPath, String optimizedDirectory, String librarySearchPath, ClassLoader parent) {/ /...}}

The constructor requires four parameters: dexPath is the path of the dex / apk / jar file to be loaded optimizedDirectory is the location where dex is optimized. On ART, oat is executed to optimize dex and generate machine code. Here is the location where the optimized odex file is stored. LibrarySearchPath is the location that native depends on. Parent is the parent class loader. By default, the corresponding class is loaded from parent.

Once the DexClassLaoder instance is created, you can simply call its loadClass (className) method to load the classes in the plug-in. The specific implementation is as follows:

/ / take the plug-in apk from assets and put it in the internal storage space private fun extractPlugin () {var inputStream = assets.open ("plugin.apk") File (filesDir.absolutePath, "plugin.apk") .writeBytes (inputStream.readBytes ())} private fun init () {extractPlugin () pluginPath = File (filesDir.absolutePath, "plugin.apk"). AbsolutePath nativeLibDir = File (filesDir) "pluginlib"). AbsolutePath dexOutPath = File (filesDir, "dexout"). AbsolutePath / / generate DexClassLoader to load the plug-in class pluginClassLoader = DexClassLoader (pluginPath, dexOutPath, nativeLibDir, this::class.java.classLoader)}

3.5 methods for executing plug-in classes

Execute the method of a class through reflection

Val loadClass = pluginClassLoader.loadClass (activityName) loadClass.getMethod ("test", null) .invoke (loadClass)

We call this process ClassLoader injection. After the injection, all classes from the host are loaded using the host's ClassLoader, and all classes from the plug-in Apk are loaded using the plug-in ClassLoader. Because of ClassLoader's parent delegation mechanism, the system class is actually not affected by ClassLoader's class isolation mechanism, so that the host Apk can use the component classes from the plug-in in the host process.

IV. Runtime Container

We mentioned earlier that the biggest difficulty in Activity plug-in is how to trick the system into admitting components in an uninstalled Apk. Because the plug-in is loaded dynamically, the four components of the plug-in cannot be registered in the host Manifest file, and the four components that are not registered in Manifest cannot interact directly with the system. If you register the Activity of the plug-in directly with the host Manifest, you lose the plug-in dynamic feature, because every time you add an Activity in the plug-in, you have to modify the host Manifest and repackage it, which is no different from writing it directly in the host.

4.1Why can't Activity interact with the system without registration

There are two meanings of not being able to interact directly here.

The system will check whether Activity is registered. If we start an Activity that is not registered in Manifest, we will find the following error:

Android.content.ActivityNotFoundException: Unable to find explicit activity class {com.zyg.commontec/com.zyg.plugin.PluginActivity}; have you declared this activity in your AndroidManifest.xml?

This log can be seen in the checkStartActivityResult method of Instrumentation:

Public class Instrumentation {public static void checkStartActivityResult (int res, Object intent) {if (! ActivityManager.isStartResultFatalError (res)) {return } switch (res) {case ActivityManager.START_INTENT_NOT_RESOLVED: case ActivityManager.START_CLASS_NOT_FOUND: if (intent instanceof Intent & & (Intent) intent). GetComponent ()! = null) throw new ActivityNotFoundException ("Unable to find explicit activity class") + ((Intent) intent) .getComponent () .toShortString () + " Have you declared this activity in your AndroidManifest.xml?); throw new ActivityNotFoundException ("No Activity found to handle" + intent);...}

The life cycle of Activity cannot be called. In fact, the main work of an Activity is called in its life cycle method. Since the previous step of the system detected the Manifest registration file and started Activity was rejected, then its life cycle method will certainly not be called. As a result, the plug-in Activity will not work properly.

4.2 Runtime container technology

Because the components in Android (Activity,Service,BroadcastReceiver and ContentProvider) are created by the system and the lifecycle is managed by the system. It is useless just to construct instances of these classes, and you also need to manage the lifecycle of the components. Among them, Activity is the most complex, and different frameworks adopt different methods. How plug-in supports component lifecycle management. It can be roughly divided into two ways:

Runtime container technology (ProxyActivity proxy)

The process of starting Activity in pre-embedded StubActivity,hook system

Our solution is very simple, that is, run-time container technology, which is simply to pre-bury some empty Android components in the host Apk. In the case of Activity, I preset a ContainerActivity extends Activity in the host and register it in AndroidManifest.xml.

What it needs to do is simply to help us act as a container for the plug-in Activity. It accepts several parameters from Intent, which are different information about the plug-in, such as:

PluginName

PluginApkPath

PluginActivityName

In fact, the most important things are pluginApkPath and pluginActivityName. When ContainerActivity starts, we load the plug-in's ClassLoader, Resource, and reflect the Activity class corresponding to pluginActivityName. When the load is complete, ContainerActivity does two things:

Forward all lifecycle callbacks from the system to the plug-in Activity

Accept the system call of the Activity method and forward it back to the system

We can accomplish the first step by overriding the lifecycle approach of ContainerActivity, while in the second step we need to define a PluginActivity, and then when we write the Activity component in the plug-in Apk, we no longer want it to integrate android.app.Activity, but from our PluginActivity.

Public class ContainerActivity extends Activity {private PluginActivity pluginActivity; @ Override protected void onCreate (Bundle savedInstanceState) {String pluginActivityName = getIntent () .getString ("pluginActivityName", ""); pluginActivity = PluginLoader.loadActivity (pluginActivityName, this); if (pluginActivity = = null) {super.onCreate (savedInstanceState); return;} pluginActivity.onCreate () } @ Override protected void onResume () {if (pluginActivity = = null) {super.onResume (); return;} pluginActivity.onResume ();} @ Override protected void onPause () {if (pluginActivity = = null) {super.onPause (); return;} pluginActivity.onPause () } / /...} public class PluginActivity {private ContainerActivity containerActivity; public PluginActivity (ContainerActivity containerActivity) {this.containerActivity = containerActivity;} @ Override public T findViewById (int id) {return containerActivity.findViewById (id);} /...} / / the component actually written in the plug-in `Apk` is public class TestActivity extends PluginActivity {/ /.}

Do you feel a little bit understood? although there are still many pits when you really do it, the principle is about that simple: starting the plug-in component depends on the container, which is responsible for loading the plug-in component and two-way forwarding, forwarding the life cycle from the system back to the plug-in component, and forwarding the system calls from the plug-in component to the system.

4.3 byte code replacement

Although this method can well achieve the purpose of starting the plug-in Activity, but because the development is very intrusive, the Activity in the plug-in must inherit PluginActivity. If you want to transform the previous module into a plug-in, you need a lot of extra work.

Class TestActivity extends Activity {}-> class TestActivity extends PluginActivity {}

Is there any way to make the writing of plug-in components the same as before?

Shadow's approach is bytecode replacement plug-in, which is a great idea, to put it simply, Android provides some Gradle plug-in development kits, of which there is a feature called Transform Api, which can intervene in the project construction process, after bytecode generation, before dex file generation, some changes to the code, specific how to do not say, you can see the documentation.

The function implemented is that after the user has configured the Gradle plug-in, the plug-in is developed normally and still written:

Class TestActivity extends Activity {}

Then after the compilation is completed, the final bytecode is shown as follows:

Class TestActivity extends PluginActivity {}

This is where the basic framework is almost over.

5. Resource Injection

The last thing I want to talk about is resource injection, which is actually very important. The development of Android applications actually advocates the concept of separation of logic and resources. All resources (layout, values, etc.) will be packaged into Apk, and then a corresponding R class will be generated, which contains references to all resources id.

It is not easy to inject resources. Fortunately, the Android system has left us a way back. The most important thing is these two interfaces:

PackageManager#getPackageArchiveInfo: parse the PackageInfo of an uninstalled Apk according to the Apk path

PackageManager#getResourcesForApplication: create a Resources instance based on ApplicationInfo

All we need to do is create a plug-in resource instance using these two methods when loading the plug-in Apk in the above ContainerActivity#onCreate. Specifically, we first use PackageManager#getPackageArchiveInfo to get the PackageInfo of the plug-in Apk. With PacakgeInfo, we can assemble an ApplicationInfo ourselves, and then create a resource instance through PackageManager#getResourcesForApplication. The code looks like this:

PackageManager packageManager = getPackageManager (); PackageInfo packageArchiveInfo = packageManager.getPackageArchiveInfo (pluginApkPath, PackageManager.GET_ACTIVITIES | PackageManager.GET_META_DATA | PackageManager.GET_SERVICES | PackageManager.GET_PROVIDERS | PackageManager.GET_SIGNATURES); packageArchiveInfo.applicationInfo.sourceDir = pluginApkPath;packageArchiveInfo.applicationInfo.publicSourceDir = pluginApkPath;Resources injectResources = null;try {injectResources = packageManager.getResourcesForApplication (packageArchiveInfo.applicationInfo);} catch (PackageManager.NameNotFoundException e) {/ /.}

After getting the resource instance, we need to Merge the host resources and plug-in resources to write a new Resources class to complete the automatic proxy in this way:

Public class PluginResources extends Resources {private Resources hostResources; private Resources injectResources; public PluginResources (Resources hostResources, Resources injectResources) {super (injectResources.getAssets (), injectResources.getDisplayMetrics (), injectResources.getConfiguration ()); this.hostResources = hostResources; this.injectResources = injectResources;} @ Override public String getString (int id, Object... FormatArgs) throws NotFoundException {try {return injectResources.getString (id, formatArgs);} catch (NotFoundException e) {return hostResources.getString (id, formatArgs);}} / /.}

Then, after ContainerActivity finishes loading the plug-in component, we create a Merge resource, copy the ContainerActivity#getResources, and replace the acquired resource:

Public class ContainerActivity extends Activity {private Resources pluginResources; @ Override protected void onCreate (Bundle savedInstanceState) {/ /... PluginResources = new PluginResources (super.getResources (), PluginLoader.getResources (pluginApkPath)); / /...} @ Override public Resources getResources () {if (pluginActivity = = null) {return super.getResources ();} return pluginResources;}}

This completes the injection of resources.

The above is what Android plug-in is like. Have you learned any knowledge or skills? If you want to learn more skills or enrich your knowledge reserve, you are welcome to follow the industry information channel.

Welcome to subscribe "Shulou Technology Information " to get latest news, interesting things and hot topics in the IT industry, and controls the hottest and latest Internet news, technology news and IT industry trends.

Views: 0

*The comments in the above article only represent the author's personal views and do not represent the views and positions of this website. If you have more insights, please feel free to contribute and share.

Share To

Development

Wechat

© 2024 shulou.com SLNews company. All rights reserved.

12
Report