目錄
Android黑科技動態(tài)加載(一)之Java中的ClassLoader
Android黑科技動態(tài)加載(二)之Android中的ClassLoader
Android黑科技動態(tài)加載(三)之動態(tài)加載資源
Android黑科技動態(tài)加載(四)之插件化開發(fā)
我們的認(rèn)識
我們都知道, 在Android中我們獲取一個資源只需要使用
Context.getResource().getXXXX()
就可以獲取到對應(yīng)的資源文件. 那么如果我們想要加載其他應(yīng)用的res內(nèi)容
, 那么就應(yīng)該構(gòu)造出他們環(huán)境的Resource
. 有了Resource還不行, 我們還需要獲取資源文件的ID
, 其中ID我們可以通過R.java
文件通過反射獲取.
所以我們的目標(biāo)就是(分別對于已安裝的應(yīng)用和未安裝的應(yīng)用):
- 構(gòu)造出Resource
- 獲取資源的ID
ResourceBundle就是我們的資源包, 其中只有兩張圖片
已經(jīng)安裝的應(yīng)用
獲取Resource
對于已經(jīng)安裝的應(yīng)用, 獲取
Resource
的方法很簡單, 只要獲取到Context
就可以獲取對應(yīng)環(huán)境下的Resource
了, 其中有一個方法Context.createPackageContext(String packageName, int flags)
可以根據(jù)包名
獲取已經(jīng)安裝應(yīng)用的Context
.
首先我們建一個Bean來存儲已經(jīng)加載的資源
public class LoadedResource {
public Resources resources;
public String packageName;
public ClassLoader classLoader;
}
然后我們就可以寫加載的方法
/**
* 獲取已安裝應(yīng)用資源
*
* @param packageName
*/
public LoadedResource getInstalledResource(String packageName) {
LoadedResource resource = mResources.get(packageName); // 先從緩存中取, 沒有就去加載
if (resource == null) {
try {
Context context = mContext.createPackageContext(packageName,
Context.CONTEXT_INCLUDE_CODE | Context.CONTEXT_IGNORE_SECURITY);
resource = new LoadedResource();
resource.packageName = packageName;
resource.resources = context.getResources();
resource.classLoader = context.getClassLoader();
mResources.put(packageName, resource); // 得到結(jié)果緩存起來
} catch (Exception e) {
e.printStackTrace();
}
}
return resource;
}
至此, 我們就能獲取到了
Resource
獲取資源ID
根據(jù)上面的思路, 我們使用反射區(qū)獲取. 大概看一下
R文件的結(jié)構(gòu)
package com.example.resourcebundle;
public final class R {
public static final class attr {
}
public static final class drawable {
public static final int image=0x7f020000;
public static final int image1=0x7f020001;
}
public static final class mipmap {
public static final int ic_launcher=0x7f030000;
}
public static final class string {
public static final int app_name=0x7f040000;
}
...
}
對應(yīng)的資源類型都有一個靜態(tài)內(nèi)部類, 那么我們就可以使用反射無獲取對應(yīng)的數(shù)值
/**
* 獲取資源ID
*
* @param packageName 包名
* @param type 對應(yīng)的資源類型, drawable mipmap等
* @param fieldName
* @return
*/
public int getResourceID(String packageName, String type, String fieldName) {
int resID = 0;
LoadedResource installedResource = getInstalledResource(packageName); // 獲取已安裝APK的資源
if (installedResource != null) {
String rClassName = packageName + ".R$" + type; // 根據(jù)匿名內(nèi)部類的命名, 拼寫出R文件的包名+類名
try {
Class cls = installedResource.classLoader.loadClass(rClassName); // 加載R文件
resID = (Integer) cls.getField(fieldName).get(null); // 反射獲取R文件對應(yīng)資源名的ID
} catch (Exception e) {
e.printStackTrace();
}
} else {
Log.w(TAG, "resource is null:" + packageName);
}
return resID;
}
現(xiàn)在我們加載已經(jīng)安裝APK的資源的編碼就已經(jīng)完成.
調(diào)用
getDrawable("com.example.resourcebundle", "image1")
未安裝的應(yīng)用
我們先看一下getDrawable
方法是怎么去獲取資源的
public Drawable getDrawable(@DrawableRes int id, @Nullable Theme theme)
throws NotFoundException {
final TypedValue value = obtainTempTypedValue();
try {
final ResourcesImpl impl = mResourcesImpl;
impl.getValue(id, value, true);
return impl.loadDrawable(this, value, id, theme, true);
} finally {
releaseTempTypedValue(value);
}
}
上面代碼我們可以看到, 資源其實是通過impl代理去拿到的, 繼續(xù)...
void getValue(@AnyRes int id, TypedValue outValue, boolean resolveRefs)
throws NotFoundException {
boolean found = mAssets.getResourceValue(id, 0, outValue, resolveRefs);
if (found) {
return;
}
throw new NotFoundException("Resource ID #0x" + Integer.toHexString(id));
}
然后再通過assets去代理獲取, 繼續(xù)看看assets從哪里設(shè)置的
public ResourcesImpl(@NonNull AssetManager assets, @Nullable DisplayMetrics metrics,
@Nullable Configuration config, @NonNull DisplayAdjustments displayAdjustments) {
mAssets = assets;
mMetrics.setToDefaults();
mDisplayAdjustments = displayAdjustments;
updateConfiguration(config, metrics, displayAdjustments.getCompatibilityInfo());
mAssets.ensureStringBlocks();
}
再尋找ResourcesImpl的構(gòu)造函數(shù)從哪里調(diào)用
public Resources(AssetManager assets, DisplayMetrics metrics, Configuration config) {
this(null);
mResourcesImpl = new ResourcesImpl(assets, metrics, config, new DisplayAdjustments());
}
最后, 我們回到Resource的構(gòu)造函數(shù)中, 也就是說正確調(diào)用Resources的構(gòu)造函數(shù), 那么我們就能構(gòu)造出正確的Resource
但是, 如何得到一個AssetManager呢? 大家請參考文章Android應(yīng)用程序資源管理器(Asset Manager)的創(chuàng)建過程分析
, 里面說明了AssetManager的加載原理和過程. 我們按部就班反射調(diào)用public final int addAssetPath(String path)
方法去添加資源文件路徑.
那么我們現(xiàn)在就可以編碼了
/**
* 加載未安裝應(yīng)用資源包
*
* @param resourcePath
* @return
*/
public LoadedResource loadResource(String resourcePath) {
LoadedResource loadResource = null;
PackageInfo info = queryPackageInfo(resourcePath); // 獲取未安裝APK的PackageInfo
if (info != null) { // 獲取成功
loadResource = mRescources.get(info.packageName); // 先從緩存中取, 存在則直接返回, 不重復(fù)添加. 否則就搜索添加
if (loadResource == null) {
try {
AssetManager assetManager = AssetManager.class.newInstance(); // 創(chuàng)建AssetManager實例
Class cls = AssetManager.class;
Method method = cls.getMethod("addAssetPath", String.class);
method.invoke(assetManager, resourcePath); // 反射設(shè)置資源加載路徑
Resources resources = new Resources(assetManager, mContext.getResources().getDisplayMetrics(),
mContext.getResources().getConfiguration()); // 構(gòu)造出正確的Resource
loadResource = new LoadedResource();
loadResource.resources = resources;
loadResource.packageName = info.packageName;
loadResource.classLoader = new DexClassLoader(resourcePath, mDexDir, null,
mContext.getClassLoader()); // 設(shè)置正確的類加載器, 因為需要去加載R文件
mRescources.put(info.packageName, loadResource); // 緩存
Log.w(TAG, "build resource:" + resourcePath);
} catch (Exception e) {
e.printStackTrace();
}
}
}
Log.w(TAG, "load resource:" + resourcePath);
return loadResource;
}
/**
* 獲取未安裝應(yīng)用PackageInfo
*
* @param resourcePath
* @return
*/
private PackageInfo queryPackageInfo(String resourcePath) {
return mContext.getPackageManager().getPackageArchiveInfo(resourcePath, PackageManager.GET_ACTIVITIES);
}
既然我們現(xiàn)在已經(jīng)獲取了Resource, 那么下面獲取資源文件就與上面是一樣的
LoadedResource loadResource = loadResource("/storage/sdcard0/bundle.apk");
Drawable drawable = getDrawable(loadResource.packageName, "image");
最后代碼
LoadedResource.java
package com.example.host.res;import android.content.res.Resources;public class LoadedResource { public Resources resources; public String packageName; public ClassLoader classLoader;}
ResourceManager.java
package com.example.host.res;
import java.io.File;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
import android.content.Context;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.res.AssetManager;
import android.content.res.Resources;
import android.graphics.drawable.Drawable;
import android.util.Log;
import dalvik.system.DexClassLoader;
public class ResourceManager {
private static final String TAG = "ResourceManager";
private ResourceManager() {
}
public static void init(Context context) {
UnInstalled.sManager.init(context);
Installed.sManager.init(context);
}
public static UnInstalled unInstalled() {
return UnInstalled.sManager;
}
public static Installed installed() {
return Installed.sManager;
}
/**
* 針對于未安裝應(yīng)用
*/
public static class UnInstalled {
static final UnInstalled sManager = new UnInstalled();
private Context mContext;
private Map<String, LoadedResource> mRescources = new HashMap<String, LoadedResource>();
private String mDexDir;
private UnInstalled() {
}
/**
* 初始化
*
* @param context
*/
public void init(Context context) {
mContext = context.getApplicationContext();
File dexDir = mContext.getDir("dex", Context.MODE_PRIVATE);
if (!dexDir.exists()) {
dexDir.mkdir();
}
mDexDir = dexDir.getAbsolutePath();
}
/**
* 獲取未安裝應(yīng)用資源的ID
*
* @param packageName
* @param fieldName
* @return
*/
public int getResourceID(String packageName, String type, String fieldName) {
int resID = 0;
LoadedResource recource = getUnInstalledRecource(packageName);
String rClassName = packageName + ".R$" + type;
Log.w(TAG, "resource class:" + rClassName + ",fieldName:" + fieldName);
try {
Class cls = recource.classLoader.loadClass(rClassName);
resID = (Integer) cls.getField(fieldName).get(null);
} catch (Exception e) {
e.printStackTrace();
}
return resID;
}
/**
* 獲取未安裝應(yīng)用Drawable
*
* @param packageName
* @param fieldName
* @return
*/
public Drawable getDrawable(String packageName, String fieldName) {
Drawable drawable = null;
int resourceID = getResourceID(packageName, "drawable", fieldName);
LoadedResource recource = getUnInstalledRecource(packageName);
if (recource != null) {
drawable = recource.resources.getDrawable(resourceID);
}
return drawable;
}
/**
* 加載未安裝應(yīng)用資源包
*
* @param resourcePath
* @return
*/
public LoadedResource loadResource(String resourcePath) {
LoadedResource loadResource = null;
PackageInfo info = queryPackageInfo(resourcePath); // 獲取未安裝APK的PackageInfo
if (info != null) { // 獲取成功
loadResource = mRescources.get(info.packageName); // 先從緩存中取, 存在則直接返回, 不重復(fù)添加. 否則就搜索添加
if (loadResource == null) {
try {
AssetManager assetManager = AssetManager.class.newInstance(); // 創(chuàng)建AssetManager實例
Class cls = AssetManager.class;
Method method = cls.getMethod("addAssetPath", String.class);
method.invoke(assetManager, resourcePath); // 反射設(shè)置資源加載路徑
Resources resources = new Resources(assetManager, mContext.getResources().getDisplayMetrics(),
mContext.getResources().getConfiguration()); // 構(gòu)造出正確的Resource
loadResource = new LoadedResource();
loadResource.resources = resources;
loadResource.packageName = info.packageName;
loadResource.classLoader = new DexClassLoader(resourcePath, mDexDir, null,
mContext.getClassLoader()); // 設(shè)置正確的類加載器, 因為需要去加載R文件
mRescources.put(info.packageName, loadResource); // 緩存
Log.w(TAG, "build resource:" + resourcePath);
} catch (Exception e) {
e.printStackTrace();
}
}
}
Log.w(TAG, "load resource:" + resourcePath);
return loadResource;
}
/**
* 獲取未安裝應(yīng)用PackageInfo
*
* @param resourcePath
* @return
*/
private PackageInfo queryPackageInfo(String resourcePath) {
return mContext.getPackageManager().getPackageArchiveInfo(resourcePath, PackageManager.GET_ACTIVITIES);
}
/**
* 獲取未安裝應(yīng)用LoadResource
*
* @param packageName
* @return
*/
public LoadedResource getUnInstalledRecource(String packageName) {
LoadedResource loadResource = mRescources.get(packageName);
if (loadResource == null) {
Log.w(TAG, "resource " + packageName + " not founded");
}
return loadResource;
}
}
/**
* 針對于已安裝應(yīng)用
*/
public static class Installed {
static final Installed sManager = new Installed();
private Context mContext;
private Map<String, LoadedResource> mResources = new HashMap<String, LoadedResource>();
private Installed() {
}
/**
* 初始化
*
* @param context
*/
public void init(Context context) {
mContext = context.getApplicationContext();
}
/**
* 獲取已安裝應(yīng)用資源
*
* @param packageName
*/
public LoadedResource getInstalledResource(String packageName) {
LoadedResource resource = mResources.get(packageName); // 先從緩存中取, 沒有就去加載
if (resource == null) {
try {
Context context = mContext.createPackageContext(packageName,
Context.CONTEXT_INCLUDE_CODE | Context.CONTEXT_IGNORE_SECURITY);
resource = new LoadedResource();
resource.packageName = packageName;
resource.resources = context.getResources();
resource.classLoader = context.getClassLoader();
mResources.put(packageName, resource); // 得到結(jié)果緩存起來
} catch (Exception e) {
e.printStackTrace();
}
}
return resource;
}
/**
* 獲取資源ID
*
* @param packageName
* @param type
* @param fieldName
* @return
*/
public int getResourceID(String packageName, String type, String fieldName) {
int resID = 0;
LoadedResource installedResource = getInstalledResource(packageName); // 獲取已安裝APK的資源
if (installedResource != null) {
String rClassName = packageName + ".R$" + type; // 根據(jù)匿名內(nèi)部類的命名, 拼寫出R文件的包名+類名
try {
Class cls = installedResource.classLoader.loadClass(rClassName); // 加載R文件
resID = (Integer) cls.getField(fieldName).get(null); // 反射獲取R文件對應(yīng)資源名的ID
} catch (Exception e) {
e.printStackTrace();
}
} else {
Log.w(TAG, "resource is null:" + packageName);
}
return resID;
}
/**
* 獲取已加載應(yīng)用Drawable
*
* @param packageName
* @param fieldName
* @return
*/
public Drawable getDrawable(String packageName, String fieldName) {
Drawable drawable = null;
int resourceID = getResourceID(packageName, "drawable", fieldName);
LoadedResource installedResource = getInstalledResource(packageName);
if (installedResource != null) {
drawable = installedResource.resources.getDrawable(resourceID);
}
return drawable;
}
}
}
activity_main.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:onClick="loadInstalledBundle"
android:text="加載已安裝APK資源" />
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:onClick="loadUninstalledBundle"
android:text="加載未安裝APK資源" />
<ImageView
android:id="@+id/imageView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="centerInside" />
</LinearLayout>
MainActivity.java
package com.example.host;
import android.app.Activity;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.view.View;
import android.widget.ImageView;
import com.example.host.act.ActivityManager;
import com.example.host.res.LoadedResource;
import com.example.host.res.ResourceManager;
public class MainActivity extends Activity {
ImageView imageView;
ActivityManager mPluginManager = ActivityManager.getInstance();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ResourceManager.init(this);
mPluginManager.init(this);
imageView = (ImageView) findViewById(R.id.imageView);
}
/**
* 加載已安裝APK資源
*
* @param v
*/
public void loadInstalledBundle(View v) {
Drawable drawable = ResourceManager.installed().getDrawable("com.example.resourcebundle", "image1");
if (drawable != null) {
imageView.setImageDrawable(drawable);
}
}
/**
* 加載未安裝APK資源
*
* @param v
*/
public void loadUninstalledBundle(View v) {
LoadedResource loadResource = ResourceManager.unInstalled().loadResource("/storage/sdcard0/bundle.apk");
Drawable drawable = ResourceManager.unInstalled().getDrawable(loadResource.packageName, "image");
if (drawable != null) {
imageView.setImageDrawable(drawable);
}
}
}
測試加載安裝APK資源就安裝ResourceBundle應(yīng)用, 測試加載未安裝APK資源就把ResourceBundle應(yīng)用放到指定位置
其中ResourceBundle只要包含image和image1的兩個drawable就可以了