> 文档中心 > Android开发-Activity中“android:exported“属性的作用,以及“Permission Denial: starting Intent“错误解决

Android开发-Activity中“android:exported“属性的作用,以及“Permission Denial: starting Intent“错误解决

如何在一个应用程序中,启动另外一个应用程序?最近正有这样的需求,也踩了一个小坑。本节介绍使用Activity中"android:exported"属性来实现这种访问。

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文件,

  .../>

 如果为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",弄明白了么?

新能源吧