1. Create a new AssetManager object 2. Call the addAssetPath method through reflection 3. Create a Resources object with the AssetsManager object as a parameter.
code show as below:
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 Dec 11, 2015 7:58:59 PM mochuan.zhb * Exp $ * @Description Manager for dynamically loaded resources */ 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; } /** * Get the 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: We use the Resources object. When obtaining resources, the ID passed 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, the id refers to the R file in the Bundle apk System.out.println("debug:"+str); Toast.makeText(getApplicationContext(),strById, Toast.LENGTH_SHORT).show(); Drawable drawable = resources.getDrawable(0x7f020000);//Note, the id refers to the R file in the Bundle apk imageView.setImageDrawable(drawable); }
The above code is to load the string and Drawable resources in the offline apk, so what about the layout resources?
Problem introduction
We use the LayoutInflate object, which is generally used as follows:
View view = LayoutInflater.from(context).inflate(R.layout.main_fragment, null);
Among them, R.layout.main_fragment we can get its ID through the above method, then the key step is how to generate a context? It is not possible to pass in the current context directly.
There are 2 solutions:
- 1. Create your own ContextImpl, Override its methods.
- 2. Through reflection, directly replace the mResources private member variable of the current context. <>br
Of course, we use the second scheme:
@Override protected void attachBaseContext(Context context) { replaceContextResources(context); super.attachBaseContext(context); } /** * Using reflection, use the Resource object of the Bundle to 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 (); } }
We replace the mResources of the Context in the attachBaseContext method of the Activity, so that we can load the layout in the offline apk.
If you want to plug-in the packaging process
of resource files, you need to understand the packaging process of Android resource files, so that you can number each plug-in, and then generate R files according to the rules. For example, taking Ctrip DynamicAPK as an example, it follows the following rules for the R file of the plug-in: 1. The R
file is int type, the first 8 digits represent the Id of the plug-in, of which two special Ids: Host is 0x7f, which comes with the android system It starts with 0x01. 2. The next 8 bits are to distinguish the resource type, such as layout, id, string, dimen, etc. 3. The next 16 bits are the number of the resource. Generate the corresponding plug-in apk
according to the above rules. Then at runtime, we can write a ResourceManager class, which inherits from the Resource object, and then all activities modify the mResource member variable of their context to the ResourceManager class, and then Override its methods, and then when loading resources, according to different id prefix, just look up the Resource of the corresponding plugin. That is, use a class for distribution.