Android 插件化开发(三):资源插件化
<p>在前面的文章中我们成功的加载了外部的Dex(Apk)并执行了插件的Bean代码。这时我们会想,能不能加载并运行插件Apk的Activity。答案当然是能,否则后续我们的研究就没意义了,但是想实现Activity的插件化运行,我们必须要解决一个问题——<strong>如何使用插件中的资源</strong>。</p><p>本文我们就讲一下插件的资源加载机制,并讲述一下如何实现资源的插件化。</p>
<h2>一、资源的加载机制</h2>
<p>Android的资源文件分为两类:</p>
<p>第一类是res目录下存放的可编辑的资源文件,这类文件在编译时系统会自动在R文件中生成资源文件的16进制值。</p>
<p>例如:</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">final</span> <span style="color: rgba(0, 0, 255, 1)">class</span><span style="color: rgba(0, 0, 0, 1)"> R {
</span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">static</span> <span style="color: rgba(0, 0, 255, 1)">final</span> <span style="color: rgba(0, 0, 255, 1)">class</span><span style="color: rgba(0, 0, 0, 1)"> anim {
</span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">static</span> <span style="color: rgba(0, 0, 255, 1)">final</span> <span style="color: rgba(0, 0, 255, 1)">int</span> abc_fade_in=0x7f050000<span style="color: rgba(0, 0, 0, 1)">;
</span><span style="color: rgba(0, 0, 255, 1)"> public</span> <span style="color: rgba(0, 0, 255, 1)">static</span> <span style="color: rgba(0, 0, 255, 1)">final</span> <span style="color: rgba(0, 0, 255, 1)">int</span> abc_fade_in=0x7f050000<span style="color: rgba(0, 0, 0, 1)">;
...
}
}</span></pre>
</div>
<p>我们在平时的开发时,访问这类资源比较简单,使用Context的getResource方法即可得到res下的各种资源。如下面代码所示:</p>
<div class="cnblogs_code">
<pre>String content = mContext.getResource().getString(R.string.content);</pre>
</div>
<p>第二类是assets目录下存放的原始资源文件。apk在编译时不会编译assets下的文件,我们不能使用R.的方式访问,只能使用AssetsManager类的open方法来获取assets目录下的文件资源。</p>
<p>而AssetsManager又源于Resources类的getAssets方法,如下面代码所示:</p>
<div class="cnblogs_code">
<pre>Resource resource =<span style="color: rgba(0, 0, 0, 1)"> getResource();
AssetsManager am </span>=<span style="color: rgba(0, 0, 0, 1)"> getResource().getAssets();
InputStream is</span>= getResource().getAssets().open("filename");</pre>
</div>
<p>通过上面的分析,我们可以初步做出一个结论:我们能使用Resources类是一个很重要的类,通过此类提供的相关API,我们能操作资源的加载。</p>
<h2>二、资源插件化的解决方案</h2>
<p>谈及资源插件化,我们不得不对AssetsManager的API多说一些。</p>
<p>AssetsManager中有一个addAssetsPath(String Path)方法,App启动的时候就会将当前的apk路径传进去,接下来AssetsManager和Resources就能访问当前apk的所有资源了。</p>
<p>AssetsManager的addAssetsPath方法不对外,但是我们可以通过反射的方式,把插件apk的路径传到这个方法,这样就把插件的资源添加到一个资源池中了。App有几个插件,我们就调用几次addAssetsPath方法,把插件的资源都塞到池子里。</p>
<p>这里我们以加载插件Apk里面的字符串资源为目标,实战一下资源插件化:</p>
<p>首先我们在插件app的string.xml里面定义字符串资源:</p>
<div class="cnblogs_code">
<pre> <span style="color: rgba(0, 0, 255, 1)"><</span><span style="color: rgba(128, 0, 0, 1)">string </span><span style="color: rgba(255, 0, 0, 1)">name</span><span style="color: rgba(0, 0, 255, 1)">="myplugin1_hello_world"</span><span style="color: rgba(0, 0, 255, 1)">></span>Hello Plugin<span style="color: rgba(0, 0, 255, 1)"></</span><span style="color: rgba(128, 0, 0, 1)">string</span><span style="color: rgba(0, 0, 255, 1)">></span></pre>
</div>
<p>然后我们在宿主app编写如下代码:</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">class</span> MainActivity <span style="color: rgba(0, 0, 255, 1)">extends</span><span style="color: rgba(0, 0, 0, 1)"> Activity {
</span><span style="color: rgba(0, 0, 255, 1)">private</span><span style="color: rgba(0, 0, 0, 1)"> AssetManager mAssetManager;
</span><span style="color: rgba(0, 0, 255, 1)">private</span><span style="color: rgba(0, 0, 0, 1)"> Resources mResources;
</span><span style="color: rgba(0, 0, 255, 1)">private</span><span style="color: rgba(0, 0, 0, 1)"> Resources.Theme mTheme;
</span><span style="color: rgba(0, 0, 255, 1)">private</span> String dexPath = <span style="color: rgba(0, 0, 255, 1)">null</span>; <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">apk文件地址</span>
<span style="color: rgba(0, 0, 255, 1)">private</span> File fileRelease = <span style="color: rgba(0, 0, 255, 1)">null</span>;<span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">释放目录</span>
<span style="color: rgba(0, 0, 255, 1)">protected</span> DexClassLoader classLoader = <span style="color: rgba(0, 0, 255, 1)">null</span><span style="color: rgba(0, 0, 0, 1)">;
</span><span style="color: rgba(0, 0, 255, 1)">private</span> String pluginName = "plugin1.apk"<span style="color: rgba(0, 0, 0, 1)">;
TextView mTextView;
@Override
</span><span style="color: rgba(0, 0, 255, 1)">protected</span> <span style="color: rgba(0, 0, 255, 1)">void</span><span style="color: rgba(0, 0, 0, 1)"> attachBaseContext(Context newBase) {
</span><span style="color: rgba(0, 0, 255, 1)">super</span><span style="color: rgba(0, 0, 0, 1)">.attachBaseContext(newBase);
extractAssets(newBase, pluginName);
}
@SuppressLint(</span>"NewApi"<span style="color: rgba(0, 0, 0, 1)">)
@Override
</span><span style="color: rgba(0, 0, 255, 1)">protected</span> <span style="color: rgba(0, 0, 255, 1)">void</span><span style="color: rgba(0, 0, 0, 1)"> onCreate(Bundle savedInstanceState) {
</span><span style="color: rgba(0, 0, 255, 1)">super</span><span style="color: rgba(0, 0, 0, 1)">.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
File extractFile </span>= <span style="color: rgba(0, 0, 255, 1)">this</span><span style="color: rgba(0, 0, 0, 1)">.getFileStreamPath(pluginName);
dexPath </span>=<span style="color: rgba(0, 0, 0, 1)"> extractFile.getPath();
fileRelease </span>= getDir("dex", 0<span style="color: rgba(0, 0, 0, 1)">);
classLoader </span>= <span style="color: rgba(0, 0, 255, 1)">new</span> DexClassLoader(dexPath, fileRelease.getAbsolutePath(), <span style="color: rgba(0, 0, 255, 1)">null</span><span style="color: rgba(0, 0, 0, 1)">, getClassLoader());
mTextView </span>=<span style="color: rgba(0, 0, 0, 1)"> findViewById(R.id.tv);
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">带资源文件的调用</span>
findViewById(R.id.btn_6).setOnClickListener(<span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> View.OnClickListener() {
@Override
</span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">void</span><span style="color: rgba(0, 0, 0, 1)"> onClick(View arg0) {
loadResources();
</span><span style="color: rgba(0, 0, 255, 1)">try</span><span style="color: rgba(0, 0, 0, 1)"> {
Class mLoadClassDynamic </span>= classLoader.loadClass("com.plugin1.Dynamic"<span style="color: rgba(0, 0, 0, 1)">);
Object dynamicObject </span>=<span style="color: rgba(0, 0, 0, 1)"> mLoadClassDynamic.newInstance();
IDynamic dynamic </span>=<span style="color: rgba(0, 0, 0, 1)"> (IDynamic) dynamicObject;
String content </span>= dynamic.getStringForResId(MainActivity.<span style="color: rgba(0, 0, 255, 1)">this</span><span style="color: rgba(0, 0, 0, 1)">);
mTextView.setText(content);
Toast.makeText(getApplicationContext(), content </span>+ ""<span style="color: rgba(0, 0, 0, 1)">, Toast.LENGTH_LONG).show();
} </span><span style="color: rgba(0, 0, 255, 1)">catch</span><span style="color: rgba(0, 0, 0, 1)"> (Exception e) {
e.printStackTrace();
}
}
});
}
</span><span style="color: rgba(0, 0, 255, 1)">protected</span> <span style="color: rgba(0, 0, 255, 1)">void</span><span style="color: rgba(0, 0, 0, 1)"> loadResources() {
</span><span style="color: rgba(0, 0, 255, 1)">try</span><span style="color: rgba(0, 0, 0, 1)"> {
AssetManager assetManager </span>= AssetManager.<span style="color: rgba(0, 0, 255, 1)">class</span><span style="color: rgba(0, 0, 0, 1)">.newInstance();
Method addAssetPath </span>= assetManager.getClass().getMethod("addAssetPath", String.<span style="color: rgba(0, 0, 255, 1)">class</span><span style="color: rgba(0, 0, 0, 1)">);
addAssetPath.invoke(assetManager, dexPath);
mAssetManager </span>=<span style="color: rgba(0, 0, 0, 1)"> assetManager;
} </span><span style="color: rgba(0, 0, 255, 1)">catch</span><span style="color: rgba(0, 0, 0, 1)"> (Exception e) {
e.printStackTrace();
}
Resources superRes </span>= <span style="color: rgba(0, 0, 255, 1)">super</span><span style="color: rgba(0, 0, 0, 1)">.getResources();
superRes.getDisplayMetrics();
superRes.getConfiguration();
mResources </span>= <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> Resources(mAssetManager, superRes.getDisplayMetrics(), superRes.getConfiguration());
mTheme </span>=<span style="color: rgba(0, 0, 0, 1)"> mResources.newTheme();
mTheme.setTo(</span><span style="color: rgba(0, 0, 255, 1)">super</span><span style="color: rgba(0, 0, 0, 1)">.getTheme());
}
@Override
</span><span style="color: rgba(0, 0, 255, 1)">public</span><span style="color: rgba(0, 0, 0, 1)"> AssetManager getAssets() {
</span><span style="color: rgba(0, 0, 255, 1)">return</span> mAssetManager == <span style="color: rgba(0, 0, 255, 1)">null</span> ? <span style="color: rgba(0, 0, 255, 1)">super</span><span style="color: rgba(0, 0, 0, 1)">.getAssets() : mAssetManager;
}
@Override
</span><span style="color: rgba(0, 0, 255, 1)">public</span><span style="color: rgba(0, 0, 0, 1)"> Resources getResources() {
</span><span style="color: rgba(0, 0, 255, 1)">return</span> mResources == <span style="color: rgba(0, 0, 255, 1)">null</span> ? <span style="color: rgba(0, 0, 255, 1)">super</span><span style="color: rgba(0, 0, 0, 1)">.getResources() : mResources;
}
@Override
</span><span style="color: rgba(0, 0, 255, 1)">public</span><span style="color: rgba(0, 0, 0, 1)"> Resources.Theme getTheme() {
</span><span style="color: rgba(0, 0, 255, 1)">return</span> mTheme == <span style="color: rgba(0, 0, 255, 1)">null</span> ? <span style="color: rgba(0, 0, 255, 1)">super</span><span style="color: rgba(0, 0, 0, 1)">.getTheme() : mTheme;
}
</span><span style="color: rgba(0, 128, 0, 1)">/**</span><span style="color: rgba(0, 128, 0, 1)">
* 把Assets里面得文件复制到 /data/data/files 目录下
*
* </span><span style="color: rgba(128, 128, 128, 1)">@param</span><span style="color: rgba(0, 128, 0, 1)"> context
* </span><span style="color: rgba(128, 128, 128, 1)">@param</span><span style="color: rgba(0, 128, 0, 1)"> sourceName
</span><span style="color: rgba(0, 128, 0, 1)">*/</span>
<span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">static</span> <span style="color: rgba(0, 0, 255, 1)">void</span><span style="color: rgba(0, 0, 0, 1)"> extractAssets(Context context, String sourceName) {
AssetManager am </span>=<span style="color: rgba(0, 0, 0, 1)"> context.getAssets();
InputStream is </span>= <span style="color: rgba(0, 0, 255, 1)">null</span><span style="color: rgba(0, 0, 0, 1)">;
FileOutputStream fos </span>= <span style="color: rgba(0, 0, 255, 1)">null</span><span style="color: rgba(0, 0, 0, 1)">;
</span><span style="color: rgba(0, 0, 255, 1)">try</span><span style="color: rgba(0, 0, 0, 1)"> {
is </span>=<span style="color: rgba(0, 0, 0, 1)"> am.open(sourceName);
File extractFile </span>=<span style="color: rgba(0, 0, 0, 1)"> context.getFileStreamPath(sourceName);
fos </span>= <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> FileOutputStream(extractFile);
</span><span style="color: rgba(0, 0, 255, 1)">byte</span>[] buffer = <span style="color: rgba(0, 0, 255, 1)">new</span> <span style="color: rgba(0, 0, 255, 1)">byte</span>;
</span><span style="color: rgba(0, 0, 255, 1)">int</span> count = 0<span style="color: rgba(0, 0, 0, 1)">;
</span><span style="color: rgba(0, 0, 255, 1)">while</span> ((count = is.read(buffer)) > 0<span style="color: rgba(0, 0, 0, 1)">) {
fos.write(buffer, </span>0<span style="color: rgba(0, 0, 0, 1)">, count);
}
fos.flush();
} </span><span style="color: rgba(0, 0, 255, 1)">catch</span><span style="color: rgba(0, 0, 0, 1)"> (IOException e) {
e.printStackTrace();
} </span><span style="color: rgba(0, 0, 255, 1)">finally</span><span style="color: rgba(0, 0, 0, 1)"> {
closeSilently(is);
closeSilently(fos);
}
}
</span>
<span style="color: rgba(0, 0, 255, 1)">private</span> <span style="color: rgba(0, 0, 255, 1)">static</span> <span style="color: rgba(0, 0, 255, 1)">void</span><span style="color: rgba(0, 0, 0, 1)"> closeSilently(Closeable closeable) {
</span><span style="color: rgba(0, 0, 255, 1)">if</span> (closeable == <span style="color: rgba(0, 0, 255, 1)">null</span><span style="color: rgba(0, 0, 0, 1)">) {
</span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)">;
}
</span><span style="color: rgba(0, 0, 255, 1)">try</span><span style="color: rgba(0, 0, 0, 1)"> {
closeable.close();
} </span><span style="color: rgba(0, 0, 255, 1)">catch</span><span style="color: rgba(0, 0, 0, 1)"> (Throwable e) {
e.printStackTrace();
}
}
}</span></pre>
</div>
<p>以上代码分为四个逻辑部分:</p>
<p>1). loadResources方法。通过反射创建AssetManager对象,调用addAssetPath方法,把插件Plugin1路径添加到这个AssetManager对象中。从此这个AssetManager就只为插件Plugin1服务了。在这个AssetManager对象的基础上,创建对应的Resource和Theme对象。</p>
<p>2). 重写Activity的getAsset,getResource和getTheme方法。重写逻辑见上面的代码。</p>
<p>3). 加载外部的插件,生成这个插件对应的ClassLoader。</p>
<p>4). 通过反射,获取插件中的类,构造出插件类的对象,然后就可以让插件类读取插件中的资源了。</p>
<p> </p>
<p>上述内容代码仓库地址为:https://github.com/renhui/RHPluginProgramming/tree/master/DexResAccess-master</p>
<p>基于这个思路,我们可以尝试使用插件资源替换当前显示的内容,实现换肤效果,核心思想是一样的,这里就不过多赘述了。</p>
<p> </p><br><br>
来源:https://www.cnblogs.com/renhui/p/11907493.html
頁:
[1]