【Android】Android插件开发 —— 打开插件的Activity(代理方式)

本文介绍了一种通过代理方式实现Android插件中Activity启动的方法。插件Activity作为一个普通类,通过持有宿主Activity引用,模拟正常Activity效果。文章还讨论了插件Activity的Context问题、生命周期方法重写以及其他方法的调用。

转:http://blog.youkuaiyun.com/h28496/article/details/50414873

前言

在写这篇之前,还有两篇关于插件开发的博客: 
1. 【Android】Android插件开发 —— 基础入门篇 
2. 【Android】Android插件开发 —— 打开插件的Activity(预注册方式) 
如果读者对这篇提到的一些东西不太清楚,可以在之前的这两篇中查看。 
3. 本文地址:http://blog.youkuaiyun.com/H28496/article/details/50414873 转载请注明。


1. 用代理的方式打开插件Activity的整体思想

  1. 插件中的Activity由于没有在宿主的AndroidManifest.xml中注册,因此不能直接由宿主程序打开。但是,我们仍然可以通过DexClassLoader去获取到插件中的Activity,并且执行它的各种方法,只不过这时的Activity就只是一个普通的类了,没有了各种生命周期,无法被当作Activity启动。
  2. 那无法被当作正常的Activity启动,是否可以模拟出是正常Activity的效果呢?这是可以的。
  3. 当要启动插件Activity的时候,启动一个宿主的Activity作为代理。当生命周期执行到onCreate() 、onStart()的时候,系统会调用宿主Activity的onCreate()、onStart()方法,再由宿主的onCreate()方法去调用插件的onCreate()方法。这就模拟出了插件Activity在执行的效果。

2. 插件的Activity

1. 关于插件Activity的Context

由于插件Activity只是一个普通的类,并没有上下文Context,所以插件的Activity在需要用到(Context)this的地方会遇到问题。比如:

startActivity(new Intent(this, xxxx.Class));
 
  • 1

这样是无法打开Activity的,怎么解决呢? 
插件Activity需要持有对宿主Activity的引用。 
在需要用到上下文Context的地方,不再使用this关键字,而是使用getActivity()代替,其中proxyActivity是宿主Activity:

public Context getActivity(){
        return proxyActivity;
}
 
  • 1
  • 2
  • 3

为了让插件能够独立运行,也就是说插件既可以独立的安装运行,又可以作为插件给宿主运行。可以在getActivity()中加一个判断,当被作为插件使用时才返回proxyActivity,正常情况下返回this:

public Context getActivity(){
    if(isProxyMode){
        return proxyActivity;
    }else{
        return this;
    }
}
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

这样创建Intent时就可以这样写了:

Intent intent = new Intent(getActivity(), XXXX.class);
 
  • 1

那插件Activity怎么获得宿主Activity的引用呢?通过setProxy()方法,其中BaseActivity是插件Activity:

public class BaseActivity extends Activity{
    /**
     * 宿主的Activity
     */
    protected Activity proxyActivity;

    /**
     * 是否作为插件运行
     */
    public boolean isProxyMode = false;

    public void setProxy(Activity activity){
        this.proxyActivity = activity;
        isProxyMode = true;
    }
}
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

setProxy(Activity activity)是由宿主Activity调用的。具体怎么调用待会再讲。

2. 关于生命周期的方法

我们知道,Activity关于生命周期的方法都会调用super.onXXXX()的。由于插件Activity并不是由系统通过正常方式打开的,在插件Activity中调用super.onXXXX()方法会报错。并且,插件的生命周期已经交给宿主Activity去执行了,所以插件的生命周期中不再需要执行super.onXXX()方法。所以我们需要重写插件Activity中关于生命周期的方法。同时考虑到我们的插件不仅可以作为插件使用,还可以作为正常的apk安装使用,使得正常安装时会调用super.onXXX(),作为插件时不再调用:

@Override
public void onCreate(Bundle savedInstanceState) {
    if(!isProxyMode){
        super.onCreate(savedInstanceState);
    }
    // 这里执行插件自己的代码
}
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

其它周期的方法类似。

3. 关于其它方法

和生命周期的方法类似,插件Activity的其他方法也不能使用super.xxxx()方法了。但和生命周期的方法不同的是:其他方法并没有通过宿主Activity代理。所以还需要我们手动调用宿主Activity执行,重写这些方法,例如:

@Override
public void setContentView(int layoutResID) {
    if(isProxyMode){
        proxyActivity.setContentView(layoutResID);
    }else{
        super.setContentView(layoutResID);
    }
}

@Override
public void startActivity(Intent intent) {
    if(isProxyMode){
        proxyActivity.startActivity(intent);
    }else{
        super.startActivity(intent);
    }
}
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

我们把上面这些重写的代码放到一个基类BaseActivity中,插件中的其他Activity类继承BaseActivity。这样我们在使用的时候就能够和普通Activity一样使用了,侵入性很低:

public class MainActivity extends BaseActivity {

    @Override
    public void onCreate(Bundle savedInstanceState) {
        TextView tv = new TextView(this);
        tv.setText("这是插件的Activity");
        setContentView(tv);
    }
}
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

当然,如果很懒,不想去重写一堆方法,也可以这样写:

getActivity().setContentView(tv);
 
  • 1

2. 宿主的Activity

1. 初始化工作

根据传入的插件的类名,从外部apk中加载对应的类并实例化出一个对象。 
这一步在前两篇博客中已有详细描述,这里只贴代码:

/**
 * 初始化classLoader
 */
private void initClassLoader() {
    // 插件放在sd卡的根目录下
    String apkPath = getIntent().getStringExtra(EXTRA_APK_PATH);

    // dex文件的释放目录
    File releasePath = getDir("dexs", 0);

    // 类加载器
    classLoader = new DexClassLoader(apkPath, releasePath.getAbsolutePath(), null, getClassLoader());


    // 注入到原生的ClassLoader中
    ClassInject inject = new ClassInject();
    inject.inject((PathClassLoader) getClassLoader(), classLoader);
}

/**
 * 加载被代理Activity的信息
 */
public void loadProxiedActivity(){      
    // 实例化被代理类
    String className = getIntent().getStringExtra(PROXIED_CLASS_NAME);
    android.util.Log.i("郑海鹏", "ProxyActivity#initProxiedActivity(): " + "传入的类名为:\n" + className);
    try {
        Class<?> clazz = classLoader.loadClass(className);
        proxiedActivity = clazz.newInstance();
        // 使得插件的Activity持有宿主Activity的引用
        Method method = proxiedActivity.getClass().getMethod("setProxy", Activity.class);
        method.setAccessible(true);
        method.invoke(proxiedActivity, this);
    } catch (Exception e) {
        e.printStackTrace();
    }
}
 
  • 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

注意插件Activity的setProxy()方法就是在这类调用的。

2. 代理操作

在系统回调生命周期时,执行插件Activity对应的方法 
例如在onPause()中执行插件的onPause()方法:

@Override
protected void onPause() {
    if(proxiedActivity != null){
        try {
            Method method = proxiedActivity.getClass().getMethod("onPause", new Class[]{});
            method.setAccessible(true);
            method.invoke(proxiedActivity, new Object[]{});
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    super.onPause();
}
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

proxiedActivity是插件Activity。这样就实现了对插件的代理。

3. 源码下载

http://download.youkuaiyun.com/detail/h28496/9379695

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值