结合源码谈谈Activity的exported属性

来源:互联网 发布:2017全球互联网数据 编辑:程序博客网 时间:2024/05/23 16:41

Activity的exported属性在单个App可能用得比较少,但对于对外接口的Activity或公司内部多个应用间接口调用的设计会有比较大的影响。本文基于android 6.0.1的源码谈谈Activity的exported属性,内容分为2部分:

  • 系统如何设定exported值
  • 如何合理设置exported

系统如何设定exported值

我们知道在AndroidManifest.xml文件中,四大组件都有android:exported属性,是个boolean值,可以为true或false
如:

<activity    android:name=".MainActivity"    android:label="@string/app_name"     android:exported="false">           

我们这里只讲activity相关,但对其他组件也有参考意义,大家可以去研究。
android:exported是个可选属性,它不像android:name是必须设置的。那么,Android系统是如何设置exported属性的值呢?
通过阅读系统源码,我们知道AndroidManifest.xml是在

frameword\base\core\java\android\content\pm\PackageParse.java

中解析,找到其中解析activity项的源码,在parseActivity函数:

private Activity parseActivity(Package owner, Resources res,            XmlPullParser parser, AttributeSet attrs, int flags, String[] outError,            boolean receiver, boolean hardwareAccelerated)        ...        boolean setExported = sa.hasValue(R.styleable.AndroidManifestActivity_exported);        if (setExported) {            a.info.exported = sa.getBoolean(R.styleable.AndroidManifestActivity_exported, false);        }        ...        if (!setExported) {            a.info.exported = a.intents.size() > 0;        }        return a;    }

这里,我们只保留了与exported属性解析相关的源码。从源码中,我们可以得出:

  • 如果显式exported属性,不管这个activity有没有设置intent-filter,那么exported的值就是显式设置的值
  • 如果没有设置exported属性,那么exported属性取决于这个activity是否有intent-filter
    • 如有intent-filter,那么exported的值就是true
    • 如没有intent-filter,那么exported的值就是false

如何合理设置exported

了解了系统如何设定exported值之后,我们接下来谈谈在开发中如何更合理设置exported。
我们知道代码离不开用户场景,那么我把一个Activity被调用的场景分为3种:封闭式、半封闭式和开放式

  • 封闭式:被调用的Activity与调用的Context必须在同一个App,其他任何App不能调用
    这种是我们最常见的Activity,有2种情况:

    1. 没有intent-filter情况,可以不设置exported或者设置exported为false
<activity            android:name=".SecondActivity"            android:label="@string/app_name" />或            <activity            android:name=".SecondActivity"            android:label="@string/app_name"             android:exported="false"/>
     2. 有intent-filter情况,必须设置exported为false
<activity            android:name=".SecondActivity"            android:label="@string/app_name"             android:exported="false">            <intent-filter>            ...
  • 半封闭式:被调用的Activity只能被部分其他App调用,如同一个公司的2个App之间
    这种场景下,除了满足封闭式设置外,还必须把调用App和被调用App设置相同的uid,即在2个App的AndroidManifest.xml添加相同的android:sharedUserId,如
<manifest xmlns:android="http://schemas.android.com/apk/res/android"    ...    android:sharedUserId="com.example.categorytest">
    为什么添加相同的android:sharedUserId就可以呢?还是看系统AMS启动Activity的源码,在

frameword\base\core\java\com\android\server\am\ActivityStackSupervisor.java

final int startActivityLocked(IApplicationThread caller,            Intent intent, String resolvedType, ActivityInfo aInfo,            IVoiceInteractionSession voiceSession, IVoiceInteractor voiceInteractor,            IBinder resultTo, String resultWho, int requestCode,            int callingPid, int callingUid, String callingPackage,            int realCallingPid, int realCallingUid, int startFlags, Bundle options,            boolean ignoreTargetSecurity, boolean componentSpecified, ActivityRecord[] outActivity,            ActivityContainer container, TaskRecord inTask) {            ...    final int startAnyPerm = mService.checkPermission(                    START_ANY_ACTIVITY, callingPid, callingUid);            if (startAnyPerm != PERMISSION_GRANTED) {                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);                    }                    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;                    }...}                

其中就调用mService.checkPermission进行鉴权,mService就是

framework\base\core\java\com\android\server\am\ActivityManagerService.java

checkPermission实际上又调用到

framework\base\core\java\android\app\ActivityManager.java的checkComponentPermission

/** @hide */    public static int checkComponentPermission(String permission, int uid,            int owningUid, boolean exported) {        // Root, system server get to do everything.        final int appId = UserHandle.getAppId(uid);        if (appId == Process.ROOT_UID || appId == Process.SYSTEM_UID) {            return PackageManager.PERMISSION_GRANTED;        }        // Isolated processes don't get any permissions.        if (UserHandle.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 && UserHandle.isSameApp(uid, owningUid)) {            return PackageManager.PERMISSION_GRANTED;        }        // If the target is not exported, then nobody else can get to it.        if (!exported) {            /*            RuntimeException here = new RuntimeException("here");            here.fillInStackTrace();            Slog.w(TAG, "Permission denied: checkComponentPermission() owningUid=" + owningUid,                    here);            */            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;    }

从代码中我们知道,设置相同的sharedUserId之后,UserHandle.isSameApp(uid, owningUid)就会返回true,因此尽管我们把exported设为false,也不会抛出Permission Denial的异常。

  • 开放式:可以被任何App调用
    这种场景主要是对外接口,如微信、微博的分享接口。大多数情况下,这样的Activity都会有intent-filter,因此也没必要显式地把exported设为true,不设是可以的,当然不能设为false。但如果你没有intent-filter,那就必须显式地把exported设为true。
    当然,对于三方app接口的intent-filter设置还有一些要求,如在隐式intent调用必须添加
android.intent.category.DEFAULT

的category。这个我会在新的文章中介绍。

0 0
原创粉丝点击