Android plug-in has many open source projects in China, but if you don’t actually develop it, it will be difficult to master it well.
The following is to start from 0, combine the current open source projects and blogs to develop plug-in solutions.
Plug-in as needed mainly solves the following problems:
1. Loading of code
(1) To solve the loading of pure Java code
(2) Android component loading, such as Activity, Service, Broadcast Receiver, ContentProvider, because they have life cycles, they need to be handled specially.
(3) Loading of Android Native code
(4) Processing of Android special controls, such as Notification, etc.
2. Resource loading
How to manage resources of different plug-ins, is it a public set or a plug-in independently managed?
Because accessing resources in Android is achieved through R.
Let's solve the above problems step by step
1. Loading of pure Java code
The main thing is to add the plugin path to the original array through ClassLoader and change DexElements.
For detailed analysis, please refer to an article I reproduced. Because I feel that the name and structure of the original post are a bit messy, I can record the reprint.
/android520/blog/794715
Android provides DexClassLoader and PathClassLoader, both of which inherit BaseDexClassLoader, but the parameters of the constructor method are different, that is, the path of optdex is different, the source code is as follows
// public class DexClassLoader extends BaseDexClassLoader { public DexClassLoader(String dexPath, String optimizedDirectory, String libraryPath, ClassLoader parent) { super(dexPath, new File(optimizedDirectory), libraryPath, parent); } } // 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); } }
Among them, optimizedDirectory is used to store the dex directory after opt, and must be an internal storage path.
DexClassLoader can load external dex or apk, as long as the path of opt is set with an internal storage path through parameters.
PathClassLoader can only load installed apks, because the opt path will use the default dex path, and external ones cannot.
The following describes how to load Java code through DexClassLoader, refer to Nuwa
This method is similar to hot fixes. If the plug-in and host code have mutual access, it needs to be implemented using instrumentation technology in the packaging.
public static boolean injectDexAtFirst(String dexPath, String dexOptPath) { // Get the system's dexElements Object baseDexElements = getDexElements(getPathList(getPathClassLoader())); // Get the dexElements of patch DexClassLoader patchDexClassLoader = new DexClassLoader(dexPath, dexOptPath, dexPath, getPathClassLoader()); Object patchDexElements = getDexElements(getPathList(patchDexClassLoader)); // Combining the latest dexElements Object allDexElements = combineArray(patchDexElements, baseDexElements); // Add the latest dexElements to the system's classLoader Object pathList = getPathList(getPathClassLoader()); (pathList, "dexElements", allDexElements); } public static ClassLoader getPathClassLoader() { return (); } /** * Reflection calls getPathList method to get data * @param classLoader * @return * @throws ClassNotFoundException * @throws NoSuchFieldException * @throws IllegalAccessException */ public static Object getPathList(ClassLoader classLoader) throws ClassNotFoundException, NoSuchFieldException, IllegalAccessException { return (classLoader, "pathList"); } /** * Reflect the dexElements data of the pathList object calling * @param pathList * @return * @throws NoSuchFieldException * @throws IllegalAccessException */ public static Object getDexElements(Object pathList) throws NoSuchFieldException, IllegalAccessException { ("Reflect To Get DexElements"); return (pathList, "dexElements"); } /** * Splice dexElements and insert the patch's dex into the head of the original dex * @param firstElement * @param secondElement * @return */ public static Object combineArray(Object firstElement, Object secondElement) { ("Combine DexElements"); // Get an array Class object. If the object is an array, getClass can only return the array type, and getComponentType can return the actual type of the array Class objTypeClass = ().getComponentType(); int firstArrayLen = (firstElement); int secondArrayLen = (secondElement); int allArrayLen = firstArrayLen + secondArrayLen; Object allObject = (objTypeClass, allArrayLen); for (int i = 0; i < allArrayLen; i++) { if (i < firstArrayLen) { (allObject, i, (firstElement, i)); } else { (allObject, i, (secondElement, i - firstArrayLen)); } } return allObject; }
Activity started using the above method has a life cycle. It should use the system default to create the Activity method, rather than the new Activity object yourself, so the life cycle of the Activity opened is normal.
However, in the above method, the activity must be registered in the host.
2. The following describes how to load unregistered Activity functions
Reference for Activity's loading principle/android520/blog/795599
Mainly done through the IActivityManager of the Hook system
3. Resource loading
Resource access is all through R. method. In fact, Android will generate an int constant value in 0x7f****** format to associate the corresponding resources.
If the resource changes, such as layout, id, drawable, etc., the content will be regenerated and the int constant value will also change.
Because the resources in the plug-in do not participate in the resource compilation of the host program, they cannot be accessed through R.
For specific principles, please refer to:https:///article/
After adding the plug-in path to the host program using addAssetPath, since the plug-in is packaged independently, the resource id also starts from 1, and the host program also starts from 1, which may cause conflicts between the plug-in and the host resource. When the system loads the resource, the latest resource found shall prevail. Therefore, it is impossible to guarantee whether the interface displays the host or plug-in.
In this way, you can change the scope of resource id generation of each plug-in when packaging, so you can refer to the introduction.
Code reference Amigo
public static void loadPatchResources(Context context, String apkPath) throws Exception { AssetManager newAssetManager = (); invokeMethod(newAssetManager, "addAssetPath", apkPath); invokeMethod(newAssetManager, "ensureStringBlocks"); replaceAssetManager(context, newAssetManager); } private static void replaceAssetManager(Context context, AssetManager newAssetManager) throws Exception { Collection<WeakReference<Resources>> references; if (.SDK_INT >= Build.VERSION_CODES.KITKAT) { Class<?> resourcesManagerClass = (""); Object resourcesManager = invokeStaticMethod(resourcesManagerClass, "getInstance"); if (getField(resourcesManagerClass, "mActiveResources") != null) { ArrayMap<?, WeakReference<Resources>> arrayMap = (ArrayMap) readField(resourcesManager, "mActiveResources", true); references = (); } else { references = (Collection) readField(resourcesManager, "mResourceReferences", true); } } else { HashMap<?, WeakReference<Resources>> map = (HashMap) readField((), "mActiveResources", true); references = (); } AssetManager assetManager = context != null ? () : null; for (WeakReference<Resources> wr : references) { Resources resources = (); if (resources == null) continue; try { writeField(resources, "mAssets", newAssetManager); originalAssetManager = assetManager; } catch (Throwable ignore) { Object resourceImpl = readField(resources, "mResourcesImpl", true); writeField(resourceImpl, "mAssets", newAssetManager); } ((), ()); } if (.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { for (WeakReference<Resources> wr : references) { Resources resources = (); if (resources == null) continue; // $SynchronizedPool<TypedArray> Object typedArrayPool = readField(resources, "mTypedArrayPool", true); // Clear all the pools while (invokeMethod(typedArrayPool, "acquire") != null) ; } } }
The above is all the content of this article. I hope it will be helpful to everyone's study and I hope everyone will support me more.