【Android开发学习笔记】【高级】【随笔】插件化——资源加载
前言
上一节我们针对插件最基本的原理进行了一个简单的demo实现,但是由于插件的Context对象被宿主所接管,因此无法加载插件程序的资源。那么如何解决这个问题捏?
有人提出这样的方案:将apk中的资源解压到某个目录下,然后通过读文件的方法进行资源加载,虽然理论上可以实现,但是实际操作起来难度很大,首先资源文件格式不一(xml、图片等),其次针对不同分辨率的手机,Android设备也会加载同名不同路径下的图片,因此这种方案实现起来难度非常大。也有人说,可以将插件资源复制一份到宿主工程中,然而如果这样做了,我们在插件更新的时候,必须宿主也得更新资源后更新,同时这样也会增加宿主工程安装包的大小,这与我们当时使用插件化来解决问题的思想不符,因此这种方案也不可使用。那么我们应该使用什么样的原理呢?
原理
通过看Android的源代码,我们发现Context下有这两个抽象的方法:

而Context对象获取资源就是靠这两个函数来完成的,而这两个抽象的方法在ContextImpl.java中实现是这个样子的:

于是我们需要一个Resources的对象,查阅代码之后,最终发现了AssetManager.java下的这个函数:

看英文注释,我们发现这个函数,我们可以传递给一个zip包或者是资源的路径,同时这个函数还是隐藏的函数,因此我们还是使用反射来,调用这个函数后就可以得到Resources的对象了。
实现
所以我们需要在宿主中实现一个 loadResources() 这样的函数,用来加载插件的资源:
package com.bryan.host; import java.io.File;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method; import dalvik.system.DexClassLoader; import android.annotation.SuppressLint;
import android.app.Activity;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.res.AssetManager;
import android.content.res.Resources;
import android.content.res.Resources.Theme;
import android.os.Bundle;
import android.provider.MediaStore.Video; public class ProxyActivity extends Activity
{
...... /* 用来加载资源的*/
protected AssetManager mAssetManager;
protected Resources mResources = null;
protected Theme mTheme = null; .... /* 加载插件的指定activity*/
@SuppressLint("NewApi") protected void OpenAppointActivity(final String className)
{
.... try
{
loadResources();
... /* 反射 调用插件中的设置代理 */
... /* 反射告诉插件是被宿主调起的*/
.... } catch (Exception e)
{
e.printStackTrace();
}
} protected void loadResources()
{
try
{
AssetManager assetManager = AssetManager.class.newInstance();
Method addAssetPath = assetManager.getClass().getMethod("addAssetPath", String.class);
addAssetPath.invoke(assetManager, mExtraDexPath);
mAssetManager = assetManager;
} catch (Exception e) {
e.printStackTrace();
}
Resources superResources = super.getResources();
mResources = new Resources(mAssetManager, superResources.getDisplayMetrics(), superResources.getConfiguration());
mTheme = mResources.newTheme();
mTheme.setTo(super.getTheme());
} /* 重写这两个加载资源的函数 */
@Override
public AssetManager getAssets()
{
if (mAssetManager != null)
{
return mAssetManager;
}
return super.getAssets();
} @Override
public Resources getResources() {
if (mResources != null)
{
return mResources;
}
return super.getResources();
} }
注意这个函数的位置,一定要放到反射插件的OnCreate之前,其次重写 getAssets() 和 getResources() 即可。
插件工程我们需要在BaseActivity中处理 getAssets() 和 getResources() 这两个函数,方法与之前类似,考虑插件自己启动还是被宿主加载的两种情况:
package com.bryan.plugin; import android.app.Activity;
import android.content.Intent;
import android.content.res.AssetManager;
import android.content.res.Resources;
import android.os.Bundle;
import android.view.View;
import android.view.ViewGroup.LayoutParams; public class BaseActivity extends Activity
{
/* 宿主工程中的代理Activity*/
protected Activity mProxyActivity; /* 判断是被谁调起的,如果是宿主调起的为1 */
int Who = 0; public void setProxy(Activity proxyActivity)
{
mProxyActivity = proxyActivity;
} @Override
protected void onCreate(Bundle savedInstanceState)
{ if (savedInstanceState != null)
{
Who = savedInstanceState.getInt("Host", 0);
}
if (Who == 0)
{
super.onCreate(savedInstanceState);
mProxyActivity = this;
}
} ...... /* 重写几个重要的添加布局的类 */
...... /* 重写加载资源的方法 */
@Override
public Resources getResources() {
if (Who == 0)
{
return super.getResources();
}
else
{
return mProxyActivity.getResources();
}
} @Override
public AssetManager getAssets() {
if (Who == 0)
{
return super.getAssets();
}
else
{
return mProxyActivity.getAssets();
}
} }
Base中处理完成之后,我们在插件主Activity中尝试一下加载资源的代码:
package com.bryan.plugin; import android.content.Context;
import android.graphics.Color;
import android.graphics.drawable.BitmapDrawable;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup.LayoutParams;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.LinearLayout; public class MainActivity extends BaseActivity { @Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); // 初始化处理布局
InitView();
} private void InitView()
{
View view = CreateView(mProxyActivity);
mProxyActivity.setContentView(view); System.out.println("MainActivity.InitView()" + "activity name is :"+mProxyActivity.getClass().getSimpleName());
} private View CreateView(final Context context)
{
LinearLayout linearLayout = new LinearLayout(context); linearLayout.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
linearLayout.setBackgroundColor(Color.parseColor("#F4F4D6"));
Button button = new Button(context);
button.setText("plugin button");
linearLayout.addView(button, LayoutParams.WRAP_CONTENT,LayoutParams.WRAP_CONTENT);
ImageView image = new ImageView(context);
image.setImageBitmap(new BitmapDrawable( getResources().openRawResource(R.drawable.qq)).getBitmap());
linearLayout.addView(image, LayoutParams.WRAP_CONTENT,LayoutParams.WRAP_CONTENT);
button.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
StartActivityByProxy("com.bryan.plugin.TestActivity");
}
});
return linearLayout;
}
}
我们在插件的资源中插入一张qq的图片,资源名字命名为qq,然后在加载资源的地方加入41-43行的代码,这通过这样的方式来进行加载资源。
结果
首先运行插件工程:

其次运行宿主工程:

可以看到在宿主工程中成功加载了插件工程的资源。
看样子我们已经解决了一个很棘手的问题,还剩一个Activity生命周期函数失效的问题,下一节我们来讲这个。
【Android开发学习笔记】【高级】【随笔】插件化——资源加载的更多相关文章
- 驱动开发学习笔记. 0.07 Uboot链接地址 加载地址 和 链接脚本地址
驱动开发学习笔记. 0.07 Uboot链接地址 加载地址 和 链接脚本地址 最近重新看了乾龙_Heron的<ARM 上电启动及 Uboot 代码分析>(下简称<代码分析>) ...
- android开发学习笔记000
使用书籍:<疯狂android讲义>——李刚著,2011年7月出版 虽然现在已2014,可我挑来跳去,还是以这本书开始我的android之旅吧. “疯狂源自梦想,技术成就辉煌.” 让我这个 ...
- 【Android开发学习笔记】【高级】【随笔】插件化——Activity生命周期
前言 如同第一章我们说的,宿主程序通过 dexclassloader 将插件的类加载进来,然后通过反射去调用它的方法,这样Activity就被当成了一个普通的类来执行了,因此系统不再接管它的生命周期, ...
- android开发学习笔记系列(1)-android起航
前言 在学习安卓的过程中,我觉得非常有必要将自己所学的东西进行整理,因为每每当我知道我应该是如何去实现功能的时候,有许多细节问题我总是会遗漏,因此我也萌生了写一系列博客来描述自己学习的路线,让我的an ...
- 【转】Android开发学习笔记(一)——初识Android
对于一名程序员来说,“自顶向下”虽然是一种最普通不过的分析问题和解决问题的方式,但其却是简单且较为有效的一种.所以,将其应用到Android的学习中来,不至于将自己的冲动演变为一种盲目和不知所措. 根 ...
- Android开发学习笔记DDMS的使用
打开DDMS DDMS 的全称是Dalvik Debug Monitor Service,是 Android 开发环境中的Dalvik虚拟机调试监控服务. DDMS里面包含了:Device(设备) F ...
- 【转】Android开发学习笔记:5大布局方式详解
Android中常用的5大布局方式有以下几种: 线性布局(LinearLayout):按照垂直或者水平方向布局的组件. 帧布局(FrameLayout):组件从屏幕左上方布局组件. 表格布局(Tabl ...
- 【Android开发学习笔记之一】5大布局方式详解
Android中常用的5大布局方式有以下几种: 线性布局(LinearLayout):按照垂直或者水平方向布局的组件. 帧布局(FrameLayout):组件从屏幕左上方布局组件. 表格布局(Tabl ...
- Android开发学习笔记(二)——编译和运行原理(1)
http://www.cnblogs.com/Pickuper/archive/2011/06/14/2078969.html 接着上一篇的内容,继续从全局了解Android.在清楚了Android的 ...
随机推荐
- HDU4057 Rescue the Rabbit(AC自动机+状压DP)
题目大概是给几个DNA片段以及它们各自的权值,如果一个DNA包含某个片段那么它的价值就加上这个片段的权值,同时包含多个相同DNA片段也只加一次,问长度l的DNA可能的最大价值. 与HDU2825大同小 ...
- LightOJ1013 Love Calculator(DP)
容易猜测到包含s1.s2序列的串的最短长度是LCS(s1,s2) + ( len(s1) - LCS(s1,s2) ) + ( len(s2) - LCS(s1,s2) ) ,即: len(s1)+l ...
- Revit二次开发示例:HelloRevit
本示例实现Revit和Revit打开的文件的相关信息. #region Namespaces using System; using System.Collections.Generic; using ...
- Revit二次开发示例:EventsMonitor
在该示例中,插件在Revit启动时弹出事件监控选择界面,供用户设置,也可在添加的Ribbon界面完成设置.当Revit进行相应操作时,弹出窗体会记录事件时间和名称. #region Namespace ...
- HDU 1242 (BFS搜索+优先队列)
题目链接: http://acm.hdu.edu.cn/showproblem.php?pid=1242 题目大意:多个起点到一个终点,普通点耗时1,特殊点耗时2,求到达终点的最少耗时. 解题思路: ...
- js中等性操作符(==)、关系操作符(<,>)和布尔操作符(!)比较规则
最近一直在笔试面试,经常碰到例如 123=='123'.'abc'==true等问题,其中有答对的,也有答错的,主要原因还是对ECMAScript的规范没有理解清楚,很多题目没有具体分析所导致.现查阅 ...
- 【BZOJ】1089: [SCOI2003]严格n元树(递推+高精度/fft)
http://www.lydsy.com/JudgeOnline/problem.php?id=1089 题意:求深度为d的n元树数目.(0<n<=32, 0<=d<=16) ...
- COJ986 WZJ的数据结构(负十四)
WZJ的数据结构(负十四) 难度级别:D: 运行时间限制:6000ms: 运行空间限制:262144KB: 代码长度限制:2000000B 试题描述 请你设计一个数据结构,完成以下功能: 给定一个大小 ...
- python模块之codecs
http://blog.csdn.net/suofiya2008/article/details/5579413
- 平易近人、兼容并蓄——Spark SQL 1.3.0概览
自2013年3月面世以来,Spark SQL已经成为除Spark Core以外最大的Spark组件.除了接过Shark的接力棒,继续为Spark用户提供高性能的SQL on Hadoop解决方案之外, ...