Android dynamic resource Loading Principles and Applications

Source: Internet
Author: User

Android dynamic resource Loading Principles and Applications
Dynamic Resource loading principle

Generally, we call the getResources () method to obtain the resource file.

public Resources getResources() {    return mResources;}
MResources is created in the init method after the contexibd object is created.
mResources = mPackageInfo.getResources(mainThread);
Call the getResources method of LoadedApk
public Resources getResources(ActivityThread mainThread) {    if (mResources == null) {        mResources = mainThread.getTopLevelResources(mResDir,                Display.DEFAULT_DISPLAY, null, this);    }    return mResources;}
The getTopLevelResources method of the ActivityThread class is also called.
Resources getTopLevelResources(String resDir, int displayId, Configuration overrideConfiguration, CompatibilityInfo compInfo) {    ResourcesKey key = new ResourcesKey(resDir, displayId, overrideConfiguration, compInfo.applicationScale, compInfo.isThemeable);    Resources r;    synchronized (mPackages) {        // ...        WeakReference
 
   wr = mActiveResources.get(key);        r = wr != null ? wr.get() : null;        if (r != null && r.getAssets().isUpToDate()) {            if (false) {                Slog.w(TAG, "Returning cached resources " + r + " " + resDir                        + ": appScale=" + r.getCompatibilityInfo().applicationScale);            }            return r;        }    }        AssetManager assets = new AssetManager();    assets.setThemeSupport(compInfo.isThemeable);    if (assets.addAssetPath(resDir) == 0) {        return null;    }    // ...    r = new Resources(assets, dm, config, compInfo);    if (false) {        Slog.i(TAG, "Created app resources " + resDir + " " + r + ": "                + r.getConfiguration() + " appScale="                + r.getCompatibilityInfo().applicationScale);    }    synchronized (mPackages) {        WeakReference
  
    wr = mActiveResources.get(key);        Resources existing = wr != null ? wr.get() : null;        if (existing != null && existing.getAssets().isUpToDate()) {            // Someone else already created the resources while we were            // unlocked; go ahead and use theirs.            r.getAssets().close();            return existing;        }                // XXX need to remove entries when weak references go away        mActiveResources.put(key, new WeakReference
   
    (r));        return r;    }}
   
  
 
ResourcesKey is constructed using resDir and other parameters. The resDir parameter indicates the path of the resource file. That is, the path of the APK program.
ResourcesKey key = new ResourcesKey(resDir, displayId, overrideConfiguration, compInfo.applicationScale, compInfo.isThemeable);
The main logic of the code above is to get the Resources object, which is obtained from a Map variable mActiveResources. This Map maintains the ResourcesKey and WeakReference . If it does not exist, create it and add it to Map.

Therefore, as long as the Map contains multiple Resources objects pointing to different resource paths or Resources objects pointing to Resources of different paths, you can access Resources of multiple paths, you can access resources in other APK files.

The main logic for creating Resources objects is

AssetManager assets = new AssetManager();assets.setThemeSupport(compInfo.isThemeable);    if (assets.addAssetPath(resDir) == 0) {        return null;} r = new Resources(assets, dm, config, compInfo);
First create an AssetManager object and then use it to create a Resources object. We used the getAssets method to read files in the assets folder. In fact, it was created here.

Constructor of AssetManager:

public AssetManager() {    synchronized (this) {        if (DEBUG_REFS) {            mNumRefs = 0;            incRefsLocked(this.hashCode());        }        init();        if (localLOGV) Log.v(TAG, "New asset manager: " + this);        ensureSystemAssets();    }}
The init () function is also a native function, and its native code is in android_util_AssetManager.cpp.
Static void android_content_AssetManager_init (JNIEnv * env, jobject clazz) {AssetManager * am = new AssetManager (); if (am = NULL) {jniThrowException (env, "java/lang/OutOfMemoryError", ""); return;} // Add the Framework resource file to the AssertManager object path. Am-> addDefaultAssets (); ALOGV ("Created AssetManager % p for Java object % p \ n", am, clazz); env-> SetIntField (clazz, gAssetManagerOffsets. mObject, (jint) am);} bool AssetManager: addDefaultAssets () {// system const char * root = getenv ("ANDROID_ROOT"); LOG_ALWAYS_FATAL_IF (root = NULL, "ANDROID_ROOT not set"); String8 path (root); // kSystemAssets are defined as static const char * kSystemAssets = "framework/framework-res.apk"; // Therefore, path is/system/framework/framework-res.apk, the resource file path for the framework. appendPath (kSystemAssets); return addAssetPath (path, NULL );}
So far, the framework resource has been added when the AssetManager is created, and the resource path of the application has been added, that is, the addAssetPath method is called.
/** * Add an additional set of assets to the asset manager.  This can be * either a directory or ZIP file.  Not for use by applications.  Returns * the cookie of the added asset, or 0 on failure. * {@hide} */public native final int addAssetPath(String path);
It is also a native method, and its native code is in android_util_AssetManager.cpp
Static jint enumerate (JNIEnv * env, jobject clazz, jstring path) {ScopedUtfChars path8 (env, path); if (path8.c _ str () = NULL) {return 0 ;} assetManager * am = assetManagerForJavaObject (env, clazz); if (am = NULL) {return 0;} void * cookie; // Add the resource path in native code. bool res = am-> addAssetPath (String8 (path8.c _ str (), & cookie); return (res )? (Jint) cookie: 0 ;}
We can see that the internal AssetManager object of the Resources object contains the framework Resources and the Resources of the application, therefore, this is why the resources object obtained by the getResources function can be used to access system resources and application resources.

 

As a reminder in this process, can we create a resource object by ourselves so that it can access other Resources by including the Resources in the specified path? The answer is yes. This idea can be used to achieve dynamic resource loading, skin replacement, theme replacement, and other functions.

Therefore, the main idea is to create an AssetManager object, use the addAssetPath function to add a specified path, use it to create a resource object, and use this resource object to obtain Resources under this path.

Note that the addAssetPath function is hide and can be called Using Reflection.

Public void loadRes (String path) {try {assetManager = AssetManager. class. newInstance (); Method addAssetPath = AssetManager. class. getMethod ("addAssetPath", String. class); addAssetPath. invoke (assetManager, path);} catch (Exception e) {} resources = new Resources (assetManager, super. getResources (). getDisplayMetrics (), super. getResources (). getConfiguration (); // You can also obtain a topic based on the resource}
Here, the parameter path is the path of the APK file, which can be obtained in the following way:
getPackageManager().getApplicationInfo("xxx", 0).sourceDir;
You can also override the getResources method and getAsset method of Context to improve code consistency.
@Overridepublic Resources getResources() {return resources == null ? super.getResources() : resources;}@Overridepublic AssetManager getAssets() {return assetManager == null ? super.getAssets() : assetManager;}
After the resource is loaded, you can use the Resources object to obtain the Resources in the corresponding path. Dynamic Resource Loading

Two buttons of different styles are provided by the app by default, and another plug-in APK program is stored in other paths of the mobile phone, different image resources are loaded when different styles are selected.

The plug-in APK only contains some resource files.

The code of the Host Program is as follows:

 

Private AssetManager assetManager; private Resources resources; private RadioGroup rg; private ImageView iv; @ Overrideprotected void onCreate (Bundle savedInstanceState) {super. onCreate (savedInstanceState); setContentView (R. layout. activity_main); iv = (ImageView) findViewById (R. id. iv); rg = (RadioGroup) findViewById (R. id. rg); rg. setOnCheckedChangeListener (new OnCheckedChangeListener () {@ Overridepublic void OnCheckedChanged (RadioGroup group, int checkedId) {switch (checkedId) {case R. id. default_skin: assetManager = null; resources = null; iv. setImageDrawable (getResources (). getDrawable (R. drawable. ic_launcher); break; case R. id. skin1: String dexPath = ""; try {dexPath = getPackageManager (). getApplicationInfo ("com. example. plugin ", 0 ). sourceDir;} catch (NameNotFoundException e) {e. printStackTrace ();} loadRes (DexPath); // because the getResources method is overwritten, the returned Resources object is maintained by ourselves. Therefore, you can access the resource iv with its id. setImageDrawable (getResources (). getDrawable (0x7f020000); break ;}});} public void loadRes (String path) {try {assetManager = AssetManager. class. newInstance (); Method addAssetPath = AssetManager. class. getMethod ("addAssetPath", String. class); addAssetPath. invoke (assetManager, path);} catch (Exception e) {} resources = new Resour Ces (assetManager, super. getResources (). getDisplayMetrics (), super. getResources (). getConfiguration () ;}@ Overridepublic Resources getResources () {return resources = null? Super. getResources (): resources;} @ Overridepublic AssetManager getAssets () {return assetManager = null? Super. getAssets (): assetManager ;}
It can be found that the id of the ic_launcher image in the plug-in APK is 0x7f020000, so the corresponding resource can be obtained through this id value.
public static final int ic_launcher=0x7f020000;
Of course, this coupling is too high and can be used to describe the principle, but it does not seem very intuitive, because this id is only known when the code of the plug-in APK is viewed, therefore, the plug-in APK can provide a function to return this id, which is called by the host APK. You can use reflection or interface.

The plug-in APK provides the getImageId function to obtain the image resource id.

public class Plugin {public static int getImageId() {return R.drawable.ic_launcher;}}
After the resource is loaded, you can call the following method to obtain the image resource:
private void setImage(String dexPath) {DexClassLoader loader = new DexClassLoader(dexPath, getApplicationInfo().dataDir, null, this.getClass().getClassLoader());try {Class
   clazz = loader.loadClass("com.example.plugin.Plugin");Method getImageId = clazz.getMethod("getImageId");int ic_launcher = (int) getImageId.invoke(clazz);iv.setImageDrawable(getResources().getDrawable(ic_launcher));} catch (Exception e) {e.printStackTrace();}}
Plug-in management

For each plug-in, declare an empty Activity in AndroidManifest. xml and add its action, for example:

<activity android:name=".plugin">            <intent-filter>                <action android:name="android.intent.plugin">            </action></intent-filter>        </activity>
In this way, the corresponding plug-in can be found in the Host Program for selection and loading.
PackageManager pm = getPackageManager();List
  
    resolveinfos = pm.queryIntentActivities(intent, 0);ActivityInfo activityInfo = resolveinfos.get(i).activityInfo;dexPaths.add(activityInfo.applicationInfo.sourceDir);
  
Effect:

Code of the Host Program

 

Private AssetManager assetManager; private Resources resources; private LinearLayout ll; private ImageView iv; private Button btn; private List
  
   
DexPaths = new ArrayList
   
    
(); @ Overrideprotected void onCreate (Bundle savedInstanceState) {super. onCreate (savedInstanceState); setContentView (R. layout. activity_main); iv = (ImageView) findViewById (R. id. iv); ll = (LinearLayout) findViewById (R. id. ll); btn = (Button) findViewById (R. id. btn); btn. setOnClickListener (new OnClickListener () {@ Overridepublic void onClick (View v) {resources = null; iv. setImageDrawable (getResources (). getDrawable (R. drawable. ic_launcher) ;}}); Intent intent = new Intent ("android. intent. plugin "); PackageManager pm = getPackageManager (); final List
    
     
Resolveinfos = pm. queryIntentActivities (intent, 0); for (int I = 0; I <resolveinfos. size (); I ++) {final ActivityInfo activityInfo = resolveinfos. get (I ). activityInfo; dexPaths. add (activityInfo. applicationInfo. sourceDir); // Add the Button final Button btn = new Button (this) based on the number of queried plug-ins; btn. setText ("style" + (I + 1); btn. setTag (I); ll. addView (btn, new LinearLayout. layoutParams (LayoutParams. WRAP_CONTENT, LayoutParams. WRAP_CONTENT); btn. setOnClickListener (new OnClickListener () {@ Overridepublic void onClick (View v) {int index = (Integer) btn. getTag (); String dexPath = dexPaths. get (index); loadRes (dexPath); setImage (resolveinfos. get (index ). activityInfo) ;}}) ;}} private void setImage (ActivityInfo activityInfo) {DexClassLoader loader = new DexClassLoader (activityInfo. applicationInfo. sourceDir, getApplicationInfo (). dataDir, null, this. getClass (). getClassLoader (); try {Class
     Clazz = loader. loadClass (activityInfo. packageName + ". plugin "); Method getImageId = clazz. getMethod ("getImageId"); int ic_launcher = (int) getImageId. invoke (clazz); iv. setImageDrawable (getResources (). getDrawable (ic_launcher);} catch (Exception e) {e. printStackTrace () ;}} public void loadRes (String path) {try {assetManager = AssetManager. class. newInstance (); Method addAssetPath = AssetManager. class. getMe Thod ("addAssetPath", String. class); addAssetPath. invoke (assetManager, path);} catch (Exception e) {e. printStackTrace ();} resources = new Resources (assetManager, super. getResources (). getDisplayMetrics (), super. getResources (). getConfiguration () ;}@ Overridepublic Resources getResources () {return resources = null? Super. getResources (): resources;} @ Overridepublic AssetManager getAssets () {return assetManager = null? Super. getAssets (): assetManager ;}
    
   
  
Two plug-ins:

 

Com. example. plugin

| -- Plugin. java

Com. example. plugin2

| -- Plugin. java

The content of the Plugin class is the same as that of the class that is provided to the Host Program for reflection calls.

Register an empty activity

<activity android:name=".plugin" android:label="@string/name">      <intent-filter>            <action android:name="android.intent.plugin">      </action></intent-filter></activity>

Related Article

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.