【Android插件化】启动没有在Manifest中注册的Activity

2024-09-03 14:58

本文主要是介绍【Android插件化】启动没有在Manifest中注册的Activity,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

1 概述

如果要启动没有在Manifest中注册的Activity,应该从startActivity着手。一般启动Activity的方式有两种,一种是startActivity,一种是startActivityForResult。其实startActivity最终调用的也是startActivityForResult,如下所示:

    //Activity.java@Overridepublic void startActivity(Intent intent, @Nullable Bundle options) {if (options != null) {startActivityForResult(intent, -1, options);} else {// Note we want to go through this call for compatibility with// applications that may have overridden the method.startActivityForResult(intent, -1);}}

当我们启动一个Activity时,AMS会对这个Activity是否在AndroidManifest中声明注册进行检查,如果没有注册,则会报错。要想启动没有在AndroidManifest中注册的Activity,则需要“欺骗AMS”,让其以为我们已经在AndroidManifest中注册了。

基本思路是:

  1. 发送要启动的Activity信息给AMS之前,把这个Activity替换为一个在AndroidManifest中声明的StubActivity,这样就能绕过AMS的检查,在替换过程中,要把原来的Activity信息保存起来。
  2. AMS通知App启动StubActivity时,我们再将保存的Activity替换StubActivity,这样就能启动没有在AndroidManifest中注册的Activity。

修改Activity启动流程如图所示(图片来自网络)

image.png

2 代码实现

AndroidManifest.xml

    <applicationandroid:name=".MainApplication"android:allowBackup="true"android:icon="@mipmap/ic_launcher"android:label="@string/app_name"android:roundIcon="@mipmap/ic_launcher_round"android:supportsRtl="true"android:theme="@style/AppTheme"><activity android:name=".MainActivity"><intent-filter><action android:name="android.intent.action.MAIN" /><category android:name="android.intent.category.LAUNCHER" /></intent-filter></activity><activity android:name=".SubActivity"/></application>

一共两个Activity,分别是MainActivity和SubActivity,一个MainApplication,SubActivity是一个壳Activity,用于欺骗AMS的。

MainActivity.java

    private View.OnClickListener mListener = new View.OnClickListener() {@Overridepublic void onClick(View v) {Intent intent = new Intent();intent.setClass(MainActivity.this, PluginTestActivity.class);startActivity(intent);}};

PluginTestActivity并没有在AndroidManifest中注册,正常情况下会报错,所以我们需要欺骗AMS。

欺骗AMS的方式有几种,这里我们选择对ActivityThread的mInstrumentation进行hook。代码如下:

public class HookHelper {public static final String PLUGIN_INTENT = "plugin_intent";public static void hookInstrumentation(Context context,String className) throws Exception{//获取ActivityThread的字节码对象Class<?> clazz = Class.forName("android.app.ActivityThread");//获取ActivityThread中的sCurrentActivityThread字段Field sCurrentActivityThreadField = ReflectUtils.getField(clazz,"sCurrentActivityThread");//获取ActivityThread中的mInstrumentation字段Field mInstrumentationField = ReflectUtils.getField(clazz,"mInstrumentation");//获取ActivityThread中的sCurrentActivityThread的值并赋值给currentActivityThreadObject currentActivityThread = sCurrentActivityThreadField.get(clazz);//获取ActivityThread中的mInstrumentation的值并赋值给instrumentationInstrumentation instrumentation = (Instrumentation) mInstrumentationField.get(currentActivityThread);//创建一个PackageManager对象packageManagerPackageManager packageManager = context.getPackageManager();//创建InstrumentationProxy对象,InstrumentationProxy是继承Instrumentation的类,下面详细介绍InstrumentationProxy instrumentationProxy = new InstrumentationProxy(instrumentation,packageManager,className);//将ActivityThread中的mInstrumentation赋值为instrumentationProxyReflectUtils.setFieldObject(clazz,currentActivityThread,"mInstrumentation",instrumentationProxy);}
}

InstrumentationProxy类如下

package com.example.plugintest;import android.app.Activity;
import android.app.Instrumentation;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.os.Bundle;
import android.os.IBinder;
import android.text.TextUtils;import java.lang.reflect.Method;
import java.util.List;/*** Created by yds* on 2020/2/14.*/
public class InstrumentationProxy extends Instrumentation{private Instrumentation mInstrumentation;private PackageManager mPackageManager;private String className;public InstrumentationProxy(Instrumentation mInstrumentation, PackageManager mPackageManager, String className) {this.mInstrumentation = mInstrumentation;this.mPackageManager = mPackageManager;this.className = className;}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) {intent.putExtra(HookHelper.PLUGIN_INTENT,intent.getComponent().getClassName());intent.setClassName(who,className);}try {Method execMethod = Instrumentation.class.getDeclaredMethod("execStartActivity",Context.class,IBinder.class,IBinder.class,Activity.class,Intent.class,int.class,Bundle.class);return (ActivityResult) execMethod.invoke(mInstrumentation,who,contextThread,token,target,intent,requestCode,options);} catch (Exception e) {e.printStackTrace();}return null;}public Activity newActivity(ClassLoader cl,String className,Intent intent) throwsInstantiationException,IllegalAccessException,ClassNotFoundException{String intentName = intent.getStringExtra(HookHelper.PLUGIN_INTENT);if(!TextUtils.isEmpty(intentName)){return super.newActivity(cl,intentName,intent);}return super.newActivity(cl,className,intent);}
}

只要将InstrumentationProxy赋值给ActivityThread的mInstrumentation,调用startActivity时,最终会调用InstrumentationProxy的execStartActivity和newActivity。我们只要在execStartActivity中将未注册的Activity保存起来,并使用已注册的SubActivity,然后在newActivity中将保存起来未注册的Activity替换SubActivity就可以了。

源码地址:https://github.com/Yedongsheng/PluginTest01

这篇关于【Android插件化】启动没有在Manifest中注册的Activity的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1133280

相关文章

Nexus安装和启动的实现教程

《Nexus安装和启动的实现教程》:本文主要介绍Nexus安装和启动的实现教程,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录一、Nexus下载二、Nexus安装和启动三、关闭Nexus总结一、Nexus下载官方下载链接:DownloadWindows系统根

Java中实现线程的创建和启动的方法

《Java中实现线程的创建和启动的方法》在Java中,实现线程的创建和启动是两个不同但紧密相关的概念,理解为什么要启动线程(调用start()方法)而非直接调用run()方法,是掌握多线程编程的关键,... 目录1. 线程的生命周期2. start() vs run() 的本质区别3. 为什么必须通过 st

Oracle修改端口号之后无法启动的解决方案

《Oracle修改端口号之后无法启动的解决方案》Oracle数据库更改端口后出现监听器无法启动的问题确实较为常见,但并非必然发生,这一问题通常源于​​配置错误或环境冲突​​,而非端口修改本身,以下是系... 目录一、问题根源分析​​​二、保姆级解决方案​​​​步骤1:修正监听器配置文件 (listener.

MySQL版本问题导致项目无法启动问题的解决方案

《MySQL版本问题导致项目无法启动问题的解决方案》本文记录了一次因MySQL版本不一致导致项目启动失败的经历,详细解析了连接错误的原因,并提供了两种解决方案:调整连接字符串禁用SSL或统一MySQL... 目录本地项目启动报错报错原因:解决方案第一个:第二种:容器启动mysql的坑两种修改时区的方法:本地

CnPlugin是PL/SQL Developer工具插件使用教程

《CnPlugin是PL/SQLDeveloper工具插件使用教程》:本文主要介绍CnPlugin是PL/SQLDeveloper工具插件使用教程,具有很好的参考价值,希望对大家有所帮助,如有错... 目录PL/SQL Developer工具插件使用安装拷贝文件配置总结PL/SQL Developer工具插

Nacos注册中心和配置中心的底层原理全面解读

《Nacos注册中心和配置中心的底层原理全面解读》:本文主要介绍Nacos注册中心和配置中心的底层原理的全面解读,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录临时实例和永久实例为什么 Nacos 要将服务实例分为临时实例和永久实例?1.x 版本和2.x版本的区别

Android学习总结之Java和kotlin区别超详细分析

《Android学习总结之Java和kotlin区别超详细分析》Java和Kotlin都是用于Android开发的编程语言,它们各自具有独特的特点和优势,:本文主要介绍Android学习总结之Ja... 目录一、空安全机制真题 1:Kotlin 如何解决 Java 的 NullPointerExceptio

maven中的maven-antrun-plugin插件示例详解

《maven中的maven-antrun-plugin插件示例详解》maven-antrun-plugin是Maven生态中一个强大的工具,尤其适合需要复用Ant脚本或实现复杂构建逻辑的场景... 目录1. 核心功能2. 典型使用场景3. 配置示例4. 关键配置项5. 优缺点分析6. 最佳实践7. 常见问题

MySQL启动报错:InnoDB表空间丢失问题及解决方法

《MySQL启动报错:InnoDB表空间丢失问题及解决方法》在启动MySQL时,遇到了InnoDB:Tablespace5975wasnotfound,该错误表明MySQL在启动过程中无法找到指定的s... 目录mysql 启动报错:InnoDB 表空间丢失问题及解决方法错误分析解决方案1. 启用 inno

无法启动此程序因为计算机丢失api-ms-win-core-path-l1-1-0.dll修复方案

《无法启动此程序因为计算机丢失api-ms-win-core-path-l1-1-0.dll修复方案》:本文主要介绍了无法启动此程序,详细内容请阅读本文,希望能对你有所帮助... 在计算机使用过程中,我们经常会遇到一些错误提示,其中之一就是"api-ms-win-core-path-l1-1-0.dll丢失