Android插件化开发,如何打开未安装的应用

本文介绍了Android插件化开发的概念,通过实例展示了如何使未安装的应用通过主应用运行。文章详细阐述了启动未安装应用Activity的原理,包括布局转移和生命周期管理,并提供了具体实现步骤,包括统一接口、BaseActivity封装、插件应用加载与启动,以及宿主Activity的编写。最后,文章给出了插件化应用的测试结果。
摘要由CSDN通过智能技术生成

什么是插件化开发

我的理解很简单,主应用就像一个插座,任何符合其要求的插头(插件应用),都可以通过这个插座来让自己从一个“死应用(未安装)”变成一个“活应用(运行)”。而对主应用来说,插上插头或者拔出插头,它都可以正常运行,只不过会丧失插头提供的额外功能。

这样,当我们需要为主应用添加功能时,在主应用中只需要编写一个功能入口即可,剩下的工作就是按照主应用提供的接口要求开发或者修改插件应用,从而完成新功能的接入。

使用了插件化开发的应用

比如我们最熟悉的支付宝,其大小只有60多MB,但是它却像一个应用市场一般,为我们提供了非常丰富的功能应用,比如淘票票(应用62.9MB)、共享单车(ofo:39.6MB、哈啰出行59.3MB…)等等,光这几个应用加起来就远比安装包大得多了。当然,支付宝很多功能都是采用H5实现的,但上面提到的三个应用则都是使用插件化实现的。那这是如何分辨出来的呢?

我们只需要打开开发者选项 -> 显示布局边界即可看到安卓每一个控件的布局边界,而在WebView中的控件,则无法识别,只会显示整个WebView的边界,由此我们就可以分辨出哪些是H5哪些是原生控件了。效果如下图:
ofo小黄车布局边界
我们可以明显的看到,每一个按钮,每一张图片都被标识了其布局边界,说明它们都是安卓原生控件,而不是使用WebView承载的。

而共享单车的选择页面却是使用WebView开发的,如下图:

共享单车选择页面布局边界
我们看到中间部分就是一个WebView,只能看到最外圈的布局边界。其实这样的设计就很巧妙,我们知道,如果只需要修改H5,那么就不需要更新app,只需要更新H5就可以了,同时,这也是为了应对变化,例如下一次再添加一个XX共享单车,则只需要在H5中再添加一个按钮即可。而单个共享单车的页面又是使用原生应用,某方面的因素也是为了提高用户体验,毕竟H5的速度比起原生还是差了些。

插件化开发如何实现

通过前面的叙述我们了解到,所谓的插件,可以认为就是一个app,而这个app不需要安装就可以通过其他应用打开,那这是如何做到的呢?

其实这中间最重要的就是生命周期管理。我们都知道,Activity都有自己的生命周期函数,但这些函数的调用却不是由我们自己控制的,而是由系统调用的。当Art在启动一个Activity的过程中,就会为其注入上下文,也就是我们最熟悉的Context,有了Context,Activity就可以拿到当前应用的资源文件,从而就能获取到布局然后显示在屏幕上(当然,Context还有很多其他的用途)。但是,Art只能启动已经安装的应用中的AndroidManifet.xml中声明过的Activity,只有这样的Activity我们才可以通过调用Context.startActivity(Intent intent)来启动。

也就是说,未安装的应用的Activity无法通过Context.startActivity(Intent intent)启动,那我们又如何去启动一个无法启动的Activity呢?

如何启动一个无法启动的Activity

答案就是曲线救国啦:
没法启动未安装的?那就启动已安装的,也就是我们的主应用的Activity(后文简称宿主Activity)。

首先,我们分析下Activity最典型的特点是什么?
1.布局,用来显示界面;
2.生命周期,控制Activity的生老病死。

布局转移

先解决第一个问题,布局。
我们需要把插件应用中的Activity的布局显示出来,其实就只需要把它的布局显示在宿主Activity上就可以了。

传递生命周期

第二个问题,生命周期。
同样的,我们只需要把宿主Activity(由系统管理,有正常的生命周期回调)的生命周期,同步到插件应用的Activity中即可。例如:在宿主Activity的onStart()方法中调用插件应用的Activity的onStart()方法,这样就实现了生命周期的传递。

具体实现

接下来就是撸码环节。

统一接口

前面说到,插头要想插到插座上,就需要满足插座提供的要求,也就是接入规范,所以我们就来制定这个规范。
我们提供一个接口,要求接入时必须实现对应的方法,主要包括Activity的所有生命周期函数(onCreate, onStart, onResume, onPause, onStop, onDestroy…)以及事件处理函数(onBackPressed, onKeyDown, onTouchEvent…)等等。

PluginInterface.java

public interface PluginInterface {
   
    /**
     * 对应Activity生命周期方法-onCreate
     * @param savedInstanceState 保存的状态信息
     */
    void onCreate(Bundle savedInstanceState);

    /**
     * 对应Activity生命周期方法-onStart
     */
    void onStart();

    /**
     * 对应Activity生命周期方法-onResume
     */
    void onResume();

    /**
     * 对应Activity生命周期方法-onPause
     */
    void onPause();

    /**
     * 对应Activity生命周期方法-onStop
     */
    void onStop();

    /**
     * 对应Activity生命周期方法-onDestroy
     */
    void onDestroy();

    /**
     * 对应Activity方法-onActivityResult
     * @param requestCode 请求码
     * @param resultCode  结果码
     * @param data        回调数据
     */
    void onActivityResult(int requestCode, int resultCode, Intent data);

    /**
     * 对应Activity方法-onRestoreInstanceState
     * @param savedInstanceState 保存的状态
     */
    void onRestoreInstanceState(Bundle savedInstanceState);

    /**
     * 对应Activity方法-onSaveInstanceState
     * @param outState 保存的状态
     */
    void onSaveInstanceState(Bundle outState);

    /**
     * 对应Activity方法-onKeyDown
     * @param keyCode 按键码
     * @param event   事件
     * @return        是否消费事件
     */
    boolean onKeyDown(int keyCode, KeyEvent event);

    /**
     * 对应Activity方法-onTouchEvent
     * @param event 事件
     * @return      是否消费事件
     */
    boolean onTouchEvent(MotionEvent event);

    /**
     * 对应Activity方法-onBackPressed
     */
    void onBackPressed();

    /**
     * 宿主注入上下文
     * @param activity 宿主activity
     */
    void attach(Activity activity);

    /**
     * 移除宿主activity
     */
    void detach();
}

这里额外添加了两个函数,attach和detach,attach是用来将宿主Activity的引用传递给插件应用的Activity,这样就可以在其中使用基本的Activity的方法了,detach则是为了防止内存泄漏啦,在onDestroy中移除宿主Activity的引用即可。

提供统一的BaseActivity

接口写好了,难道直接由插件应用的Activity去继承实现吗?当然不行啦,要是直接这样扔一个接口给开发者,那可能会比产品经理死得还惨。。。

所以我们需要对其再进行封装,尽量减少接入者的工作量,这样也有利于统一规范。

假设当前宿主Activity会正确回调上述接口中所有的方法(后文会详细介绍宿主Activity的编写),那么我们的插件Activity就有了生命周期了。那么在这里我们还需要重写所有跟Context有关的方法,因为插件Activity是没有上下文的,我们只能通过手动为其设置上下文,才能让它正确的调用Activity的方法和使用应用资源。

那么与Context有关的方法有哪些呢?

主要有setContentView(int layoutResID), findViewById(int id), getLayoutInflater(), getClassLoader(), getWindow() 以及 getWindowManager()等等,可能还有漏掉的,这里只列出了最常用的几个。

BasePluginActivity.java

public class BasePluginActivity extends AppCompatActivity  implements PluginInterface {
   

    /**
     * 宿主Activity
     */
    protected Activity mHostActivity;

    /**
     * 将布局设置到宿主Activity上
     * @param layoutResID 布局资源ID
     */
    @Override
    public void setContentView(int layoutResID) {
   
        if (mHostActivity == null) {
   
            super.setContentView(layoutResID);
        } else {
   
            mHostActivity.setContentView(layoutResID);
        }
    }

    /**
     * 布局都设置到别人那了
     * 自然只能去别人那要了
     */
    @Override
    public <T extends View> T findViewById(int id) {
   
        if (mHostActivity == null) {
   
            return super.findViewById(id);
        } else {
   
            return mHostActivity.findViewById(id);
        }
    }

    /**
     * 这里获取到的ClassLoader是使用插件应用的apk生成的DexClassLoader
     */
    @Override
    public ClassLoader getClassLoader() {
   
        if (mHostActivity == null) {
   
            return super.getClassLoader();
        } else {
   
            return mHostActivity.getClassLoader();
        }
    }

    @NonNull
    @Override
    public LayoutInflater getLayoutInflater() {
   
        if (mHostActivity == null) {
   
            return super.getLayoutInflater();
        } else {
   
            return mHostActivity.getLayoutInflater();
        }
    }

    @Override
    public Window getWindow() {
   
        if (mHostActivity == null) {
   
            return super.getWindow();
        } else {
   
            return mHostActivity.getWindow();
        }
    }

    @Override
    public WindowManager getWindowManager() {
   
        if (mHostActivity == null) {
   
            return super.getWindowManager();
        } else {
   
            return mHostActivity.getWindowManager();
        }
    }

    @Override
    public void onCreate(Bundle savedInstanceState) {
   
        if (mHostActivity == null) {
   
            super.onCreate(savedInstanceState);
        }
    }

    @Override
    public void onStart() {
   
        if (mHostActivity == null) {
   
            super.onStart();
        }
    }

    @Override
    public void onResume() {
   
        if (mHostActivity == null) {
   
            super.onResume();
        }
    }

    @Override
    public void onPause() {
   
        if (mHostActivity == null) {
   
            super.onPause();
        }
    }

    @Override
    public void onStop() {
   
        
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值