探究Exported属性对startActivity的影响

本文同步更新于旺仔的个人博客,访问可能有点慢,多刷新几次。

缘由

这几天想做一个点击跳转到TIM的扫一扫的Activity的功能,代码是这样的,就是普通的跳转

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呢,因为不加的话会报异常,然后闪退,报的异常内容如下:

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?没有权限?然后呢,百度了下,发现是Activity的属性exported的值为false,然后别的app就打不开这个Activity了,如果要打开的话,就必须和这个Activity在同一Application下或者uid相同才行。

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    ...
    android:sharedUserId="com.example.categorytest">
    ...
</manifest>

同一不能打开的还有在没有设置exported属性的时候,也没有设置intent-filter属性的话,也是打不开这个Activity的。

<activity
android:name=".ScannerActivity"
android:label="@string/app_name" 
android:exported="false"/> <!-- 设置了exported属性值为false -->

<!-- 如果Activity里面至少有一个filter的话,意味着这个Activity可以被其它应用从外部唤起,这个时候它的默认值是true -->
<activity
android:name=".SecondActivity"
android:label="@string/app_name">
    <intent-filter>
    </intent-filter>
</activity>

然后我们Analyze APK一下我们的TIM的apk,打开它的AndroidManifest.xml文件,然后搜索ScannerActivity,发现ScannerActivity里面的exported的值果然是false。

既然如此的话,那就看一下当Activity的exported属性值为false的时候,为什么不能调起这个Activity,而且还会报异常。
startActivity的源码看起,既然我们一开始的问题是Permission Denial,那么我们查看的关键词就必须包含permission,这样看起源码来就方便许多。

源码

Activity类

首先是Activity里面的startActivity,发现他是调用自己的另一个同名不同参数的方法。

@Override
public void startActivity(Intent intent) {
   this.startActivity(intent, null);
}

跳到startActivity(Intent intent, @Nullable Bundle options)方法后,因为options参数为null,所以是调用startActivityForResult(@RequiresPermission Intent intent, int requestCode)这个方法。

@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方法后,发现又是调用同名不同参数的方法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条件里面的代码,关键词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类

跳转到execStartActivity方法里,同样关键词startActivity,可以看到是ActivityManagerNative.getDefault().startActivity()方法和checkStartActivityResult()方法,我们先来看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;
    }

点进去之后发现,这里面就是我们经常startActivity之后,在类没找到或者没有在AndroidManifest中注册等等之后会报出的异常的判断方法。

    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接口

点击startActivity()之后,跳转到IActivityManager接口里面来了,这个接口就是管理Activity的,然后我们从ActivityManagerNative.getDefault().startActivity()看出调用者是在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类

这时候到了ActivityManagerNative类里面,实现了IActivityManager接口,同时ActivityManagerNative还是一个抽象类,说明ActivityManagerNative.getDefault().startActivity()调用startActivity调用的对象是该类的子类。


public abstract class ActivityManagerNative extends Binder implements IActivityManager
{
    ....
    static public IActivityManager getDefault() {
        return gDefault.get();
    }
    ....
}

然后我们通过ctrl + shift + F打开搜索,关键词是extends ActivityManagerNative,scope选择custom,然后Find。

然后就找到了ActivityManagerService

ActivityManagerService类

ActivityManagerService类类是final类型,不能被继承,然后我们来看一下他的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方法是调用了startActivityAsUser方法,我们继续走下去,来到了startActivityAsUser方法后发现,是调用了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方法内容很多,挑重点看,关键词startActivity,同时看permission相关的有没有,然后我们找到了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;
        }
    }

继续,走到startActivityLocked方法里面,内容特别多,同样挑关键词startActivitypermission看,结果我们找到了mSupervisor.checkStartAnyActivityPermission方法和startActivityUnchecked方法,既然我们的目的是找跟permission相关的,那么我们就只看checkStartAnyActivityPermission方法内容吧。

    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类

根据mSupervisor.checkStartAnyActivityPermission我们来到了ActivityStackSupervisor类的checkStartAnyActivityPermission方法,方法内容不长,直接往下看

    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) {
        // 判断权限
        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;
            // 重点就是这里了
            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;
    }

找了那么久,终于找到了,开头提出的问题,就是下面这段代码里面的!aInfo.exported出现的。

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,里面有个exported的属性,就是我们在AndroidManifest.xml里面设置的值。

/**
 * 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 {
}

总结

翻了那么多的源码,看到这里我们的疑惑就解除了,这里报异常是还没进行到调起要跳转的Activity的时候就已经报SecurityException异常了,也就是在checkStartAnyActivityPermission方法里面报异常,没有再往下面走startActivityUnchecked这里面启动Activity的代码。

所以,在exported属性为false的时候,别人是调用不了这个Activity的,那么我的一开始的想法是实现不了的,那就只能通过命令行来调起Activity了,当然这操作是需要root的。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,332评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,508评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,812评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,607评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,728评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,919评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,071评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,802评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,256评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,576评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,712评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,389评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,032评论 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,798评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,026评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,473评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,606评论 2 350