13.源码阅读(启动一个没有注册的Activity为何会抛出异常-have you declared this activity in yo...
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的时候再将这个未注册的替换过去,达到偷梁换柱的目的
具体实现见下一篇博客
低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
- 上一篇
Android绘制(一):来用shape绘出想要的图形吧!
目录 前言 shape绘制 矩形 椭圆 线 环 用shape绘制SeekBar 最后 前言 在没有UI设计师的时候, 或者是想简单看下效果的时候, 用shape进行快速绘制是极好的! 官方文档. shape绘制 一共有四种shape: rectangle, oval, line, ring. 矩形 我们一个一个来看, 首先是矩形: 矩形例子 <?xml version="1.0" encoding="utf-8"?> <shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle"> <!-- 尺寸 --> <size android:width="160dp" android:height="80dp" /> <!-- 颜色 --> <!--<solid android:color="@color/colorPrimary" />--> <!-- 内间距 --> <p...
- 下一篇
[安卓] 17、一个简单的例子学安卓侧滑设计——用开源slidingmenu
效果如下: 下面是工程结构: 整个工程包括android-v7、SlidingMenu-lib和主工程SlidingMenuTest部分 其中前两个作为lib,后一个为主工程 主工程包含两个lib工程方法为:(易错点) 选择主工程属性 而主工程包括: 逻辑部分:(SRC)其中MainActivity.java是主入口,在主入口内调用部分函数可加载MainFragment.java所对应的Fragment 而CircleImageView.java主要负责使头像呈圆形用的 显示部分:(layout)activity_main.xml对应主页面,layout_actionbar.xml对应actionbar(actionbar的形式可以通过这个调节), layout_menu.xml是侧栏,item_menu.xml是侧栏中的列表的item(上一节中讲了自定义列表和这个类似) 这里MainActivity协调整体逻辑: 该avtivity中代码比较简洁,一看便知道怎么用fragment的了——17、18 怎么设置侧滑——15 怎么设置bar——16...
相关文章
文章评论
共有0条评论来说两句吧...
文章二维码
点击排行
推荐阅读
最新文章
- 设置Eclipse缩进为4个空格,增强代码规范
- SpringBoot2配置默认Tomcat设置,开启更多高级功能
- CentOS7设置SWAP分区,小内存服务器的救世主
- Docker安装Oracle12C,快速搭建Oracle学习环境
- CentOS7编译安装Cmake3.16.3,解决mysql等软件编译问题
- CentOS7安装Docker,走上虚拟化容器引擎之路
- Mario游戏-低调大师作品
- Docker使用Oracle官方镜像安装(12C,18C,19C)
- Jdk安装(Linux,MacOS,Windows),包含三大操作系统的最全安装
- SpringBoot2更换Tomcat为Jetty,小型站点的福音