本文同步更新于旺仔的個(gè)人博客,訪問(wèn)可能有點(diǎn)慢,多刷新幾次。
緣由
這幾天想做一個(gè)點(diǎn)擊跳轉(zhuǎn)到TIM的掃一掃的Activity的功能,代碼是這樣的,就是普通的跳轉(zhuǎn)
Intent intent = new Intent();
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
ComponentName component = new ComponentName("com.tencent.tim", "com.tencent.biz.qrcode.activity.ScannerActivity");
intent.setComponent(component);
intent.setAction("android.intent.action.VIEW");
try {
startActivity(intent);
} catch (Exception e) {
e.printStackTrace();
}
為什么我后面要加try/catch呢,因?yàn)椴患拥脑挄?huì)報(bào)異常,然后閃退,報(bào)的異常內(nèi)容如下:
java.lang.SecurityException: Permission Denial:
starting Intent { act=android.intent.action.VIEW flg=0x10000000
cmp=com.tencent.tim/com.tencent.biz.qrcode.activity.ScannerActivity }
from ProcessRecord{e0031ac 25553:top.jowanxu.xposedtest/u0a175}
(pid=25553, uid=10175) not exported from uid 10151
Exported屬性
wtf?沒(méi)有權(quán)限?然后呢,百度了下,發(fā)現(xiàn)是Activity的屬性exported的值為false,然后別的app就打不開這個(gè)Activity了,如果要打開的話,就必須和這個(gè)Activity在同一Application下或者uid相同才行。
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
...
android:sharedUserId="com.example.categorytest">
...
</manifest>
同一不能打開的還有在沒(méi)有設(shè)置exported屬性的時(shí)候,也沒(méi)有設(shè)置intent-filter屬性的話,也是打不開這個(gè)Activity的。
<activity
android:name=".ScannerActivity"
android:label="@string/app_name"
android:exported="false"/> <!-- 設(shè)置了exported屬性值為false -->
<!-- 如果Activity里面至少有一個(gè)filter的話,意味著這個(gè)Activity可以被其它應(yīng)用從外部喚起,這個(gè)時(shí)候它的默認(rèn)值是true -->
<activity
android:name=".SecondActivity"
android:label="@string/app_name">
<intent-filter>
</intent-filter>
</activity>
然后我們Analyze APK一下我們的TIM的apk,打開它的AndroidManifest.xml文件,然后搜索ScannerActivity,發(fā)現(xiàn)ScannerActivity里面的exported的值果然是false。

既然如此的話,那就看一下當(dāng)Activity的exported屬性值為false的時(shí)候,為什么不能調(diào)起這個(gè)Activity,而且還會(huì)報(bào)異常。
從startActivity的源碼看起,既然我們一開始的問(wèn)題是Permission Denial,那么我們查看的關(guān)鍵詞就必須包含permission,這樣看起源碼來(lái)就方便許多。
源碼
Activity類
首先是Activity里面的startActivity,發(fā)現(xiàn)他是調(diào)用自己的另一個(gè)同名不同參數(shù)的方法。
@Override
public void startActivity(Intent intent) {
this.startActivity(intent, null);
}
跳到startActivity(Intent intent, @Nullable Bundle options)方法后,因?yàn)?code>options參數(shù)為null,所以是調(diào)用startActivityForResult(@RequiresPermission Intent intent, int requestCode)這個(gè)方法。
@Override
public void startActivity(Intent intent, @Nullable Bundle options) {
if (options != null) {
startActivityForResult(intent, -1, options);
} else {
// Note we want to go through this call for compatibility with
// applications that may have overridden the method.
startActivityForResult(intent, -1);
}
}
跳到startActivityForResult方法后,發(fā)現(xiàn)又是調(diào)用同名不同參數(shù)的方法startActivityForResult(@RequiresPermission Intent intent, int requestCode, @Nullable Bundle options)。
public void startActivityForResult(@RequiresPermission Intent intent, int requestCode) {
// requestCode = -1
startActivityForResult(intent, requestCode, null);
}
接著看mParent == null條件里面的代碼,關(guān)鍵詞startActivity,然后找到execStartActivity(),是Instrumentation類里面的方法。
public void startActivityForResult(@RequiresPermission Intent intent, int requestCode,
@Nullable Bundle options) {
if (mParent == null) {
options = transferSpringboardActivityOptions(options);
Instrumentation.ActivityResult ar =
mInstrumentation.execStartActivity(
this, mMainThread.getApplicationThread(), mToken, this,
intent, requestCode, options);
if (ar != null) {
mMainThread.sendActivityResult(
mToken, mEmbeddedID, requestCode, ar.getResultCode(),
ar.getResultData());
}
....
} else {
....
}
}
Instrumentation類
跳轉(zhuǎn)到execStartActivity方法里,同樣關(guān)鍵詞startActivity,可以看到是ActivityManagerNative.getDefault().startActivity()方法和checkStartActivityResult()方法,我們先來(lái)看checkStartActivityResult()方法。
public ActivityResult execStartActivity(
Context who, IBinder contextThread, IBinder token, String target,
Intent intent, int requestCode, Bundle options) {
IApplicationThread whoThread = (IApplicationThread) contextThread;
....
try {
intent.migrateExtraStreamToClipData();
intent.prepareToLeaveProcess(who);
int result = ActivityManagerNative.getDefault()
.startActivity(whoThread, who.getBasePackageName(), intent,
intent.resolveTypeIfNeeded(who.getContentResolver()),
token, target, requestCode, 0, null, options);
checkStartActivityResult(result, intent);
} catch (RemoteException e) {
throw new RuntimeException("Failure from system", e);
}
return null;
}
點(diǎn)進(jìn)去之后發(fā)現(xiàn),這里面就是我們經(jīng)常startActivity之后,在類沒(méi)找到或者沒(méi)有在AndroidManifest中注冊(cè)等等之后會(huì)報(bào)出的異常的判斷方法。
public static void checkStartActivityResult(int res, Object intent) {
if (res >= ActivityManager.START_SUCCESS) {
return;
}
switch (res) {
case ActivityManager.START_INTENT_NOT_RESOLVED:
case ActivityManager.START_CLASS_NOT_FOUND:
if (intent instanceof Intent && ((Intent)intent).getComponent() != null)
throw new ActivityNotFoundException(
"Unable to find explicit activity class "
+ ((Intent)intent).getComponent().toShortString()
+ "; have you declared this activity in your AndroidManifest.xml?");
throw new ActivityNotFoundException(
"No Activity found to handle " + intent);
case ActivityManager.START_PERMISSION_DENIED:
throw new SecurityException("Not allowed to start activity "
+ intent);
case ActivityManager.START_FORWARD_AND_REQUEST_CONFLICT:
throw new AndroidRuntimeException(
"FORWARD_RESULT_FLAG used while also requesting a result");
case ActivityManager.START_NOT_ACTIVITY:
throw new IllegalArgumentException(
"PendingIntent is not an activity");
case ActivityManager.START_NOT_VOICE_COMPATIBLE:
throw new SecurityException(
"Starting under voice control not allowed for: " + intent);
case ActivityManager.START_VOICE_NOT_ACTIVE_SESSION:
throw new IllegalStateException(
"Session calling startVoiceActivity does not match active session");
case ActivityManager.START_VOICE_HIDDEN_SESSION:
throw new IllegalStateException(
"Cannot start voice activity on a hidden session");
case ActivityManager.START_CANCELED:
throw new AndroidRuntimeException("Activity could not be started for "
+ intent);
default:
throw new AndroidRuntimeException("Unknown error code "
+ res + " when starting " + intent);
}
}
IActivityManager接口
點(diǎn)擊startActivity()之后,跳轉(zhuǎn)到IActivityManager接口里面來(lái)了,這個(gè)接口就是管理Activity的,然后我們從ActivityManagerNative.getDefault().startActivity()看出調(diào)用者是在ActivityManangerNative類里面。
/**
* System private API for talking with the activity manager service. This
* provides calls from the application back to the activity manager.
*
* {@hide}
*/
public interface IActivityManager extends IInterface {
public int startActivity(IApplicationThread caller, String callingPackage, Intent intent,
String resolvedType, IBinder resultTo, String resultWho, int requestCode, int flags,
ProfilerInfo profilerInfo, Bundle options) throws RemoteException;
....
}
ActivityManagerNative類
這時(shí)候到了ActivityManagerNative類里面,實(shí)現(xiàn)了IActivityManager接口,同時(shí)ActivityManagerNative還是一個(gè)抽象類,說(shuō)明ActivityManagerNative.getDefault().startActivity()調(diào)用startActivity調(diào)用的對(duì)象是該類的子類。
public abstract class ActivityManagerNative extends Binder implements IActivityManager
{
....
static public IActivityManager getDefault() {
return gDefault.get();
}
....
}
然后我們通過(guò)ctrl + shift + F打開搜索,關(guān)鍵詞是extends ActivityManagerNative,scope選擇custom,然后Find。

然后就找到了ActivityManagerService類

ActivityManagerService類
ActivityManagerService類類是final類型,不能被繼承,然后我們來(lái)看一下他的startActivity方法。
public final class ActivityManagerService extends ActivityManagerNative
implements Watchdog.Monitor, BatteryStatsImpl.BatteryCallback {
....
@Override
public final int startActivity(IApplicationThread caller, String callingPackage,
Intent intent, String resolvedType, IBinder resultTo, String resultWho, int requestCode,
int startFlags, ProfilerInfo profilerInfo, Bundle bOptions) {
return startActivityAsUser(caller, callingPackage, intent, resolvedType, resultTo,
resultWho, requestCode, startFlags, profilerInfo, bOptions,
UserHandle.getCallingUserId());
}
}
startActivity方法是調(diào)用了startActivityAsUser方法,我們繼續(xù)走下去,來(lái)到了startActivityAsUser方法后發(fā)現(xiàn),是調(diào)用了ActivityStarter類里面的startActivityMayWait方法。
@Override
public final int startActivityAsUser(IApplicationThread caller, String callingPackage,
Intent intent, String resolvedType, IBinder resultTo, String resultWho, int requestCode,
int startFlags, ProfilerInfo profilerInfo, Bundle bOptions, int userId) {
enforceNotIsolatedCaller("startActivity");
userId = mUserController.handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(),
userId, false, ALLOW_FULL_ONLY, "startActivity", null);
// TODO: Switch to user app stacks here.
return mActivityStarter.startActivityMayWait(caller, -1, callingPackage, intent,
resolvedType, null, null, resultTo, resultWho, requestCode, startFlags,
profilerInfo, null, null, bOptions, false, userId, null, null);
}
ActivityStarter類
startActivityMayWait方法內(nèi)容很多,挑重點(diǎn)看,關(guān)鍵詞startActivity,同時(shí)看permission相關(guān)的有沒(méi)有,然后我們找到了startActivityLocked方法。
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, IActivityManager.WaitResult outResult, Configuration config,
Bundle bOptions, boolean ignoreTargetSecurity, int userId,
IActivityContainer iContainer, TaskRecord inTask) {
....
ResolveInfo rInfo = mSupervisor.resolveIntent(intent, resolvedType, userId);
....
ActivityInfo aInfo = mSupervisor.resolveActivity(intent, rInfo, startFlags, profilerInfo);
....
final ActivityRecord[] outRecord = new ActivityRecord[1];
int res = startActivityLocked(caller, intent, ephemeralIntent, resolvedType,
aInfo, rInfo, voiceSession, voiceInteractor,
resultTo, resultWho, requestCode, callingPid,
callingUid, callingPackage, realCallingPid, realCallingUid, startFlags,
options, ignoreTargetSecurity, componentSpecified, outRecord, container,
inTask);
....
return res;
}
}
繼續(xù),走到startActivityLocked方法里面,內(nèi)容特別多,同樣挑關(guān)鍵詞startActivity和permission看,結(jié)果我們找到了mSupervisor.checkStartAnyActivityPermission方法和startActivityUnchecked方法,既然我們的目的是找跟permission相關(guān)的,那么我們就只看checkStartAnyActivityPermission方法內(nèi)容吧。
final int startActivityLocked(IApplicationThread caller, Intent intent, Intent ephemeralIntent,
String resolvedType, ActivityInfo aInfo, ResolveInfo rInfo,
IVoiceInteractionSession voiceSession, IVoiceInteractor voiceInteractor,
IBinder resultTo, String resultWho, int requestCode, int callingPid, int callingUid,
String callingPackage, int realCallingPid, int realCallingUid, int startFlags,
ActivityOptions options, boolean ignoreTargetSecurity, boolean componentSpecified,
ActivityRecord[] outActivity, ActivityStackSupervisor.ActivityContainer container,
TaskRecord inTask) {
int err = ActivityManager.START_SUCCESS;
....
final int userId = aInfo != null ? UserHandle.getUserId(aInfo.applicationInfo.uid) : 0;
....
boolean abort = !mSupervisor.checkStartAnyActivityPermission(intent, aInfo, resultWho,
requestCode, callingPid, callingUid, callingPackage, ignoreTargetSecurity, callerApp,
resultRecord, resultStack, options);
abort |= !mService.mIntentFirewall.checkStartActivity(intent, callingUid,
callingPid, resolvedType, aInfo.applicationInfo);
....
try {
mService.mWindowManager.deferSurfaceLayout();
err = startActivityUnchecked(r, sourceRecord, voiceSession, voiceInteractor, startFlags,
true, options, inTask);
} finally {
mService.mWindowManager.continueSurfaceLayout();
}
postStartActivityUncheckedProcessing(r, err, stack.mStackId, mSourceRecord, mTargetStack);
return err;
}
ActivityStackSupervisor類
根據(jù)mSupervisor.checkStartAnyActivityPermission我們來(lái)到了ActivityStackSupervisor類的checkStartAnyActivityPermission方法,方法內(nèi)容不長(zhǎng),直接往下看
boolean checkStartAnyActivityPermission(Intent intent, ActivityInfo aInfo,
String resultWho, int requestCode, int callingPid, int callingUid,
String callingPackage, boolean ignoreTargetSecurity, ProcessRecord callerApp,
ActivityRecord resultRecord, ActivityStack resultStack, ActivityOptions options) {
// 判斷權(quán)限
final int startAnyPerm = mService.checkPermission(START_ANY_ACTIVITY, callingPid,
callingUid);
// 如果startAnyPerm的值為0,也就是PERMISSION_GRANTED的話,直接返回true
if (startAnyPerm == PERMISSION_GRANTED) {
return true;
}
final int componentRestriction = getComponentRestrictionForCallingPackage(
aInfo, callingPackage, callingPid, callingUid, ignoreTargetSecurity);
final int actionRestriction = getActionRestrictionForCallingPackage(
intent.getAction(), callingPackage, callingPid, callingUid);
if (componentRestriction == ACTIVITY_RESTRICTION_PERMISSION
|| actionRestriction == ACTIVITY_RESTRICTION_PERMISSION) {
if (resultRecord != null) {
resultStack.sendActivityResultLocked(-1,
resultRecord, resultWho, requestCode,
Activity.RESULT_CANCELED, null);
}
final String msg;
// 重點(diǎn)就是這里了
if (actionRestriction == ACTIVITY_RESTRICTION_PERMISSION) {
msg = "Permission Denial: starting " + intent.toString()
+ " from " + callerApp + " (pid=" + callingPid
+ ", uid=" + callingUid + ")" + " with revoked permission "
+ ACTION_TO_RUNTIME_PERMISSION.get(intent.getAction());
} else 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);
}
if (actionRestriction == ACTIVITY_RESTRICTION_APPOP) {
final String message = "Appop Denial: starting " + intent.toString()
+ " from " + callerApp + " (pid=" + callingPid
+ ", uid=" + callingUid + ")"
+ " requires " + AppOpsManager.permissionToOp(
ACTION_TO_RUNTIME_PERMISSION.get(intent.getAction()));
Slog.w(TAG, message);
return false;
} else if (componentRestriction == ACTIVITY_RESTRICTION_APPOP) {
final String message = "Appop Denial: starting " + intent.toString()
+ " from " + callerApp + " (pid=" + callingPid
+ ", uid=" + callingUid + ")"
+ " requires appop " + AppOpsManager.permissionToOp(aInfo.permission);
Slog.w(TAG, message);
return false;
}
if (options != null && options.getLaunchTaskId() != -1) {
final int startInTaskPerm = mService.checkPermission(START_TASKS_FROM_RECENTS,
callingPid, callingUid);
if (startInTaskPerm != PERMISSION_GRANTED) {
final String msg = "Permission Denial: starting " + intent.toString()
+ " from " + callerApp + " (pid=" + callingPid
+ ", uid=" + callingUid + ") with launchTaskId="
+ options.getLaunchTaskId();
Slog.w(TAG, msg);
throw new SecurityException(msg);
}
}
return true;
}
找了那么久,終于找到了,開頭提出的問(wèn)題,就是下面這段代碼里面的!aInfo.exported出現(xiàn)的。
if (actionRestriction == ACTIVITY_RESTRICTION_PERMISSION) {
msg = "Permission Denial: starting " + intent.toString()
+ " from " + callerApp + " (pid=" + callingPid
+ ", uid=" + callingUid + ")" + " with revoked permission "
+ ACTION_TO_RUNTIME_PERMISSION.get(intent.getAction());
} else 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);
aInfo的類型是ActivityInfo,里面有個(gè)exported的屬性,就是我們?cè)贏ndroidManifest.xml里面設(shè)置的值。
/**
* Information you can retrieve about a particular application
* activity or receiver. This corresponds to information collected
* from the AndroidManifest.xml's <activity> and
* <receiver> tags.
*/
public class ActivityInfo extends ComponentInfo
implements Parcelable {
}
總結(jié)
翻了那么多的源碼,看到這里我們的疑惑就解除了,這里報(bào)異常是還沒(méi)進(jìn)行到調(diào)起要跳轉(zhuǎn)的Activity的時(shí)候就已經(jīng)報(bào)SecurityException異常了,也就是在checkStartAnyActivityPermission方法里面報(bào)異常,沒(méi)有再往下面走startActivityUnchecked這里面啟動(dòng)Activity的代碼。
所以,在exported屬性為false的時(shí)候,別人是調(diào)用不了這個(gè)Activity的,那么我的一開始的想法是實(shí)現(xiàn)不了的,那就只能通過(guò)命令行來(lái)調(diào)起Activity了,當(dāng)然這操作是需要root的。