前言


  上一节我们针对插件最基本的原理进行了一个简单的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开发学习笔记】【高级】【随笔】插件化——资源加载的更多相关文章

  1. 驱动开发学习笔记. 0.07 Uboot链接地址 加载地址 和 链接脚本地址

    驱动开发学习笔记. 0.07 Uboot链接地址 加载地址 和 链接脚本地址 最近重新看了乾龙_Heron的<ARM 上电启动及 Uboot 代码分析>(下简称<代码分析>) ...

  2. android开发学习笔记000

    使用书籍:<疯狂android讲义>——李刚著,2011年7月出版 虽然现在已2014,可我挑来跳去,还是以这本书开始我的android之旅吧. “疯狂源自梦想,技术成就辉煌.” 让我这个 ...

  3. 【Android开发学习笔记】【高级】【随笔】插件化——Activity生命周期

    前言 如同第一章我们说的,宿主程序通过 dexclassloader 将插件的类加载进来,然后通过反射去调用它的方法,这样Activity就被当成了一个普通的类来执行了,因此系统不再接管它的生命周期, ...

  4. android开发学习笔记系列(1)-android起航

    前言 在学习安卓的过程中,我觉得非常有必要将自己所学的东西进行整理,因为每每当我知道我应该是如何去实现功能的时候,有许多细节问题我总是会遗漏,因此我也萌生了写一系列博客来描述自己学习的路线,让我的an ...

  5. 【转】Android开发学习笔记(一)——初识Android

    对于一名程序员来说,“自顶向下”虽然是一种最普通不过的分析问题和解决问题的方式,但其却是简单且较为有效的一种.所以,将其应用到Android的学习中来,不至于将自己的冲动演变为一种盲目和不知所措. 根 ...

  6. Android开发学习笔记DDMS的使用

    打开DDMS DDMS 的全称是Dalvik Debug Monitor Service,是 Android 开发环境中的Dalvik虚拟机调试监控服务. DDMS里面包含了:Device(设备) F ...

  7. 【转】Android开发学习笔记:5大布局方式详解

    Android中常用的5大布局方式有以下几种: 线性布局(LinearLayout):按照垂直或者水平方向布局的组件. 帧布局(FrameLayout):组件从屏幕左上方布局组件. 表格布局(Tabl ...

  8. 【Android开发学习笔记之一】5大布局方式详解

    Android中常用的5大布局方式有以下几种: 线性布局(LinearLayout):按照垂直或者水平方向布局的组件. 帧布局(FrameLayout):组件从屏幕左上方布局组件. 表格布局(Tabl ...

  9. Android开发学习笔记(二)——编译和运行原理(1)

    http://www.cnblogs.com/Pickuper/archive/2011/06/14/2078969.html 接着上一篇的内容,继续从全局了解Android.在清楚了Android的 ...

随机推荐

  1. FZU2219 StarCraft(哈夫曼树)

    一个工人可以变成两个工人,这样可以画出一颗二叉树,那么就是在叶子上建的建筑. 问题的时间花费,可以看作是这颗二叉树中各个叶子的深度*k+叶子对应建筑耗费时间中的最大值. 容易想到,类似哈夫曼树一样,从 ...

  2. C#线程间同步无法关闭

    用C#做了个线程间同步的小程序,但每次关闭窗口后进程仍然在,是什么原因? 解决方法: 要加一句 线程.IsBackground = true; 否则退出的只是窗体 上面的方法没看懂... MSDN上说 ...

  3. JavaScript中toStirng()与Object.prototype.toString.call()方法浅谈

    toStirng()与Object.prototype.toString.call()方法浅谈 一.toString()是一个怎样的方法?它是能将某一个值转化为字符串的方法.然而它是如何将一个值从一种 ...

  4. HDU 4666 Hyperspace(曼哈顿距离)

    题目链接 这是HDU第400个题. #include <cstdio> #include <cstring> #include <set> #include < ...

  5. COJ990 WZJ的数据结构(负十)

    DFS序(带入栈出栈标记): 对于一个节点,我们用L[i]和R[i]表示它入栈和出栈的时间.这样[L[i],R[i]]就表示了以i为根的区间. 我们还要将入栈的符号为+,出栈的符号为-,那么令V[i] ...

  6. hiho 毁灭者问题

    描述 在 Warcraft III 之冰封王座中,毁灭者是不死族打三本后期时的一个魔法飞行单位. 毁灭者的核心技能之一,叫做魔法吸收(Absorb Mana): 现在让我们来考虑下面的问题: 假设你拥 ...

  7. oracle系列--第四篇 Oracle的卸载

    对于oracle的卸载,是一件相对麻烦的事情,不像其他软件那样,我们可以根据卸载向导,就可以方便快捷地卸载软件. oracle的卸载,我们要涉及到修改注册表,重启计算机等操作. 我们有些时候有必要卸载 ...

  8. Struts1与Struts2的12点区别

    Struts1与Struts2的12点区别  1) 在Action实现类方面的对比:Struts 1要求Action类继承一个抽象基类:Struts 1的一个具体问题是使用抽象类编程而不是接口.Str ...

  9. 微课程--Android--Activity组建与Intent

    安卓的四大基本组件 Activity是经常使用的组件 1 展示用户界面 2 响应用户操作 Service 1 在后台长时间运行 2 没有用户界面 ContentProvider 1 管理和共享应用数据 ...

  10. C# - Try catch 中 使用 End()

    如果在Try中执行End()会被扑捉到.这通常并不是我想要捕捉的错误.而是结束页面的手段.可通过调整为以下代码结构修复 //this.DbHelp.BeginTransaction(); //设置回滚 ...