网友通过本文主要向大家介绍了android 动态添加控件,android 动态权限,android 动态加载布局,android 动态添加布局,android 动态注册广播等相关知识,希望对您有所帮助,也希望大家支持linkedu.com www.linkedu.com
Android动态资源加载原理和应用
动态加载资源原理
通常我们调用getResources()方法获取资源文件
public Resources getResources() { return mResources; }mResources是在创建ContextImp对象后的init方法里面创建的
mResources = mPackageInfo.getResources(mainThread);调用了LoadedApk的getResources方法
public Resources getResources(ActivityThread mainThread) { if (mResources == null) { mResources = mainThread.getTopLevelResources(mResDir, Display.DEFAULT_DISPLAY, null, this); } return mResources; }又调用到了ActivityThread类的getTopLevelResources方法
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) { // ... WeakReferenceResourcesKey使用resDir和其他参数来构造,这里主要是resDir参数,表明资源文件所在的路径。也就是APK程序所在路径。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 key = new ResourcesKey(resDir, displayId, overrideConfiguration, compInfo.applicationScale, compInfo.isThemeable);上面代码的主要逻辑是获取Resources对象,从一个Map变量mActiveResources获取,这个Map维护了ResourcesKey和WeakReference
因此只要这个Map中包含多个指向不同资源路径的Resources对象或者说我们有指向不同路径的资源的Resources对象,就可以访问多个路径的资源,即有实现访问其他APK文件中的资源的可能。
创建Resources对象的主要逻辑为
AssetManager assets = new AssetManager(); assets.setThemeSupport(compInfo.isThemeable); if (assets.addAssetPath(resDir) == 0) { return null; } r = new Resources(assets, dm, config, compInfo);首先创建AssetManager对象,然后用其创建Resources对象。我们以前使用getAssets方法读取assets文件夹中的文件,其实他就是在这里创建的。
AssetManager的构造函数:
public AssetManager() { synchronized (this) { if (DEBUG_REFS) { mNumRefs = 0; incRefsLocked(this.hashCode()); } init(); if (localLOGV) Log.v(TAG, "New asset manager: " + this); ensureSystemAssets(); } }init()函数也是一个native函数,其native代码在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; } // 将Framework的资源文件添加到AssertManager对象的路径中。 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定义为static const char* kSystemAssets = "framework/framework-res.apk"; // 因此,path为/system/framework/framework-res.apk,framework对应的资源文件 path.appendPath(kSystemAssets); return addAssetPath(path, NULL); }到此为止,在创建AssetManager的时候完成了添加framework资源,然后添加本应用的资源路径,即调用addAssetPath方法
/** * 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);也是一个native方法,其native代码在android_util_AssetManager.cpp中
static jint android_content_AssetManager_addAssetPath(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; // 在native代码中完成添加资源路径的工作 bool res = am->addAssetPath(String8(path8.c_str()), &cookie); return (res) ? (jint)cookie : 0; }可以看到,Resources对象的内部AssetManager对象包含了framework的资源还包含了应用程序本身的资源,因此这也就是为什么能使用getResources函数获得的resources对象来访问系统资源和本应用资源的原因。
受此过程的提醒,我们是不是可以自己创建一个Resources对象,让它的包含我们指定路径的资源,就可以实现访问其他的资源了呢?答案是肯定的,利用这个思想可以实现资源的动态加载,换肤、换主题等功能都可以利用这种方法实现。
于是,主要思想就是创建一个AssetManager对象,利用addAssetPath函数添加指定的路径,用其创建一个Resources对象,使用该Resources对象获取该路径下的资源。
需要注意的是addAssetPath函数是hide的,可以使用反射调用。
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()); // 也可以根据资源获取主题 }这里的参数path就是APK文件的路径,可以通过以下方式获取
getPackageManager().getApplicationInfo("xxx", 0).sourceDir;并且还可以重写Context的getResources方法,getAsset方法,提高代码的一致性。
@Override public Resources getResources() { return resources == null ? super.getResources() : resources; } @Override public AssetManager getAssets() { return assetManager == null ? super.getAssets() : assetManager; }于是在加载了资源之后就可以通过该Resources对象获取对应路径下面的资源了。
动态加载资源
两种不同风格的按钮,默认的是本应用提供的资源,还有一种作为另一个单独的插件APK程序存放在手机的其他路径中,当选择不同的风格时加载不同的图片资源。
插件APK仅仅包含了一些资源文件。
宿主程序的代码具体如下
privat