Android plug-in loading Resource resources in plug-in apk

How to load resource files in apk that is not installed? We found from the source code of android.content.res.AssetManager.java that it has a private method addAssetPath, only need to pass in the path of the apk as a parameter, we can get the corresponding AssetsManager object, and then we can use the AssetsManager object , create a Resources object, and then you can access the resources in the apk from the Resource object. The summary is as follows:
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.

Guess you like

Origin http://10.200.1.11:23101/article/api/json?id=327007201&siteId=291194637