Android Plugin-based foundation (4), dynamic startup of the Activity in the plugin
Android plugin BASICS (4): ActivityAuthor in dynamic startup plug-in: Introduction to Zheng Haibo-mochuan
How to dynamically start the Activity in the plug-in? First, we will analyze what preparations are required to start the Activity in the plug-in?
1. loading Activity classes in the plug-in
That is, the ClassLoader problem. We can know from the MultiDex In the first lesson that we can dynamically load the apk and then load the class in the plug-in into the current ClassLoader. In this way, the class in the plug-in and the class in the host belong to the same ClassLoader, and the mutual call between them will be solved. 2. Registration of activities in the plug-in AndroidManifest. xml
Because the plug-in apk has its own AndroidManifest file, to dynamically start the Activity in the plug-in at runtime, You need to register the Activity of the plug-in apk to the AndroidManifest file of the host apk during packaging. 3. Resource loading of Activity in the plug-in
It is one of the most difficult plug-ins to handle resource loading issues! We need to consider many issues:
1. How to obtain the Resources object in real time when the plug-in Activity is running, and find the Resources in Resources based on the id of the R file corresponding to the plug-in package name.
2. How does one resolve the Resource Name Conflict between the resource file in the plug-in apk and Other plug-ins and hosts?
3. How can the resources of the host and various plug-ins be managed in a unified and convenient manner? Solution: 1. Loading of Classes
We use the previously modified AssetsMultiDexLoader to load the apk under the assets Directory. Since the previous blog has explained the problem, I will not repeat it again.
2. The Activity in the plug-in is registered in the host AndroidManifest.
This issue needs to be handled during packaging. That is to say, we need to transform our packaging tool and merge the AndroidManifest files of each plug-in into the AndroidManifest file of the host during packaging.
3. Loading of plug-in resources
We need to handle the compilation and running processes separately:
3.1 compilation process
Let's first review the Android packaging process:
1. Generate the R. java File
For example:
aapt package -f -m -J ./gen -S res -M AndroidManifest.xml -I D:\android_sdk_for_studio\platforms\android-22\android.jar
2. Clear the bin directory
Clear the last generated file
3. Compile the java file and jar package
javac -encoding GBK -target 1.5 -bootclasspath D:\android_sdk_for_studio\platforms\android-22\android.jar -d bin src\net\mobctrl\normal\apk\*.java gen\net\mobctrl\normal\apk\R.java -classpath libs\*.jar
4. Use the dx tool to package it into classes. dex
dx --dex --output=C:\Users\mochuan.zhb\newworkspace\BundleApk5\bin\classes.dex C:\Users\mochuan.zhb\newworkspace\BundleApk5\bin\
5. Compile the resource source file
aapt package -f -M AndroidManifest.xml -S res -I D:\android_sdk_for_studio\platforms\android-22\android.jar -F bin\resources.ap_ --non-constant-id
6. Use sdklib. jar to generate unsigned apk
java -cp D:\android_sdk_for_studio\tools\lib\sdklib.jar com.android.sdklib.build.ApkBuilderMain bin\MyCommond.apk -v -u -z bin\resources.ap_ -f bin\classes.dex -rf C:\Users\mochuan.zhb\newworkspace\BundleApk5\src
7. Use jarsigner to sign the apk
Jarsigner-verbose-keystore C: \ test. keystore-storepass 123456-keypass 123456-signedjar C: \ projectdemo-signed.apk C: \ test.apk test
3.2 modification to the compilation process
To solve the resource conflicts between plug-ins and between hosts, We need to number the plug-ins and modify the R file generation process. We know that the ID in the R file is an int type, with a total of 32 bits. So what are the meanings of these 32 bits?
1. The first eight digits represent the packageId of the plug-in. Two Special IDs are: Host is 0x7f, and android comes with a prefix of 0x01.
2. The following 8 bits differentiate resource types, such as layout, id, string, and dimen.
3. the last 16 digits are resource numbers.
To resolve resource naming conflicts, the following two methods are generally used:
1. Conventions
During development, the team defines the resource naming conventions. For example, each business line is named according to certain rules, so that everyone can abide by the rules to avoid duplication.
Then, during packaging, we merge the resources of each plug-in to generate the R file in a unified manner. The content of all plug-ins and the R file of the host is identical, resources are stored in the resources of the host project.
Note: This is the Android packaging process used by Google. For example, the main project depends on lib_project1 and lib_project2. when compiling the main project, it copies the resources of each lib project to the main project, and then uses aapt to generate a unified R file, multiple R files with different package names are generated, but the content of the R file is exactly the same.
2. Modify aapt
To avoid duplicate resource names, we can modify the source code of aapt so that it can generate different IDS Based on Different packageids. That is to say, the id in the R file is composed of the following 32 characters:
[PackageId (8)] [resourceType (8)] [resourcesSeq (16)]
We allocate a packageId for each plug-in the range of (1,127 ).
After modifying the source code of aapt, we need to modify the first and fifth steps in the packaging process. When generating R files and compiling resources, we need to use the transformed aapt. In this way, the R file of the final generated apk is allocated according to our method.
We can decompile the R file in javastest.apk, as shown in. The packageId we set is 5:
Decompiled test.apk]
The id after decompilation must be converted to hexadecimal display.
Note:
Decompilation steps:
1. decompress the apk File
2. Use the d2j-dex2jar tool to convert dex to jar
3. decompile jar using Java-Decompiler
3.3 load resources during running 1. Load the plug-in apk to the current AssetsManager
Core code:
/*** Modify AssetManager ** @ param assetManager * @ param apkPaths * @ return */private static AssetManager modifyAssetManager (AssetManager assetManager, List
ApkPaths) {if (apkPaths = null | apkPaths. size () = 0) {return null;} try {for (String apkPath: apkPaths) {try {AssetManager. class. getDeclaredMethod ("addAssetPath", String. class ). invoke (assetManager, apkPath);} catch (Throwable th) {System. out. println ("debug: createAssetManager:" + th. getMessage (); th. printStackTrace () ;}} return assetManager;} catch (Throwable th) {System. out. println ("debug: createAssetManager:" + th. getMessage (); th. printStackTrace ();} return null ;} /*** obtain the Resources in the resource manager of the entire App ** @ param context * @ param apkPath * @ return */public static Resources getAppResource (Context context) {System. out. println ("debug: getAppResource... "); AssetsManager. copyAllAssetsApk (context); // obtain the dex File list File dexDir = context. getDir (AssetsManager. APK_DIR, Context. MODE_PRIVATE); File [] szFiles = dexDir. listFiles (new FilenameFilter () {@ Override public boolean accept (File dir, String filename) {return filename. endsWith (AssetsManager. FILE_FILTER) ;}}); if (szFiles = null | szFiles. length = 0) {return context. getResources ();} System. out. println ("debug: getAppResource szFiles =" + szFiles. length); List
ApkPaths = new ArrayList
(); For (File f: szFiles) {Log. I (TAG, "load file:" + f. getName (); apkPaths. add (f. getAbsolutePath (); System. out. println ("debug: apkPath =" + f. getAbsolutePath ();} AssetManager assetManager = modifyAssetManager (context. getAssets (), apkPaths); AppResource resources = new AppResource (assetManager, context. getResources (). getDisplayMetrics (), context. getResources (). getConfiguration (); return resources ;}
2. Loading Resources in Application
Core code
Public class HostApplication extends Application {private Resources mAppResources = null; private Resources mOldResources = null; @ Override public void onCreate () {super. onCreate (); mOldResources = super. getResources (); AssetsMultiDexLoader. install (this); // load the apk installResource () ;}@ Override public Resources getResources () {if (mAppResources = null) {return mOldResources;} return this. mAppResources;} private void installResource () {if (mAppResources = null) {mAppResources = BundlerResourceLoader. getAppResource (this); // load the resource object in assets }}@ Override public AssetManager getAssets () {if (this. mAppResources = null) {return super. getAssets ();} return this. mAppResources. getAssets ();}}
3. Replace the Resource object with the plug-in Activity
Take BundleActivity as an example:
public class BundleActivity extends BaseActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.bundle_layout); findViewById(R.id.text_view).setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { Toast.makeText(getApplicationContext(), "Hello", Toast.LENGTH_LONG).show(); } }); } @Override public Resources getResources() { return getApplication().getResources(); }}
The most important thing is to override the getResources () method and replace the current Resource method with the Application Resource.