13.源码阅读(启动一个没有注册的Activity为何会抛出异常-have you declared this activity in your AndroidManifest.xml?--android api 23)

简介: app中每一个activity都要在AndroidManifest文件中配置,否则启动会抛出异常Unable to find explicit activity class .

app中每一个activity都要在AndroidManifest文件中配置,否则启动会抛出异常

Unable to find explicit activity class ..; have you declared this activity in your AndroidManifest.xml?

那么我们是否可以启动一个没有注册的activity呢?这就是今天看源码的目的

系统如何检查AndroidManifest中是否注册有一个activity?

在文章03.源码阅读(Activity启动流程--android api 23)中我们已经通过源码知道,启动一个activity调用startActivity后,会进入Instrumentation的方法中

public ActivityResult execStartActivity(
            Context who, IBinder contextThread, IBinder token, Activity target,
            Intent intent, int requestCode, Bundle options) {
            ......
            int result = ActivityManagerNative.getDefault()
                .startActivity(whoThread, who.getBasePackageName(), intent,
                        intent.resolveTypeIfNeeded(who.getContentResolver()),
                        token, target != null ? target.mEmbeddedID : null,
                        requestCode, 0, null, options);
            //通过result值进行一些列的异常抛出,上边的那个就是这里抛出的,
            checkStartActivityResult(result, intent);
            ......
    }

已经知道ActivityManagerNative.getDefault()(api26叫ActivityManager.getService())这行代码已经看过多次,但是这次还是要贴出来说一说,因为很重要,它得到的是IActivityManager这个接口的一个实现类----ActivityMangagerService,最终还是要去看ActivityMangagerService中的startActivity

/**
     * Retrieve the system's default/global activity manager.
     */
    static public IActivityManager getDefault() {
        return gDefault.get();
    }

    private static final Singleton<IActivityManager> gDefault = new Singleton<IActivityManager>() {
        protected IActivityManager create() {
            IBinder b = ServiceManager.getService("activity");
            if (false) {
                Log.v("ActivityManager", "default service binder = " + b);
            }
            IActivityManager am = asInterface(b);
            if (false) {
                Log.v("ActivityManager", "default service = " + am);
            }
            return am;
        }
    };

public abstract class Singleton<T> {
    private T mInstance;

    protected abstract T create();

    public final T get() {
        synchronized (this) {
            if (mInstance == null) {
                mInstance = create();
            }
            return mInstance;
        }
    }
}

IActivityManager这个接口是hook拦截activity创建的关键,这里先放着
进入ActivityManagerService的startActivity中,一路点进入会来到ActivityStackSupervisor类中的startActivityLocked方法

final int startActivityLocked(IApplicationThread caller,
            Intent intent, String resolvedType, ActivityInfo aInfo,
            IVoiceInteractionSession voiceSession, IVoiceInteractor voiceInteractor,
            IBinder resultTo, String resultWho, int requestCode,
            int callingPid, int callingUid, String callingPackage,
            int realCallingPid, int realCallingUid, int startFlags, Bundle options,
            boolean ignoreTargetSecurity, boolean componentSpecified, ActivityRecord[] outActivity,
            ActivityContainer container, TaskRecord inTask) {
            //activity启动的返回结果
            int err = ActivityManager.START_SUCCESS;
            ......
            if (err == ActivityManager.START_SUCCESS && aInfo == null) {
                    // 就是在这个返回值的情况下会抛出activity未注册的异常
                    err = ActivityManager.START_CLASS_NOT_FOUND;
            }
            ......
}

看一下什么情况下会返回这个,err == ActivityManager.START_SUCCESS 一上来就设置了,所以关键看aInfo,它什么时候会为null,那么现在要回退过去了,看看aInfo是何时赋值的,找到ActivityStackSupervisor中的方法startActivityMayWait

final int startActivityMayWait(IApplicationThread caller, int callingUid,
            String callingPackage, Intent intent, String resolvedType,
            IVoiceInteractionSession voiceSession, IVoiceInteractor voiceInteractor,
            IBinder resultTo, String resultWho, int requestCode, int startFlags,
            ProfilerInfo profilerInfo, WaitResult outResult, Configuration config,
            Bundle options, boolean ignoreTargetSecurity, int userId,
            IActivityContainer iContainer, TaskRecord inTask) {
            ......
            ActivityInfo aInfo =
                resolveActivity(intent, resolvedType, startFlags, profilerInfo, userId);
            ......
}
ActivityInfo resolveActivity(Intent intent, String resolvedType, int startFlags,
            ProfilerInfo profilerInfo, int userId) {
        ......
        ActivityInfo aInfo;
        try {
            ResolveInfo rInfo =
                AppGlobals.getPackageManager().resolveIntent(
                        intent, resolvedType,
                        PackageManager.MATCH_DEFAULT_ONLY
                                    | ActivityManagerService.STOCK_PM_FLAGS, userId);
            //ActivityInfo是从ResolveInfo中的activityInfo赋值得到的,所以需要找到ResolveInfo如何获取的
            aInfo = rInfo != null ? rInfo.activityInfo : null;
        } catch (RemoteException e) {
            aInfo = null;
        }
        ......
    }

AppGlobals.getPackageManager()最终得到的是IPackageManager的实现类PackageManagerService,来到这里的resolveIntent方法->chooseBestActivity->findPreferredActivity->getActivityInfo

@Override
    public ActivityInfo getActivityInfo(ComponentName component, int flags, int userId) {
        if (!sUserManager.exists(userId)) return null;
        //检查users-permission 
        enforceCrossUserPermission(Binder.getCallingUid(), userId, false, false, "get activity info");
        synchronized (mPackages) {
            PackageParser.Activity a = mActivities.mActivities.get(component);

            if (DEBUG_PACKAGE_INFO) Log.v(TAG, "getActivityInfo " + component + ": " + a);
            if (a != null && mSettings.isEnabledLPr(a.info, flags, userId)) {
                PackageSetting ps = mSettings.mPackages.get(component.getPackageName());
                if (ps == null) return null;
                return PackageParser.generateActivityInfo(a, flags, ps.readUserState(userId),
                        userId);
            }
            if (mResolveComponentName.equals(component)) {
                return PackageParser.generateActivityInfo(mResolveActivity, flags,
                        new PackageUserState(), userId);
            }
        }
        return null;
    }

进入PackageParser中,可以看到,只要Activity a不为null,ActivityInfo就不会为null,那么到了这里又要回退过去看看Activity什么时候为null的

public static final ActivityInfo generateActivityInfo(Activity a, int flags,
            PackageUserState state, int userId) {
        if (a == null) return null;
        if (!checkUseInstalledOrHidden(flags, state)) {
            return null;
        }
        if (!copyNeeded(flags, a.owner, state, a.metaData, userId)) {
            return a.info;
        }
        // Make shallow copies so we can store the metadata safely
        ActivityInfo ai = new ActivityInfo(a.info);
        ai.metaData = a.metaData;
        ai.applicationInfo = generateApplicationInfo(a.owner, flags, state, userId);
        return ai;
    }

来到PackageManagerService类中,可以看到activity是从集合中取出来的

@Override
    public ActivityInfo getActivityInfo(ComponentName component, int flags, int userId) {
        if (!sUserManager.exists(userId)) return null;
        enforceCrossUserPermission(Binder.getCallingUid(), userId, false, false, "get activity info");
        synchronized (mPackages) {
            PackageParser.Activity a = mActivities.mActivities.get(component);

            if (DEBUG_PACKAGE_INFO) Log.v(TAG, "getActivityInfo " + component + ": " + a);
            if (a != null && mSettings.isEnabledLPr(a.info, flags, userId)) {
                PackageSetting ps = mSettings.mPackages.get(component.getPackageName());
                if (ps == null) return null;
                return PackageParser.generateActivityInfo(a, flags, ps.readUserState(userId),
                        userId);
            }
            if (mResolveComponentName.equals(component)) {
                return PackageParser.generateActivityInfo(mResolveActivity, flags,
                        new PackageUserState(), userId);
            }
        }
        return null;
    }

PackageParser.Activity a = mActivities.mActivities.get(component);这个集合以ComponentName为key,以这个Activity为value,ComponentName肯定是类的全类名,包名+类名的形式,那么能不能从这个集合中取出这个activity关键就是看这个activity有没有被加入集合了

private final ArrayMap<ComponentName, PackageParser.Activity> mActivities
                = new ArrayMap<ComponentName, PackageParser.Activity>();

可以在PackageManagerService类中找到这个方法

public final void addActivity(PackageParser.Activity a, String type) {
            final boolean systemApp = a.info.applicationInfo.isSystemApp();
            mActivities.put(a.getComponentName(), a);
            ......
        }

这里可以猜测一下,app启动后(或者app安装时就已经扫描保存了)应该会扫描AndroidManifest文件,然后将activity都加入到这个集合,然后启动一个activity的时候如果从这个集合中找不到这个activity,就抛出异常activity未在AndroidManifest中注册,所以肯定有一个扫描AndroidManifest文件的过程,这个过程我们放到下一篇博客中说,今天看到这里基本上已经达到我们的目的了,总结一下

所有在AndroidManifest中注册的activity会被加入一个集合中,这个集合以这个activity的包名+类型为key值存储这个activity,当调用startActivity启动一个activity的时候,会根据这个activity的全类名去这个集合中查找,如果查找不到就表明这个activity没有在AndroidManifest中注册,抛出异常(这里省略了系统扫描AndroidManifest配置文件的过程,以app启动时,activity已经被加入集合为前提)

再次回到我们最初的问题,我们是否可以启动一个没有注册的activity?通过看源码也了解到了,之所以没有注册的activity启动会被抛出异常,是因为有一个监测的过程,如果能绕过这个监测过程,岂不是就可以启动这个activity了

这里采用偷梁换柱的方式来实现,通过动态代理hook Activity的启动过程,启动activity时,系统通过Intent中传递的Activity的ComponentName去集合中查找,只要查找得到就会躲过检查,那么我们的思路是这样的,当我们要启动一个未注册的activity时,传递一个已经注册的activity的componentname作为傀儡,当监测通过的时候,真正要启动这个activity的时候再将这个未注册的替换过去,达到偷梁换柱的目的

具体实现见下一篇博客

相关文章
|
6月前
|
存储 消息中间件 人工智能
【08】AI辅助编程完整的安卓二次商业实战-修改消息聊天框背景色-触发聊天让程序异常终止bug牵涉更多聊天消息发送优化处理-优雅草卓伊凡
【08】AI辅助编程完整的安卓二次商业实战-修改消息聊天框背景色-触发聊天让程序异常终止bug牵涉更多聊天消息发送优化处理-优雅草卓伊凡
473 10
【08】AI辅助编程完整的安卓二次商业实战-修改消息聊天框背景色-触发聊天让程序异常终止bug牵涉更多聊天消息发送优化处理-优雅草卓伊凡
|
5月前
|
XML 数据采集 API
用Lxml高效解析XML格式数据:以天气API为例
免费Python教程:实战解析中国天气网XML数据,详解Lxml库高效解析技巧、XPath用法、流式处理大文件及IP封禁应对策略,助你构建稳定数据采集系统。
339 0
|
8月前
|
存储 机器学习/深度学习 API
Android API Level 到底是什么?和安卓什么关系?应用发布如何知道自己的版本?优雅草卓伊凡
Android API Level 到底是什么?和安卓什么关系?应用发布如何知道自己的版本?优雅草卓伊凡
1269 31
Android API Level 到底是什么?和安卓什么关系?应用发布如何知道自己的版本?优雅草卓伊凡
|
监控 Java 应用服务中间件
高级java面试---spring.factories文件的解析源码API机制
【11月更文挑战第20天】Spring Boot是一个用于快速构建基于Spring框架的应用程序的开源框架。它通过自动配置、起步依赖和内嵌服务器等特性,极大地简化了Spring应用的开发和部署过程。本文将深入探讨Spring Boot的背景历史、业务场景、功能点以及底层原理,并通过Java代码手写模拟Spring Boot的启动过程,特别是spring.factories文件的解析源码API机制。
470 2
|
存储 人工智能 API
AgentScope:阿里开源多智能体低代码开发平台,支持一键导出源码、多种模型API和本地模型部署
AgentScope是阿里巴巴集团开源的多智能体开发平台,旨在帮助开发者轻松构建和部署多智能体应用。该平台提供分布式支持,内置多种模型API和本地模型部署选项,支持多模态数据处理。
9909 78
AgentScope:阿里开源多智能体低代码开发平台,支持一键导出源码、多种模型API和本地模型部署
|
10月前
|
XML 搜索推荐 Android开发
Android改变进度条控件progressbar的样式(根据源码修改)
本文介绍了如何基于Android源码自定义ProgressBar样式。首先分析了系统源码中ProgressBar样式的定义,发现其依赖一张旋转图片实现动画效果。接着分两步指导开发者实现自定义:1) 模仿源码创建一个旋转动画XML文件(放置在drawable文件夹),修改图片为自定义样式;2) 在UI控件中通过`indeterminateDrawable`属性应用该动画。最终实现简单且个性化的ProgressBar效果,附带效果图展示。
621 2
|
11月前
|
NoSQL 应用服务中间件 PHP
布谷一对一直播源码android版环境配置流程及功能明细
部署需基于 CentOS 7.9 系统,硬盘不低于 40G,使用宝塔面板安装环境,包括 PHP 7.3(含 Redis、Fileinfo 扩展)、Nginx、MySQL 5.6、Redis 和最新 Composer。Swoole 扩展需按步骤配置。2021.08.05 后部署需将站点目录设为 public 并用 ThinkPHP 伪静态。开发环境建议 Windows 操作系统与最新 Android Studio,基础配置涉及 APP 名称修改、接口域名更换、包名调整及第三方登录分享(如 QQ、微信)的配置,同时需完成阿里云与腾讯云相关设置。
|
存储 API 文件存储
单页图床HTML源码+本地API接口图床系统源码
图床系统是一种用于存储和管理图片文件的在线服务。它允许用户上传图片文件,并生成相应的图片链接,从而方便用户在网页、社交媒体或其他平台上分享图片。
543 2
单页图床HTML源码+本地API接口图床系统源码
|
存储 数据可视化 JavaScript
可视化集成API接口请求+变量绑定+源码输出
可视化集成API接口请求+变量绑定+源码输出
431 4
|
开发工具 uml git
AOSP源码下载方法,解决repo sync错误:android-13.0.0_r82
本文分享了下载AOSP源码的方法,包括如何使用repo工具和处理常见的repo sync错误,以及配置Python环境以确保顺利同步特定版本的AOSP代码。
2918 0
AOSP源码下载方法,解决repo sync错误:android-13.0.0_r82