Android 9 (P)非SDK API限制调用开发指南
Android 9 (P)开发适配指南系列博客目录:
Adnroid 9 (P) recovery升级Map of '@/cache/recovery/block.map’failed问题分析指南
Android 9 (P)版本解决VNDK library: XXX’s ABI has EXTENDING CHANGES
Android 9 (P)非SDK API限制调用开发指南
Android 9 (P)适配以太网功能开发指南
Android 9 (P)在user模式下无法使用fastboot烧录怎么破
Android 9 (P)静默安装/卸载App适配终极指南
引言
有过Android开发经验的童鞋应该知道,每一次Android大版本的升级,往往会有大量的APP出现兼容性问题,导致这个情况的主要原因是由于APP的热修复以及依赖Android internal API(内部API),也就是非SDK API。这些API是指标记@hide的类、方法以及字段,它们不属于官方Android SDK的字段与函数(当然这其中也包括一些废旧SDK的使用,这个不是本篇讨论的重点)。
- 非SDK接口指不在官方Android SDK涵盖范围内的 Java 字段和方法。此类接口是 SDK 的内部实现细节,可能随时会被修改,且不对开发者另行通知。
Google希望未来Android大版本升级,APP都能正常运行,而很多APP对内部API的调用通过反射或JNI间接调用的方法来调用,破坏兼容性。 为此Google从Android P开始限制对内部API的使用,继续使用则抛出如下异常。
Google在Android P版本上对隐藏的Java API进行了一定的限制,后续版本会逐步的完善限制。App侧通过反射等方式调用的Java API将会有很多限制,对于Android应用想再搞一些插件化之类的黑科技便是带着脚手铐跳舞,即便能跳但舞姿已不太优雅了。这也是为了Android 生态在未来更好的发展。真是为难了谷歌妈咪为了Android的和谐发展所做的努力。
Android 7.0对Native的NDK的调用限制是手铐,而Android 9.0对Java层SDK的调用限制就是脚铐。脚铐手铐同时上,为了Android碎片化的整理真是操作了心啊。
注意:本文是以Android 9源码为基础来说明Android P对非SDK API调用开发指南。
libcore/ojluni/src/main/java/java/lang/Class.java
art/runtime/native/java_lang_Class.cc
art/runtime/hidden_api.h
art/runtime/runtime.h
art/libdexfile/dex/hidden_api_access_flags.h
art/runtime/hidden_api.cc
art/runtime/art_method-inl.h
frameworks/base/core/java/android/content/pm/ApplicationInfo.java
libcore/libart/src/main/java/dalvik/system/VMRuntime.java
一.实例演示
说一千道一万,首先让我们来一个实例演示一番,这样给大伙有一个直观的感受不是。
1.1 实例代码
这里我们以VMRuntime类来说明,其中有一个本地方法如下:
//VMRuntime.java
/**
* Sets the list of exemptions from hidden API access enforcement.
*
* @param signaturePrefixes
* A list of signature prefixes. Each item in the list is a prefix match on the type
* signature of a blacklisted API. All matching APIs are treated as if they were on
* the whitelist: access permitted, and no logging..
*/
public native void setHiddenApiExemptions(String[] signaturePrefixes);
通过反射调用该方法:
public void useHideFun() {
try {
Log.e("HIDE", "START");
Class<?> VMRuntimeClass = null;
VMRuntimeClass = Class.forName("dalvik.system.VMRuntime");
String[] test = null;
Method setHiddenApiExemptionsMethod = VMRuntimeClass
.getDeclaredMethod("setHiddenApiExemptions", String[].class);
setHiddenApiExemptionsMethod.setAccessible(true);
Log.e("HIDE", "SUCCESS");
} catch (ClassNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (NoSuchMethodException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
1.2 运行实例
我们运行一把,其中明显实体不能访问hiden method,这就是Android P对非SDK API的调用的限制。
04-27 14:27:03.468 5933 5933 W om.example.tes: Accessing hidden method Ldalvik/system/VMRuntime;->setHiddenApiExemptions([Ljava/lang/String;)V (blacklist, reflection)
04-27 14:27:03.468 683 683 I ConfigStore: android::hardware::configstore::V1_0::ISurfaceFlingerConfigs::hasHDRDisplay retrieved: 0
04-27 14:27:03.469 683 683 I ConfigStore: android::hardware::configstore::V1_0::ISurfaceFlingerConfigs::hasHDRDisplay retrieved: 0
04-27 14:27:03.469 5933 5933 W System.err: java.lang.NoSuchMethodException: setHiddenApiExemptions [class [Ljava.lang.String;]
04-27 14:27:03.469 683 683 I ConfigStore: android::hardware::configstore::V1_0::ISurfaceFlingerConfigs::hasHDRDisplay retrieved: 0
04-27 14:27:03.469 5933 5933 W System.err: at java.lang.Class.getMethod(Class.java:2068)
04-27 14:27:03.470 5933 5933 W System.err: at java.lang.Class.getDeclaredMethod(Class.java:2047)
04-27 14:27:03.470 5933 5933 W System.err: at com.example.test.MainActivity.getProperty(MainActivity.java:68)
04-27 14:27:03.470 5933 5933 W System.err: at com.example.test.MainActivity.onCreate(MainActivity.java:27)
04-27 14:27:03.471 5933 5933 W System.err: at android.app.Activity.performCreate(Activity.java:7144)
04-27 14:27:03.471 5933 5933 W System.err: at android.app.Activity.performCreate(Activity.java:7135)
04-27 14:27:03.471 5933 5933 W System.err: at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1272)
04-27 14:27:03.472 5933 5933 W System.err: at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2932)
04-27 14:27:03.472 5933 5933 W System.err: at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3087)
04-27 14:27:03.473 5933 5933 W System.err: at android.app.servertransaction.LaunchActivityItem.execute(LaunchActivityItem.java:78)
04-27 14:27:03.473 5933 5933 W System.err: at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:108)
04-27 14:27:03.475 5933 5933 W System.err: at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:68)
04-27 14:27:03.475 5933 5933 W System.err: at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1817)
04-27 14:27:03.476 5933 5933 W System.err: at android.os.Handler.dispatchMessage(Handler.java:106)
04-27 14:27:03.476 5933 5933 W System.err: at android.os.Looper.loop(Looper.java:193)
04-27 14:27:03.476 5933 5933 W System.err: at android.app.ActivityThread.main(ActivityThread.java:6746)
04-27 14:27:03.476 5933 5933 W System.err: at java.lang.reflect.Method.invoke(Native Method)
04-27 14:27:03.477 5933 5933 W System.err: at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:493)
04-27 14:27:03.477 5933 5933 W System.err: at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:858)
二.源码分析
在正式开发分析前,先奉上源码分析流程图,这样有一个整体的轮廓认识。
2.1 getDeclaredMethod
通过前面的实例我们可以看到,我们是在调用getDeclaredMethod的过程中失败了,所以我们先大胆的猜测一定是Android P在getDeclaredMethod的调用过程中做了限制,然后导致反射失败了。让我们大胆验证,小心求证。先从Class.java开始分析。
//Class.java
@CallerSensitive
public Method getDeclaredMethod(String name, Class<?>... parameterTypes)
throws NoSuchMethodException, SecurityException {
return getMethod(name, parameterTypes, false);//见2.2
}
2.2 getMethod
//Class.java
private Method getMethod(String name, Class<?>[] parameterTypes, boolean recursivePublicMethods)
throws NoSuchMethodException {
if (name == null) {//判断方法名是否为空
throw new NullPointerException("name == null");
}
if (parameterTypes == null) {//判断参数是否为空
parameterTypes = EmptyArray.CLASS;
}
for (Class<?> c : parameterTypes) {
if (c == null) {
throw new NoSuchMethodException("parameter type is null");
}
}
//一切条件满足且是非public方法,则执行getDeclaredMethodInternal方法,具体见2.3
Method result = recursivePublicMethods ? getPublicMethodRecursive(name, parameterTypes)
: getDeclaredMethodInternal(name, parameterTypes);
// Fail if we didn't find the method or it was expected to be public.
//如果没有找到,则抛出异常
if (result == null ||
(recursivePublicMethods && !Modifier.isPublic(result.getAccessFlags()))) {
throw new NoSuchMethodException(name + " " + Arrays.toString(parameterTypes));
}
return result;
}
2.3 getDeclaredMethodInternal
//Class.java
/**
* Returns the method if it is defined by this class; {@code null} otherwise. This may return a
* non-public member.
*
* @param name the method name
* @param args the method's parameter types
*/
@FastNative
private native Method getDeclaredMethodInternal(String name, Class<?>[] args);
这是一个Java本地方法,最终通过JNI调用到art/runtime/native/java_lang_Class.cc里面,如下:
//native/java_lang_Class.cc
static jobject Class_getDeclaredMethodInternal(JNIEnv* env, jobject javaThis,
jstring name, jobjectArray args) {
ScopedFastNativeObjectAccess soa(env);
StackHandleScope<1> hs(soa.Self());
DCHECK_EQ(Runtime::Current()->GetClassLinker()->GetImagePointerSize(), kRuntimePointerSize);
DCHECK(!Runtime::Current()->IsActiveTransaction());
Handle<mirror::Method> result = hs.NewHandle(
mirror::Class::GetDeclaredMethodInternal<kRuntimePointerSize, false>(
soa.Self(),
DecodeClass(soa, javaThis),
soa.Decode<mirror::String>(name),
soa.Decode<mirror::ObjectArray<mirror::Class>>(args)));
检测该方法是否允许访问
if (result == nullptr || ShouldBlockAccessToMember(result->GetArtMethod(), soa.Self())) {
return nullptr;
}
return soa.AddLocalReference<jobject>(result.Get());
}
这个地方是关键,我们来看一下Android 9和Android 8相比做了哪些修改。下图中左边是Android 9,右边是Android 8:
是不是一下就看出来了,Android 的代码在反射时候会增加一个ShouldBlockAccessToMember()判断,如果返回true,那么你在getDeclaredMethod()时候就会得到null。如果你的Rom不想限制客户的API反射调用的话,就可以屏蔽这个地方即可。
2.4 ShouldBlockAccessToMember
template<typename T>
ALWAYS_INLINE static bool ShouldBlockAccessToMember(T* member, Thread* self)
REQUIRES_SHARED(Locks::mutator_lock_) {
hiddenapi::Action action = hiddenapi::GetMemberAction( // 获取的action的类型是重点
member, self, IsCallerTrusted, hiddenapi::kReflection); // kReflection : 反射方式调用
if (action != hiddenapi::kAllow) {
hiddenapi::NotifyHiddenApiListener(member); // 当不是kAllow时,则需要警告或者弹窗,则通过此方法通知
}
return action == hiddenapi::kDeny; // 返回true则被限制,也就是当action是kAllow/kAllowButWarn/kAllowButWarnAndToast返回false,都是允许accessmember的
}
让我们来重点分析一下这个函数,这个地方就是反射接口限制判断的地方,此处的入参参数AccessMethod access_method等于hiddenapi::kReflection,除此之外还有其他几种模式,如下:
2.4.1 AccessMethod
hidden_api.h
// Hidden API enforcement policy
// This must be kept in sync with ApplicationInfo.ApiEnforcementPolicy in
// frameworks/base/core/java/android/content/pm/ApplicationInfo.java
enum class EnforcementPolicy {
kNoChecks = 0,
kJustWarn = 1, // keep checks enabled, but allow everything (enables logging)
kDarkGreyAndBlackList = 2, // ban dark grey & blacklist
kBlacklistOnly = 3, // ban blacklist violations only
kMax = kBlacklistOnly,
};
enum Action {
kAllow, //允许
kAllowButWarn, //允许 + 警告
kAllowButWarnAndToast, //允许+警告+弹窗
kDeny //阻止
};
enum AccessMethod {
kNone, //测试模式,不会出现在实际场景访问权限
kReflection, //Java发射调用
kJNI, //JNI调用过程
kLinking, //动态链接过程
};
2.4.1 各种限制场景分析
通过前面的篇章我们知道ShouldBlockAccessToMember是限制内部API访问的最核心代码,让我们对其限制的几种模式一一分析:
-
kReflection反射过程
Class_newInstance:对象实例化
Class_getDeclaredConstructorInternal:构造方法
Class_getDeclaredMethodInternal:获取方法
Class_getDeclaredField:获取字段
Class_getPublicFieldRecursive:获取字段
-
kJNI的JNI调用过程
FindMethodID:查找方法
FindFieldID:查找字段
-
kJNI的JNI调用过程
UnstartedClassNewInstance
UnstartedClassGetDeclaredConstructor
UnstartedClassGetDeclaredMethod
UnstartedClassGetDeclaredField
2.5 GetMemberAction
hidden_api.h
template<typename T>
这里的Action就是允许,警告,弹窗,阻止其中的一种
inline Action GetMemberAction(T* member,
Thread* self,
std::function<bool(Thread*)> fn_caller_is_trusted,
AccessMethod access_method)
REQUIRES_SHARED(Locks::mutator_lock_) {
//获取hidenn API的可访问标识
// HiddenApiAccessFlags 定义在 hidden_api_access_flags.h 白名单 浅灰名单 深灰名单 黑名单
HiddenApiAccessFlags::ApiList api_list = member->GetHiddenApiAccessFlags();
//获取相应的访问行为【小节2.6】
Action action = GetActionFromAccessFlags(member->GetHiddenApiAccessFlags());
if (action == kAllow) { //允许则直接返回
return action;
}
//检测是否平台调用,通过函数指针调用到IsCallerTrusted函数【小节2.7】
if (fn_caller_is_trusted(self)) {
return kAllow;
}
//对于hidden接口,且非平台调用【小节2.8】
return detail::GetMemberActionImpl(member, api_list, action, access_method);
}
此方法里有三处要分析,分别是:
- 当Action=kAllow,则直接返回;否则执行如下:
- 通过fn_caller_is_trusted看当前是否是系统调用的,如果是系统调用则返回,否则执行如下:
- 通过GetMemberActionImpl()做进一步判断
在分析上面三个方法之前先来看看HiddenApiAccessFlags 的定义如下:
. art/libdexfile/dex/hidden_api_access_flags.h
class HiddenApiAccessFlags {
public:
enum ApiList {
kWhitelist = 0, //白名单
kLightGreylist, // 浅灰名单
kDarkGreylist, // 深灰名单
kBlacklist, // 黑名单
};
这几个名单的定义如下:
-
白名单:SDK 本身
-
浅灰名单:仍允许调用的非 SDK 方法和字段
-
深灰名单
– 若应用的 target SDK 低于 Android P (即 targetSdkVersion <28):允许调用深灰名单中的接
– 若应用的 target SDK 为 Android P 或更高 (即 targetSdkVersion >= 28):深灰名单与黑名单的限制相同
-
黑名单:不论 target SDK 版本为多少,所有应用均不允许调用黑名单接口。对开发者来说,相当于系统里不存在这些接口。比如,当应用试图调用此类接口时,系统会抛出 NoSuchMethodError / NoSuchFieldException 异常,并且在应用获取特定类的字段和方法列表时,不在返回列表中包含此类接口。
2.6 GetActionFromAccessFlags
先看GetActionFromAccessFlags(member->GetHiddenApiAccessFlags()),要先分析member->GetHiddenApiAccessFlags.
art_method-inl.h
inline HiddenApiAccessFlags::ApiList ArtMethod::GetHiddenApiAccessFlags()
REQUIRES_SHARED(Locks::mutator_lock_) {
if (UNLIKELY(IsIntrinsic())) {
switch (static_cast<Intrinsics>(GetIntrinsic())) {
case Intrinsics::kSystemArrayCopyChar:
case Intrinsics::kStringGetCharsNoCheck:
case Intrinsics::kReferenceGetReferent:
// These intrinsics are on the light greylist and will fail a DCHECK in
// SetIntrinsic() if their flags change on the respective dex methods.
// Note that the DCHECK currently won't fail if the dex methods are
// whitelisted, e.g. in the core image (b/77733081). As a result, we
// might print warnings but we won't change the semantics.
return HiddenApiAccessFlags::kLightGreylist;
case Intrinsics::kVarHandleFullFence:
case Intrinsics::kVarHandleAcquireFence:
case Intrinsics::kVarHandleReleaseFence:
case Intrinsics::kVarHandleLoadLoadFence:
case Intrinsics::kVarHandleStoreStoreFence:
case Intrinsics::kVarHandleCompareAndExchange:
case Intrinsics::kVarHandleCompareAndExchangeAcquire:
case Intrinsics::kVarHandleCompareAndExchangeRelease:
case Intrinsics::kVarHandleCompareAndSet:
case Intrinsics::kVarHandleGet:
case Intrinsics::kVarHandleGetAcquire:
case Intrinsics::kVarHandleGetAndAdd:
case Intrinsics::kVarHandleGetAndAddAcquire:
case Intrinsics::kVarHandleGetAndAddRelease:
case Intrinsics::kVarHandleGetAndBitwiseAnd:
case Intrinsics::kVarHandleGetAndBitwiseAndAcquire:
case Intrinsics::kVarHandleGetAndBitwiseAndRelease:
case Intrinsics::kVarHandleGetAndBitwiseOr:
case Intrinsics::kVarHandleGetAndBitwiseOrAcquire:
case Intrinsics::kVarHandleGetAndBitwiseOrRelease:
case Intrinsics::kVarHandleGetAndBitwiseXor:
case Intrinsics::kVarHandleGetAndBitwiseXorAcquire:
case Intrinsics::kVarHandleGetAndBitwiseXorRelease:
case Intrinsics::kVarHandleGetAndSet:
case Intrinsics::kVarHandleGetAndSetAcquire:
case Intrinsics::kVarHandleGetAndSetRelease:
case Intrinsics::kVarHandleGetOpaque:
case Intrinsics::kVarHandleGetVolatile:
case Intrinsics::kVarHandleSet:
case Intrinsics::kVarHandleSetOpaque:
case Intrinsics::kVarHandleSetRelease:
case Intrinsics::kVarHandleSetVolatile:
case Intrinsics::kVarHandleWeakCompareAndSet:
case Intrinsics::kVarHandleWeakCompareAndSetAcquire:
case Intrinsics::kVarHandleWeakCompareAndSetPlain:
case Intrinsics::kVarHandleWeakCompareAndSetRelease:
// These intrinsics are on the blacklist and will fail a DCHECK in
// SetIntrinsic() if their flags change on the respective dex methods.
// Note that the DCHECK currently won't fail if the dex methods are
// whitelisted, e.g. in the core image (b/77733081). Given that they are
// exclusively VarHandle intrinsics, they should not be used outside
// tests that do not enable hidden API checks.
return HiddenApiAccessFlags::kBlacklist;
default:
// Remaining intrinsics are public API. We DCHECK that in SetIntrinsic().
return HiddenApiAccessFlags::kWhitelist;
}
} else {
return HiddenApiAccessFlags::DecodeFromRuntime(GetAccessFlags());
}
}
GetHiddenApiAccessFlags()获取相应的flag,接着看GetActionFromAccessFlags:
hidden_api.h
inline Action GetActionFromAccessFlags(HiddenApiAccessFlags::ApiList api_list) {
if (api_list == HiddenApiAccessFlags::kWhitelist) {
return kAllow; //位于白名单,则允许访问
}
EnforcementPolicy policy = Runtime::Current()->GetHiddenApiEnforcementPolicy();
if (policy == EnforcementPolicy::kNoChecks) {
return kAllow; //非强制执行策略,则允许访问
}
if (policy == EnforcementPolicy::kJustWarn) {
return kAllowButWarn;
}
//执行到这,policy>=kDarkGreyAndBlackList
if (static_cast<int>(policy) > static_cast<int>(api_list)) {
return api_list == HiddenApiAccessFlags::kDarkGreylist
? kAllowButWarnAndToast : kAllowButWarn;
} else {
return kDeny;
}
}
以上逻辑用图来展示EnforcementPolicy和HiddenApiAccessFlags在不同取值的情况下,所对应的Action值。 纵轴代表强制策略级别,横轴代表隐藏API的标识,表中数据代表Action,如下所示:
纵轴/横轴 |
kWhitelist |
kLightGreylist |
kDarkGreylist |
kBlacklist |
kNoChecks |
kAllow |
kAllow |
kAllow |
kAllow |
kJustWarn |
kAllow |
kAllowButWarn |
kAllowButWarn |
kAllowButWarn |
kDarkGreyAndBlackList |
kAllow |
kAllowButWarn |
kDeny |
kDeny |
kBlacklistOnly |
kAllow |
kAllowButWarn |
kAllowButWarnAndToast |
kDeny |
策略图解如下:
- 当HiddenApiAccessFlags等于kWhitelist,则Action=kAllow,否则如下
- 当EnforcementPolicy等于kNoChecks,则Action=kAllow,否则如下
- 当EnforcementPolicy等于kJustWarn,则Action=kAllowButWarn,否则如下
- 当HiddenApiAccessFlags等于kLightGreylist,则Action=kAllowButWarn,否则如下
- 当HiddenApiAccessFlags等于kDarkGreylist,且等于EnforcementPolicy=kBlacklistOnly,则- kAllowButWarnAndToast,否则如下
- 否则Action=kDeny
2.6.1 EnforcementPolicy
EnforcementPolicy的级别如下:
// Hidden API enforcement policy
// This must be kept in sync with ApplicationInfo.ApiEnforcementPolicy in
// frameworks/base/core/java/android/content/pm/ApplicationInfo.java
enum class EnforcementPolicy {
kNoChecks = 0,
kJustWarn = 1, // keep checks enabled, but allow everything (enables logging)
kDarkGreyAndBlackList = 2, // ban dark grey & blacklist
kBlacklistOnly = 3, // ban blacklist violations only
kMax = kBlacklistOnly,
};
- kNoChecks:允许调用所有API,不做任何检测
- kJustWarn:允许调用所有API,但是对于私有API的调用会打印警告log
- kDarkGreyAndBlackList:会阻止调用dark grey或black list中的API
- kBlacklistOnly:会阻止调用black list中的API
可通过SetHiddenApiEnforcementPolicy()来修改Runtime中的成员变量hidden_api_policy_,如下所示:
void SetHiddenApiEnforcementPolicy(hiddenapi::EnforcementPolicy policy) {
hidden_api_policy_ = policy;
}
hiddenapi::EnforcementPolicy GetHiddenApiEnforcementPolicy() const {
return hidden_api_policy_;
}
2.7 IsCallerTrusted
fn_caller_is_trusted 其实是通过函数指针调用到IsCallerTrusted函数
java_lang_Class.cc
static bool IsCallerTrusted(Thread* self) REQUIRES_SHARED(Locks::mutator_lock_) {
...
FirstExternalCallerVisitor visitor(self);
visitor.WalkStack();
//【见小节2.7.1】
return visitor.caller != nullptr &&
hiddenapi::IsCallerTrusted(visitor.caller->GetDeclaringClass())