Android plug-in (3) Loading Resource Resources in the plug-in apk
How can I load resource files not installed in apk? From android. content. res. assetManager. java source code shows that it has a private method addAssetPath. You only need to pass in the apk path as a parameter to obtain the corresponding AssetsManager object, and then we can use the AssetsManager object, create a Resources object, and then you can access Resources in the apk from the Resource object. Summary:
1. Create an AssetManager object. 2. Call the addAssetPath method through reflection. 3. Use the AssetsManager object as the parameter and create the Resources object.
The Code is as follows:
Package net. mobctrl. hostapk; import java. io. file; import android. content. context; import android. content. res. assetManager; import android. content. res. resources;/*** @ Author Zheng Haibo * @ PersonalWebsite http://www.mobctrl.net * @ version $ Id: LoaderResManager. java, v 0.1 December 11, 2015 7:58:59 mochuan. zhb * Exp $ * @ Description: Dynamically Loaded Resource Manager */public class BundlerResourceLoader {private static AssetManager createAssetManager (String apkPath) {try {AssetManager assetManager = AssetManager. class. newInstance (); 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 Resources in the Bundle * @ param context * @ param apkPath * @ return */public static Resources getBundleResource (Context context) {AssetsManager. copyAllAssetsApk (context); File dir = context. getDir (AssetsManager. APK_DIR, Context. MODE_PRIVATE); String apkPath = dir. getAbsolutePath () + "/BundleApk.apk"; System. out. println ("debug: apkPath =" + apkPath + ", exists =" + (new File (apkPath ). exists (); AssetManager assetManager = createAssetManager (apkPath); return new Resources (assetManager, context. getResources (). getDisplayMetrics (), context. getResources (). getConfiguration ());}}
DEMO
Note: When we use the Resources object to obtain Resources, the passed ID must be the ID of the resource corresponding to the R file in the offline apk. If you use the getIdentifier method, the first parameter is the resource name, the second parameter is the resource type, and the third parameter is the package name of the offline apk. Remember the third parameter.
Resources resources = BundlerResourceLoader. getBundleResource (getApplicationContext (); imageView = (ImageView) findViewById (R. id. image_view_iv); if (resources! = Null) {String str = resources. getString (resources. getIdentifier ("test_str", "string", "net.mobctrl.normal.apk"); String strById = resources. getString (0x7f050001); // note that id refers to the R File System in Bundle apk. out. println ("debug:" + str); Toast. makeText (getApplicationContext (), strById, Toast. LENGTH_SHORT ). show (); Drawable drawable = resources. getDrawable (0x7f020000); // note that id refers to the R file imageView in Bundle apk. setImageDrawable (drawable );}
The above code loads the strings and Drawable Resources in the offline apk. What about the layout resource?
Problem Introduction
We use the LayoutInflate object as follows:
View view = LayoutInflater.from(context).inflate(R.layout.main_fragment, null);
Here, we can obtain the ID of R. layout. main_fragment through the above method. The key step is how to generate a context? It is not feasible to directly pass in the current context.
There are two solutions:
-1. Create a ContextImpl and Override its method.
-2. Use reflection to directly replace the mResources private member variable of the current context. <> Br
Of course, we use the second solution:
@ Override protected void attachBaseContext (Context context) {replaceContextResources (context); super. attachBaseContext (context);}/*** use the reflection method, use the Bundle Resource object, replace the mResources object of the Context * @ param context */public void replaceContextResources (Context context) {try {Field field = context. getClass (). getDeclaredField ("mResources"); field. setAccessible (true); field. set (context, mBundleResources); System. out. println ("debug: repalceResources succ");} catch (Exception e) {System. out. println ("debug: repalceResources error"); e. printStackTrace ();}}
In the attachBaseContext method of the Activity, we replace the mResources of the Context, so that we can load the layout in the offline apk.
Package resource files
If you want to implement plug-ins, you need to understand the packaging process of the Android resource file. In this way, you can number each plug-in and generate the R file according to the rules. For example, if Ctrip DynamicAPK is used as an example, it will follow the following rules for the R file of the plug-in:
1. the R file is of the int type, and the first eight digits represent the plug-in Id. Two Special IDs: 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. the last 16 digits are resource numbers.
Generate the corresponding plug-in apk according to the above rules. Then, at runtime, we can write a ResourceManager class that inherits from the Resource object. Then, all the activities change the mResource member variable of the context to the ResourceManager class, then Override its method, and then find the Resource of the corresponding plug-in based on the prefix of different IDs when loading the Resource. That is to say, a class is used for distribution.