赞
踩
如何在一个应用程序中,启动另外一个应用程序?最近正有这样的需求,也踩了一个小坑。本节介绍使用Activity中"android:exported"属性来实现这种访问。
在程序清单AndroidMenifest.xml文件中,可以设置这个属性。
Android中的Activity中"android:exported"属性设置为true,意味着允许让外部组件启动这个Activity;反之,则不允许让外部组件启动这个Activity;
如果设置了false,又在外部试图启动这个Activity,则会发生程序崩溃,报异常,例如:
java.lang.SecurityException: Permission Denial: starting Intent
我要实现的功能是在App1中启动App2。在App1中,使用startActivity启动App2的activity,从而实现需求。只要将exported 设置为false,就入坑了。主要代码如下:
在app1中,
- //start other app
- Intent intent = mContext.getPackageManager().getLaunchIntentForPackage("com.test.app2");
- if (intent != null) {
- intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
- mContext.startActivity(intent);
- }
这就会启动App2中的入口Activity。
在App2中,AndroidManifest.xml文件,
- <application android:icon="@drawable/icon"
- android:allowBackup="false"
- android:name=".MyApp2"
- android:label="@string/app_name"
- android:theme="@android:style/Theme.Light">
- <activity android:name="com.test.app2.activity.MainActivity"
-
- android:exported="true" //如果为false,就会发生异常
- android:finishOnTaskLaunch="false"
- android:launchMode="singleInstance"
-
- >
-
- </activity>
- ...
- />
如果为exported 设置为false,就会发生异常,恭喜你,成功入坑。报错信息如下:
03-09 19:53:10.077 20340-20340/com.test.app1 D/AndroidRuntime: Shutting down VM
--------- beginning of crash
03-09 19:53:10.078 20340-20340/com.test.app1 E/AndroidRuntime: FATAL EXCEPTION: main
Process: com.test.app1, PID: 20340
java.lang.SecurityException: Permission Denial: starting Intent { act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] flg=0x10000000 pkg=com.test.app2 cmp=com.test.app2/com.test.app2.activity.MainActivity } from ProcessRecord{3d6aa2d 20340:com.test.app1/u0a83} (pid=20340, uid=10083) not exported from uid 10117
at android.os.Parcel.readException(Parcel.java:1620)
at android.os.Parcel.readException(Parcel.java:1573)
at android.app.ActivityManagerProxy.startActivity(ActivityManagerNative.java:2658)
at android.app.Instrumentation.execStartActivity(Instrumentation.java:1507)
at android.app.Activity.startActivityForResult(Activity.java:3930)
at android.app.Activity.startActivityForResult(Activity.java:3890)
at android.app.Activity.startActivity(Activity.java:4213)
at android.app.Activity.startActivity(Activity.java:4181)
at com.test.app1.ui.DemoMainActivity.onClick(DemoMainActivity.java:70)
at android.view.View.performClick(View.java:5204)
at android.view.View$PerformClick.run(View.java:21153)
at android.os.Handler.handleCallback(Handler.java:739)
at android.os.Handler.dispatchMessage(Handler.java:95)
at android.os.Looper.loop(Looper.java:148)
at android.app.ActivityThread.main(ActivityThread.java:5417)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:726)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:616)
当然,根据提示,也可以看出来,是“not exported”,所以,填坑也很容易:
android:exported="true"
追根溯源,我们来看一下源码。搜索“not exported from uid” ,可以看到,有好几处都能搜到,而且代码文件看名字就能知道,是Android四大组件相关代码,如图:
结合我们的错误信息提示,定位到错误提示的代码在ActivityStack.java的
startActivityLocked方法中:
- final int startAnyPerm = mService.checkPermission(
- START_ANY_ACTIVITY, callingPid, callingUid);
- final int componentPerm = mService.checkComponentPermission(aInfo.permission, callingPid,
- callingUid, aInfo.applicationInfo.uid, aInfo.exported);
- if (startAnyPerm != PERMISSION_GRANTED && componentPerm != PERMISSION_GRANTED) {
-
- String msg;
- if (!aInfo.exported) {
- msg = "Permission Denial: starting " + intent.toString()
- + " from " + callerApp + " (pid=" + callingPid
- + ", uid=" + callingUid + ")"
- + " not exported from uid " + aInfo.applicationInfo.uid;
- } else {
- msg = "Permission Denial: starting " + intent.toString()
- + " from " + callerApp + " (pid=" + callingPid
- + ", uid=" + callingUid + ")"
- + " requires " + aInfo.permission;
- }
- Slog.w(TAG, msg);
- throw new SecurityException(msg);
- }
哦,原来是在startActivityLocked中抛出的异常。还记得activity的启动流程么,一系列的startActivity相关函数的调用。
通过调用mService.checkPermission 和 mService.checkComponentPermission 进行获取权限。再根据返回值来判断是否有权限,如果没有权限,就会
throw new SecurityException(msg);
这个msg,就是上面我们提到的错误信息。
那么,mService是谁呢?答案就是大名鼎鼎的AMS(ActivityManagerService)的实例。
在check权限的时候,会进行进程id,用户id,是否是同一个应用,是否是系统用户以及exported是否为true......等等条件的判断。
我们直接来看到底是谁去check了exported的。答案是:ActivityManager。调用链:
- ActivityStack.startActivityLocked -->
- ActivityManagerService.checkComponentPermission -->
- ActivityManager.checkComponentPermission -->
ActivityManager.checkComponentPermission的代码:
- /** @hide */
- public static int checkComponentPermission(String permission, int uid,
- int owningUid, boolean exported) {
- // Root, system server get to do everything.
- if (uid == 0 || uid == Process.SYSTEM_UID) {
- return PackageManager.PERMISSION_GRANTED;
- }
- // Isolated processes don't get any permissions.
- if (UserId.isIsolated(uid)) {
- return PackageManager.PERMISSION_DENIED;
- }
- // If there is a uid that owns whatever is being accessed, it has
- // blanket access to it regardless of the permissions it requires.
- if (owningUid >= 0 && UserId.isSameApp(uid, owningUid)) {
- return PackageManager.PERMISSION_GRANTED;
- }
- // If the target is not exported, then nobody else can get to it.
- if (!exported) {
- Slog.w(TAG, "Permission denied: checkComponentPermission() owningUid=" + owningUid);
- return PackageManager.PERMISSION_DENIED;
- }
- if (permission == null) {
- return PackageManager.PERMISSION_GRANTED;
- }
- try {
- return AppGlobals.getPackageManager()
- .checkUidPermission(permission, uid);
- } catch (RemoteException e) {
- // Should never happen, but if it does... deny!
- Slog.e(TAG, "PackageManager is dead?!?", e);
- }
- return PackageManager.PERMISSION_DENIED;
- }
可以看到,
// If the target is not exported, then nobody else can get to it.
if (!exported) {
Slog.w(TAG, "Permission denied: checkComponentPermission() owningUid=" + owningUid);
return PackageManager.PERMISSION_DENIED;
}
在这里进行了exported的判断,如果exported为false,就会
return PackageManager.PERMISSION_DENIED;
终于,知道了这个异常是怎么被throw出来的了。
android:exported="true" 和 android:exported="false",弄明白了么?
---------------------
作者:liranke
来源:CSDN
原文:https://blog.csdn.net/liranke/article/details/123437721
版权声明:本文为作者原创文章,转载请附上博文链接!
内容解析By:CSDN,CNBLOG博客文章一键转载插件
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。