android 动态添加资源,Android插件化——动态资源加载
8种机械键盘轴体对比本人程序员,要买一个写代码的键盘,请问红轴和茶轴怎么选?顾名思义我们之前的热更新只是解决了代码的加载,对于外部资源我们还不能直接使用。我们工程的资源最终打包后都会放到R.java文档中,然后我们可以获取Resources然后通过类似resources.getDrawable(id);方式通过id来获取我们具体的资源。但对于外部资源也就是不在我们工程中没有被放到R.java文档.
8种机械键盘轴体对比
本人程序员,要买一个写代码的键盘,请问红轴和茶轴怎么选?
顾名思义我们之前的热更新只是解决了代码的加载,对于外部资源我们还不能直接使用。我们工程的资源最终打包后都会放到R.java文档中,然后我们可以获取Resources然后通过类似resources.getDrawable(id);方式通过id来获取我们具体的资源。但对于外部资源也就是不在我们工程中没有被放到R.java文档中的资源我们如果使用呢?1Drawable drawable = resources.getDrawable(resId);
上面这种方式是我们平时获取资源的使用方式。
我们使用Resources的getXXX 通过resId来获取资源。
那我们考虑考虑是否可以获取插件的Resources呢?如果可以我们是不是就可以实现资源的动态加载了呢?
答案是可以的!
我们先来看看Activity是如何获取Resources的:
我们直接定位到代码,ContextThemeWrapper
继承关系,如
ContextThemeWrapper > ContextWrapper > Context1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16public Resources getResources(){
return getResourcesInternal();
}
private Resources getResourcesInternal(){
if (mResources == null) {
if (mOverrideConfiguration == null) {
mResources = super.getResources();
} else {
final Context resContext = createConfigurationContext(mOverrideConfiguration);
mResources = resContext.getResources();
}
}
return mResources;
}
我们发现获取Resources的方法,再来看看Resources.java的构造函数1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18* AssetManager.
*
* @deprecated Resources should not be constructed by apps.
* See {@link android.content.Context#createConfigurationContext(Configuration)}.
*
* @param assets Previously created AssetManager.
* @param metrics Current display metrics to consider when
* selecting/computing resource values.
* @param config Desired device configuration to consider when
* selecting/computing resource values (optional).
*/
@Deprecated
public Resources(AssetManager assets, DisplayMetrics metrics, Configuration config){
this(null);
mResourcesImpl = new ResourcesImpl(assets, metrics, config, new DisplayAdjustments());
}
我们发现Resources是由AssetManager创建处理的。
再来分析一下AssetManger1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19/**
* Provides access to an application's raw asset files; see {@link Resources}
* for the way most applications will want to retrieve their resource data.
* This class presents a lower-level API that allows you to open and read raw
* files that have been bundled with the application as a simple stream of
* bytes.
*/
public final class AssetManager implements AutoCloseable{
...
/**
* @deprecated Use {@link #setApkAssets(ApkAssets[], boolean)}
* @hide
*/
@Deprecated
public int addAssetPath(String path){
return addAssetPathInternal(path, false /*overlay*/, false /*appAsLib*/);
}
...
}
我们发现一个addAssetPath的方法,实际这个方法可以使我们传入的apk path为我们构建出AssetManager 然后通过AssetManager创建出Resources。如上是我们实现动态加载资源的初步思路。
下面实践一下是否可行?1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104package com.baweigame.plugindemoapplication;
import android.app.Application;
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.os.Environment;
import android.util.Log;
import java.io.File;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
public class MyApplication extends Application{
private AssetManager assetManager;
private Resources newResource;
private Resources.Theme mTheme;
private PackageInfo packageInfo;
@Override
protected void attachBaseContext(Context base){
super.attachBaseContext(base);
//创建我们自己的Resource
String apkPath = Environment.getExternalStorageDirectory().getAbsolutePath() + "/app-debug.apk";
packageInfo = getPackageInfo(apkPath);
if (new File(apkPath).exists()){
loadOtherResource(apkPath);
}
}
@Override
public void onCreate(){
super.onCreate();
}
public void loadOtherResource(String apkPath){
//创建AssetManager
try {
assetManager = AssetManager.class.newInstance();
Method addAssetPathMethod = assetManager.getClass().getDeclaredMethod("addAssetPath", String.class);
addAssetPathMethod.setAccessible(true);
addAssetPathMethod.invoke(assetManager, apkPath);
//初始化AssetManager内部参数
Method ensureStringBlocks = AssetManager.class.getDeclaredMethod("ensureStringBlocks");
ensureStringBlocks.setAccessible(true);
ensureStringBlocks.invoke(assetManager);
Resources supResource = getResources();
Log.e("Main", "supResource = " + supResource);
newResource = new Resources(assetManager, supResource.getDisplayMetrics(), supResource.getConfiguration());
mTheme = newResource.newTheme();
mTheme.setTo(super.getTheme());
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
@Override
public AssetManager getAssets(){
return assetManager==null?super.getAssets():assetManager;
}
@Override
public Resources getResources(){
return newResource==null?super.getResources():newResource;
}
@Override
public Resources.Theme getTheme(){
return mTheme == null ? super.getTheme() : mTheme;
}
/**
* 获取apk包信息
* @param resourcePath apk路径
* @return
*/
private PackageInfo getPackageInfo(String resourcePath){
PackageInfo packageInfo=getPackageManager().getPackageArchiveInfo(resourcePath, PackageManager.GET_ACTIVITIES);
return packageInfo;
}
/**
* 获取插件包名
* @return
*/
public String getPluginPackageName(){
return packageInfo.packageName;
}
}
上面代码中,我们发现获取了内置存储中的app-debug.apk文档,因为是demo所以直接写死了。
下面我们来看看loadOtherResource这个方法都做了什么?
我们发现它主要利用反射来获取
assetManager
newResource
mTheme
3个主要对象,并将我们的apk通过“addAssetPath”添加到了manager中。
然后重写了Application的3个方法getAssets、getResources、getTheme,用于替换成我们资源apk的资源。
然后我们看看MainActivity中做了什么?1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49package com.baweigame.plugindemoapplication;
import android.app.Activity;
import android.content.res.AssetManager;
import android.content.res.Resources;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
public class MainActivity extends Activity{
@Override
protected void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
MyApplication application= (MyApplication) getApplication();
int aa=application.getResources().getIdentifier("activity_demo","layout",application.getPluginPackageName());
try{
setContentView(aa);
}
catch (Exception e){
Log.e("123", "onCreate: ");
}
}
@Override
public AssetManager getAssets(){
if(getApplication() != null && getApplication().getAssets() != null){
return getApplication().getAssets();
}
return super.getAssets();
}
@Override
public Resources.Theme getTheme(){
if(getApplication() != null && getApplication().getTheme() != null){
return getApplication().getTheme();
}
return super.getTheme();
}
@Override
public Resources getResources(){
if(getApplication() != null && getApplication().getTheme() != null){
return getApplication().getResources();
}
return super.getResources();
}
}
我们发现实现了从资源apk中获取activity_demo布局文档然后设置给MainActivity。
注意我们要使用其他apk资源必须重写Activity中的getAssets、getTheme、getResources。1application.getResources().getIdentifier("activity_demo","layout",application.getPluginPackageName());
这个方法用于获取指定资源,目前获取的是layout类型的activity_demo布局文档资源。
下面是我们加载的资源项目截图:
如上就是我们动态加载资源的简单实现。
更多推荐
所有评论(0)