Android插件化原理解析

Android插件化资源的使用及动态加载 附demo 上一篇我们已经完成了一个真正可运行的插件化demo,而且demo中也解决了插件中不可以使用资源的问题,但是由于篇幅的问题我们并没有对原理讲解,所以这一篇是对上一篇的一个收尾,如果没有看过上一篇建议先看Android插件化完美实现代码资源加载及原理讲解 附可运行demo.demo地址 : https://github.com/ljqloveyou123/LiujiaqiAndroid我们的宿主应用调用 阅读详情

概述

Android插件化技术,可以实现功能模块的按需加载和动态更新,其本质是动态加载未安装的apk。

本文涉及源码为API 28

插件化原理

插件化要解决的三个核心问题: 类加载、 资源加载、 组件生命周期管理。

类加载

Android中常用的两种类加载器: PathClassLoader和 DexClassLoader,它们都继承于BaseDexClassLoader。

// PathClassLoader.java
public class PathClassLoader extends BaseDexClassLoader {
    public PathClassLoader(String dexPath, ClassLoader parent) {
        super(dexPath, null, null, parent);
    }

    public PathClassLoader(String dexPath, String librarySearchPath, ClassLoader parent) {
        super(dexPath, null, librarySearchPath, parent);
    }
}

// DexClassLoader.java
public class DexClassLoader extends BaseDexClassLoader {
    public DexClassLoader(String dexPath, String optimizedDirectory, String librarySearchPath, ClassLoader parent) {
        super(dexPath, null, librarySearchPath, parent);
    }
}

说明:

  • DexClassLoader的构造函数比PathClassLoader多了一个, optimizedDirectory参数,这个是用来指定dex的优化产物odex的路径,在源码注释中,指出这个参数从API 26后就弃用了。
  • PathClassLoader主要用来加载系统类和应用程序的类,在ART虚拟机上可以加载未安装的apk的dex,在Dalvik则不行。
  • DexClassLoader用来加载未安装apk的dex。

资源加载

Android系统通过Resource对象加载资源,因此只需要添加资源(即apk文件)所在路径到 AssetManager中,即可实现对插件资源的访问。

// 创建AssetManager对象
AssetManager assetManager = new AssetManager();
// 将apk路径添加到AssetManager中
if (assetManager.addAssetPath(apkPath) == 0) {
    return null;
}
// 创建插件Resource对象
Resources pluginResources = new Resources(assetManager, metrics, getConfiguration());

说明:由于AssetManager的构造方法时 hide的,需要通过反射区创建。

组件生命周期管理

对于Android来说,并不是说类加载进来就可以使用了,很多组件都是有“生命”的;因此对于这些有血有肉的类,必须给他们注入活力,也就是所谓的 组件生命周期管理。

在解决插件中组件的生命周期,通常的做法是通过 Hook相应的系统对象,实现欺上瞒下,后面将通过Activity的插件化来进行讲解。

Activity插件化

四大组件的插件化是插件化技术的核心知识点,而Activity插件化更是重中之中,Activity插件化的主流实现方式是通过 Hook技术实现。

Activity的启动过程

image

image

上图列出的是启动一个Activity的主要过程,具体步骤如下:

  1. Activity1调用startActivity,实际会调用 Instrumentation类的 execStartActivity方法,Instrumentation是系统用来监控Activity运行的一个类,Activity的整个生命周期都有它的影子。
  2. 通过跨进程的binder调用,进入到 ActivityManagerService(AMS)中,其内部会处理Activity栈。之后又通过跨进程调用进入到Activity2所在的进程中。
  3. ApplicationThread是一个binder对象,其运行在binder线程池中,内部包含一个 H类,该类继承于Handler。ApplicationThread将启动Activity2的信息通过H对象发送给主线程。
  4. 主线程拿到Activity2的信息后,调用Instrumentation类的 newAcitivity方法,其内部通过ClassLoader创建Activity2实例。

加载插件中的类

public class PluginHelper {

    private static final String TAG = "PluginHelper";

    private static final String CLASS_DEX_PATH_LIST = "dalvik.system.DexPathList";
    private static final String FIELD_PATH_LIST = "pathList";
    private static final String FIELD_DEX_ELEMENTS = "dexElements";

       private static void loadPluginClass(Context context, ClassLoader hostClassLoader) throws Exception {
        // Step1. 获取到插件apk,通常都是从网络上下载,这里为了演示,直接将插件apk push到手机
        File pluginFile = context.getExternalFilesDir("plugin");
        Log.i(TAG, "pluginPath:" + pluginFile.getAbsolutePath());
        if (pluginFile == null || !pluginFile.exists() || pluginFile.listFiles().length == 0) {
            Toast.makeText(context, "插件文件不存在", Toast.LENGTH_SHORT).show();
            return;
        }
        pluginFile = pluginFile.listFiles()[0];
        // Step2. 创建插件的DexClassLoader
        DexClassLoader pluginClassLoader = new DexClassLoader(pluginFile.getAbsolutePath(), null, null, hostClassLoader);
        // Step3. 通过反射获取到pluginClassLoader中的pathList字段
        Object pluginDexPathList = ReflectUtil.getField(BaseDexClassLoader.class, pluginClassLoader, FIELD_PATH_LIST);
        // Step4. 通过反射获取到DexPathList的dexElements字段
        Object pluginElements = ReflectUtil.getField(Class.forName(CLASS_DEX_PATH_LIST), pluginDexPathList, FIELD_DEX_ELEMENTS);

        // Step5. 通过反射获取到宿主工程中ClassLoader的pathList字段
        Object hostDexPathList = ReflectUtil.getField(BaseDexClassLoader.class, hostClassLoader, FIELD_PATH_LIST);
        // Step6. 通过反射获取到宿主工程中DexPathList的dexElements字段
        Object hostElements = ReflectUtil.getField(Class.forName(CLASS_DEX_PATH_LIST), hostDexPathList, FIELD_DEX_ELEMENTS);

        // Step7. 将插件ClassLoader中的dexElements合并到宿主ClassLoader的dexElements
        Object array = combineArray(hostElements, pluginElements);

        // Step8. 将合并的dexElements设置到宿主ClassLoader
        ReflectUtil.setField(Class.forName(CLASS_DEX_PATH_LIST), hostDexPathList, FIELD_DEX_ELEMENTS, array);
    }
}

处理插件Activity的启动

在Android中,Activity的启动需要在 AndroidManifest.xml中配置,如果没有配置的话,就会报 ActivityNotFoundException异常,而插件的Activity无法再宿主AndroidManifest中注册。在上面的Activity的启动流程图,Activity的启动是要经过AMS的校验的,所以就需要对AMS下功夫。

Step1. 在宿主工程的AndroidManifest.xml中预先注册Activity进行占坑。

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    package="com.github.xch168.plugindemo">

    <application>

        <!--占坑Activity,不需要创建类-->
        <activity android:name=".StubActivity" />
    </application>

</manifest>

Step2. 使用占坑Activity绕过AMS验证。

Activity的启动,实际会调用 Instrumentation类的 execStartActvity方法,所以可以对其进行hook,将启动插件Activity的Intent替换成宿主预注册的插桩Activity,从而绕过ASM的验证。

Instrumentation代理类:

public class InstrumentationProxy extends Instrumentation {

    private Instrumentation mInstrumentation;
    private PackageManager mPackageManager;

    public InstrumentationProxy(Instrumentation instrumentation, PackageManager packageManager) {
        mInstrumentation = instrumentation;
        mPackageManager = packageManager;
    }

    public ActivityResult execStartActivity(Context who, IBinder contextThread, IBinder token, Activity target, Intent intent, int requestCode, Bundle options) {
        List<ResolveInfo> infos = mPackageManager.queryIntentActivities(intent, PackageManager.MATCH_ALL);
        if (infos == null || infos.size() == 0) {
            // 保存要启动的插件Activity的类名
            intent.putExtra(HookHelper.TARGET_INTENT, intent.getComponent().getClassName());
            // 构建插桩Activity的Intent
            intent.setClassName(who, "com.github.xch168.plugindemo.StubActivity");
        }
        try {
            Method execMethod = Instrumentation.class.getDeclaredMethod("execStartActivity", Context.class, IBinder.class, IBinder.class, Activity.class, Intent.class, int.class, Bundle.class);
            // 将插桩Activity的Intent传给ASM验证
            return (ActivityResult) execMethod.invoke(mInstrumentation, who, contextThread, token, target, intent, requestCode, options);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }
}

Hook:

public class HookHelper {
    public static final String TARGET_INTENT = "target_intent";

    public static void hookInstrumentation(Context context) throws Exception {
        Class<?> contextImplClass = Class.forName("android.app.ContextImpl");
        Object activityThread = ReflectUtil.getField(contextImplClass, context, "mMainThread");
        Class<?> activityThreadClass = Class.forName("android.app.ActivityThread");
        Object mInstrumentation = ReflectUtil.getField(activityThreadClass, activityThread, "mInstrumentation");
        // 用代理Instrumentation来替换mMainThread中的mInstrumentation,从而接管Instrumentation的任务
        ReflectUtil.setField(activityThreadClass, activityThread, "mInstrumentation", new InstrumentationProxy((Instrumentation) mInstrumentation, context.getPackageManager()));
    }
}

Step3. 还原插件Activity

上面我们使用插桩Activity来绕过ASM的验证,接下来的步骤会创建 StubActivity实例,会找不到类,并且我们要启动的是插件Activity而不是插桩Activity,所以就需要对Intent进行还原。 在Activity启动流程第10步,通过插件的ClassLoader反射创建插件Activity,所以可以在这hook进行还原。

public class InstrumentationProxy extends Instrumentation {
    // ...

    public Activity newActivity(ClassLoader cl, String className, Intent intent) throws IllegalAccessException, InstantiationException, ClassNotFoundException {
        // 获取插件Activity的类名
        String intentName = intent.getStringExtra(HookHelper.TARGET_INTENT);
        if (!TextUtils.isEmpty(intentName)) {
            // 创建插件Activity实例
            return super.newActivity(cl, intentName, intent);
        }
        return super.newActivity(cl, className, intent);
    }
}

Step4. 在Application中hook Instrumentation。

public class App extends Application {

    @Override
    protected void attachBaseContext(Context base) {
        super.attachBaseContext(base);

        try {
            HookHelper.hookInstrumentation(base);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

处理插件Activity的生命周期

经过上面的处理后,插件Activity可以启动了,但是是否具有生命周期呢?接下来通过源码来探索一下。

Activity的 finish方法可以触发Activity生命周期的变化。

public void finish() {
    finish(DONT_FINISH_TASK_WITH_ACTIVITY);
}

private void finish(int finishTask) {
    // ...

    // mToken是该Activity的标识
    if (ActivityManager.getService().finishActivity(mToken, resultCode, resultData, finishTask)) {
        mFinished = true;
    }
}

说明:

  • 调用ASM的finishActivity方法,接着ASM通过ApplicationThread调用ActivityThread。
  • ActivityThread最终会调用performDestroyActivity方法。
public final class ActivityThread extends ClientTransactionHandler {
    // ...

    ActivityClientRecord performDestroyActivity(IBinder token, boolean finishing, int configChanges, boolean getNonConfigInstance, String reason) {

        // 获取保存到token的Activity
        mInstrumentation.callActivityOnDestroy(r.activity);
    }
}

token中的Activity是从何而来呢?解析来我们来看看ActivityThread的performLaunchActivity方法。

public final class ActivityThread extends ClientTransactionHandler {

    private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {

        // ...
        activity = mInstrumentation.newActivity(cl, component.getClassName(), r.intent);

        r.activity = activity;

        mActivities.put(r.token, r);
    }
}

说明:在performLaunchActivity方法中,会将当前启动的Activity放在token中的activity属性,并将其置于mActivities中,而mInstrumentation的newActivity方法已经被我们hook了,所以该activity即为插件Activity,后续各个生命周期的调用都会通知给插件Activity。

加载插件中的资源

当插件Activity创建的时候会调用 setContentView通过id去操作布局,因为凡是通过id去获取资源的方式都是通过 Resources去获取的。但是宿主apk不知道到插件apk的存在,所以宿主Resources也无法加载插件apk的资源。因此需要为插件apk构建一个 Resources,然后插件apk中都通过这个Resource区获取资源。

public class PluginHelper {

    private static Resources sPluginResources;

    public static void initPluginResource(Context context) throws Exception {
        Class<AssetManager> clazz = AssetManager.class;
        AssetManager assetManager = clazz.newInstance();
        Method method = clazz.getMethod("addAssetPath", String.class);
        method.invoke(assetManager, context.getExternalFilesDir("plugin").listFiles()[0].getAbsolutePath());
        sPluginResources = new Resources(assetManager, context.getResources().getDisplayMetrics(), context.getResources().getConfiguration());
    }

    public static Resources getPluginResources() {
        return sPluginResources;
    }
}
public class App extends Application {

    @Override
    public Resources getResources() {
        return PluginHelper.getPluginResources() == null ? super.getResources() : PluginHelper.getPluginResources();
    }
}

说明:在Application中重写 getResources,并返回插件的Resources,因为插件apk中的四大组件实际都是在宿主apk创建的,那么他们拿到的Application实际上都是宿主的,所以它们只需要通过 getApplication().getResources()就可以非常方便的拿到插件的Resource。

插件工程

插件工程比较简单,就一个Activity。

public class PluginActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_plugin);
    }

    @Override
    public Resources getResources() {
        return getApplication() != null && getApplication().getResources() != null ? getApplication().getResources() : super.getResources();
    }
}

说明:重写 getResources方法,并返回插件Resources,因为需要通过插件Resources才能用id去操作资源文件。

测试

Step1. 将插件项目打包成apk;

Step2. 通过adb命令 adb push<local><remote>将apk推送到手机;

Step3. 宿主应用加载插件apk。

总结

插件化要处理的细节非常多,不仅要适配不同版本的Android系统,还要适配国产的各种ROM。要深入学习插件化的各种解决方案,可以去探索开源的插件化框架。

2018年Android 9.0上Android开始对私有API的使用进行限制,所以后面插件化可能退出历史主流,但是了解插件化涉及到的知识和技术,可以更好的理解Android系统。


Demo地址:https://github.com/xch168/PluginDemo

交流更多技术可以加群:4112676

深度解析Android插件化技术的核心原理 插件化技术通过自定义ClassLoader,实现对插件包中的类和资源的加载和管理,从而实现动态加载插件模块的目的。在Android开发中,插件化技术是一种重要的应用架构模式,它允许开发者在不修改主应用代码的情况下,动态地加载和执行插件模块。该方法首先创建插件的ClassLoader,然后使用反射机制实例化插件中的类,并将插件实例添加到插件管理器中。当出现线上Bug时,开发者可以通过修复Bug的插件模块,将其作为补丁动态加载到主应用中,从而实现Bug的修复。插件化技术可以帮助开发者实现应用的模块化开发。 阅读详情

相关推荐

Verilog——反相器

反相器代码: module inv( A, Y );//A,Y是端口 //定义端口属性 input A; output Y; //定义输入输出关系 assign Y=~A;//Y等于A的反 endmodule //inv是module的名字

weixin_41788560的博客 9499

android 插件化 demo,Android 插件化开发-主题皮肤更换

本项目是以插件化开发思想进行的,主要工作和代码如下资源文件,这里以color资源为例1、首先我们需要准备一个皮肤包,这个皮肤包里面不会包含任何Activity,里面只有资源文件,这里我为了简单,仅仅加入一个color.xml(其实就相当于Android系统中的framework_res.apk)#E61ABD#38F709#000000#FFFFFF2、将该资源打包成apk文件,放入sd卡中(实际...

weixin_35502466的博客 328

TI C2000 DSP CLA实战:用F28069的协处理器实现ADC采样与PWM相位实时联动控制

本文详细介绍了如何利用TI C2000系列DSP的CLA协处理器在TMS320F28069上实现ADC采样与PWM相位的实时联动控制。通过CLA的零周期上下文切换和硬件级任务触发特性,显著提升了电机控制和数字电源应用的实时性能,特别适用于需要亚微秒级响应的场景。

weixin_30932215的博客 3966

Android插件化原理(一)—— 插件类加载、类加载原理、(双亲委托机制)

Acitivity启动原理(一),pauseActivity原理

blankmargin的博客 2402

二维数字图像相关算法软件Ncorr的使用心得

二维开源软件Ncorr的DIC分析操作过程详解。

m0_72612723的博客 3万+

【crawler笔记】R语言简单动态网页爬虫(rvest包)示例

1、爬虫目标 大家普遍认为Python的爬虫功能强大,但在解决动态加载或者登陆网站时,Python存在一定困难的,相对于一些普通爬虫,使用R语言会更方便。 以https://www.thepaper.cn/为例,爬取首页的新闻(标题、内容、时间),主要采用的是动态网页中常用的httr包。 初始学习时,参考了B站《20分钟入门基于R语言的网络爬虫_哔哩哔哩 (゜-゜)つロ 干杯~-bilibili》的视频,但代码运行出现了报错,所以在原代码上做了修改。 2、代码分析及报错修改 library(rv

seeyouer1205的博客 3275

Android 插件化原理及实践

概述 插件化是一个非常大的话题,他包含很多的知识点,我们今天简单的学习一下他的原理,并且从零开始实现插件化,这里主要用到了Hook技术 关联文章 Android APK资源加载流程 Android 中的ClassLoader Android App启动过程 Android 热修复原理实战 设计模式 – 代理模式 插件化需要解决的问题和技术 Hook技术 插件的类加载 插件的资源加载 启动插件Ac...

R.先森的博客 465

【一键搞定】ThinkPHP6.0.2+PHPStudy+Composer极速安装指南——附完整下载包与避坑技巧

本文提供了一份ThinkPHP6.0.2的完整下载包,并详细介绍了如何配合PHPStudy和Composer进行极速安装与配置。指南涵盖了环境准备、Composer国内镜像加速、项目部署及常见避坑技巧,旨在帮助开发者,尤其是内网或网络不佳的用户,快速搭建可运行的TP6开发环境,绕过依赖下载的繁琐步骤。

assembly8low的博客 1006

Android组件化开发简单示例

一、组件化初始模型 1、通过一个简单的android项目初始架构图来了解组件化,如下图: 打个生动的比喻,把我们的APP当成一个电脑主机,那么app外壳就是主机外壳,main组件就是主板,其他各个组件就类似于硬盘、网卡、显卡之类的东西,各个组件连接到主板上,然后再安装到主机壳中,对外展示为一个完整的电脑主机。 2、app外壳和main组件是我们app的必备组成部分,一起构成了可对外发布的完整app,其他组件可以集成进来,也可以不集成进来,只会增加或者减少我们app的功能,但不影响我们app的最终.

美奇软件开发工作室 8473

从Protocol到硬件控制:揭秘高通UEFI中ABL与XBL的协作机制

本文深入解析了高通UEFI架构中ABL与XBL的协作机制,重点探讨了Protocol通信在硬件控制中的关键作用。通过对比传统GPIO控制方式,揭示了Protocol架构在安全性、可维护性和灵活性方面的优势,并提供了关机充电场景的实战案例分析及调试技巧。

weixin_29185199的博客 430

[SV]SystemVerilog中,系统函数$cast()的作用是什么?

摘要:SystemVerilog中的$cast()系统函数用于安全的动态类型转换,主要在类层次结构中将父类句柄转换为子类句柄(向下转换)。它通过运行时类型检查确保转换合法性,成功时返回1并赋值句柄,失败则返回0。这种机制使得可以通过父类句柄访问子类特有成员,在多态场景(如UVM验证)中尤为重要。使用时需检查返回值处理转换失败情况。$cast()比静态转换更安全,但不能用于无关类或基本数据类型间的转换。

元直的博客 1210

Android 插件化原理解析

Android 插件化原理解析——Hook机制之AMS&PMS

yzpbright的博客 232

玩转Docker | 使用Docker部署Trilium Notes知识库工具

玩转Docker | 使用Docker部署Trilium Notes知识库工具

qq_59334230的博客 1441

Android 插件化原理解析——广播插件的实现与安装apk原理解析

android技术特别成熟了,热修复,组件化......等框架已经层出不穷,如果还仅限于使用框架上,技术永远很难得到成长,只有我们懂得他的原理,能够娓娓道来,能够自己动手的写出来,技术才会越来越好,与其想着未来怎么办,不如把握现在。这一篇文章教大家手写出插件化框架,插件化技术是Android工程师必备的技术之一,我们要懂其思想,知其原理。

1 1万+

迈瑞BS系列全自动生化分析仪LIS协议接口手册(内含HL7和ASTM)

迈瑞BS系列全自动生化分析仪LIS协议接口手册,内含HL7和ASTM)

WINCC7.3破解文件

WINCC7.3硬件够破解文件,有需要的下载!WINCC7.3硬件够破解文件,有需要的下载!

Android插件化原理解析——概要

2015年是Android插件化技术突飞猛进的一年,随着业务的发展各大厂商都碰到了Android Native平台的瓶颈: 从技术上讲,业务逻辑的复杂导致代码量急剧膨胀,各大厂商陆续出到65535方法数的天花板;同时,运营为王的时代对于模块热更新提出了更高的要求。 在业务层面上,功能模块的解耦以及维护团队的分离也是大势所趋;各个团队维护着同一个App的不同模块,如果每个模块升级新功能都需要对整个app进行升级,那么发布流程不仅复杂而且效率低下;在讲究小步快跑和持续迭代的移动互联网必将遭到淘汰。

孤独的狼 494

C# WinForm 基于DirectShow.dll编写的程序,可边预览边保存视频,音频和图片

基于微软的DirectShow.dll的二次开发1.自动识别连接的摄像头跟麦克风且可以对其进行选择(默认选中第一个)2.可录制wmv和avi两种格式的视频并且都带有音频,avi可以录制的分辨率和帧数进行选择,wmv默认进行了淸晰度优化如不喜欢可去除代码中有相关提示3.可在预览点开之后进行拍照,也可边录制视频边拍照4.添加了视频和音频的压缩功能(酌情使用)这次二次开发发现C#对DirectShow的相关信息太少,这次做完也不敢藏私共享出来,还望大家指正

Android插件化之DroidPlugin原理解析

DroidPlugin原理解析 从系统设计的角度,组件和窗口的逻辑实体都存在于系统服务,比如Activity创建后,其逻辑控制主体在AMS,对于窗口,其实逻辑控制主体在WMS android将逻辑主体放置于系统服务,系统就可以对组件和窗口的生命周期,显示状态进行强掌控,这样就能做到在各种状态变更时能做到及时回调通知 所以,创建任何组件,都需要通过RPC通讯到AMS创建 — 第一个hook点 那逻辑...

拿节 2312

Android插件化之RePlugin原理解析

Replugin原理解析

拿节 1915

Android 插件化原理解析——Service的插件化

在 Activity生命周期管理 以及 广播的管理 中我们详细探讨了Android系统中的Activity、BroadcastReceiver组件的工作原理以及它们的插件化方案,相信读者已经对Android Framework和插件化技术有了一定的了解;本文将探讨Android四大组件之一——Service组件的插件化方式。 与Activity, BroadcastReceiver相比,

huangkun125的专栏 3206

android插件化实现,Android插件化原理解析——剧终

从我写下 Android插件化原理解析 系列第一篇文章至今,已经过去了两年时间。这期间,插件化技术也得到了长足的发展;与此同时,React Native,PWA,App Bundle,以及最近的Flutter也如火如荼。由于实现插件化需要太多的黑科技,它给项目的维护成本和稳定性增加了诸多不确定性;我个人认为,2017年手淘Atlas插件化项目的开源标志着插件化的落幕,2018年Android 9....

weixin_35992880的博客 176

Android插件化原理解析——ContentProvider的插件化

目前为止我们已经完成了Android四大组件中Activity,Service以及BroadcastReceiver的插件化,这几个组件各不相同,我们根据它们的特点定制了不同的插件化方案;那么对于ContentProvider,它又有什么特点?应该如何实现它的插件化? 与Activity,BroadcastReceiver等频繁被使用的组件不同,我们接触和使用ContentProvider的

huangkun125的专栏 2488

Android插件化原理解析及常见框架对比

常见android插件框架对比 Dynamic-Load-Apk DroidPlugin VirtualApk RePlugin 总结

分享Android应用和系统开发、音视频编码、性能优化相关知识 2076

Android 插件化 广播,Android插件化原理解析——广播的管理

在Activity生命周期管理 以及 插件加载机制 中我们详细讲述了插件化过程中对于Activity组件的处理方式,为了实现Activity的插件化我们付出了相当多的努力;那么Android系统的其他组件,比如BroadcastReceiver,Service还有ContentProvider,它们又该如何处理呢?相比Activity,BroadcastReceiver要简单很多——广播的生命周期...

weixin_34221384的博客 287
上一篇: Android 面试 15 家大厂,这个问题是必问!
下一篇: Android中的权限问题
动脑学院[David]
博客等级 码龄8年 44粉丝 31原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值