如何在一个应用程序中,启动另外一个应用程序?最近正有这样的需求,也踩了一个小坑。本节介绍使用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文件,
<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",弄明白了么?
|