https://blog.csdn.net/tkwxty/article/details/105784784
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,除此之外还有其他几种模式,如下:
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, //动态链接过程
};
通过前面的篇章我们知道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
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());
}
hiddenapi::IsCallerTrusted ,是调用到了如下位置:
hidden_api.h
ALWAYS_INLINE
inline bool IsCallerTrusted(ObjPtr<mirror::Class> caller,
ObjPtr<mirror::ClassLoader> caller_class_loader,
ObjPtr<mirror::DexCache> caller_dex_cache)
REQUIRES_SHARED(Locks::mutator_lock_) {
if (caller_class_loader.IsNull()) {
return true; // Boot classloader,则返回true
}
if (!caller_dex_cache.IsNull()) {
const DexFile* caller_dex_file = caller_dex_cache->GetDexFile();
if (caller_dex_file != nullptr && caller_dex_file->IsPlatformDexFile()) {
return true; // caller是平台dex文件,则返回true
}
}
if (!caller.IsNull() &&
caller->ShouldSkipHiddenApiChecks() &&
Runtime::Current()->IsJavaDebuggable()) {
return true; //处于debuggable调试模式且caller已被标记可信任,则返回true
}
return false;//除了以上三种情况都是false
}
caller被认为是可信任的场景如下:
- 当类加载器是Boot classloader,则返回true
- 当caller是平台dex文件,则返回true
- 处于debuggable调试模式且caller已被标记可信任,则返回true
2.8 GetMemberActionImpl
hidden_api.cc
template<typename T>
Action GetMemberActionImpl(T* member,
HiddenApiAccessFlags::ApiList api_list,
Action action,
AccessMethod access_method) {
//获取签名
MemberSignature member_signature(member);
Runtime* runtime = Runtime::Current();
const bool shouldWarn = kLogAllAccesses || runtime->IsJavaDebuggable();
if (shouldWarn || action == kDeny) {
//判断是否为可豁免接口【小节2.8.1】
if (member_signature.IsExempted(runtime->GetHiddenApiExemptions())) {
action = kAllow;
MaybeWhitelistMember(runtime, member);
return kAllow;
}
if (access_method != kNone) {
//打印包含有关此类成员访问信息的日志消息【小节2.8.2】
member_signature.WarnAboutAccess(access_method, api_list);
}
}
...
if (action == kDeny) {
return action; //拒绝调用
}
if (access_method != kNone) {
// 根据运行时标志的不同,我们可以将成员移动到白名单中,并在下次访问成员时跳过警告。
MaybeWhitelistMember(runtime, member);
//如果此操作需要UI警告,设置适当的标志
if (shouldWarn &&
(action == kAllowButWarnAndToast || runtime->ShouldAlwaysSetHiddenApiWarningFlag())) {
runtime->SetPendingHiddenApiWarning(true);
}
}
return action;
}
runtime.h
class Runtime {
...
// SetHiddenApiEnforcementPolicy()可修改该值
hiddenapi::EnforcementPolicy hidden_api_policy_;
//SetHiddenApiExemptions()可修改该值
std::vector<std::string> hidden_api_exemptions_;
...
}
GetHiddenApiExemptions()是获取Runtime里面的成员变量hidden_api_exemptions_
hidden_api.cc
void MemberSignature::WarnAboutAccess(AccessMethod access_method,
HiddenApiAccessFlags::ApiList list) {
LOG(WARNING) << "Accessing hidden " << (type_ == kField ? "field " : "method ")
<< Dumpable<MemberSignature>(*this) << " (" << list << ", " << access_method << ")";
打印包含有关此类成员访问信息的日志消息
三.放开Android P非SDK API的限制
这个主要可以从三个方面下手,如下:
diff --git a/art/runtime/native/java_lang_Class.cc b/art/runtime/native/java_lang_Class.cc
index c07c32a..b56c1a6 100644
--- a/art/runtime/native/java_lang_Class.cc
+++ b/art/runtime/native/java_lang_Class.cc
@@ -115,6 +115,13 @@ ALWAYS_INLINE static bool ShouldEnforceHiddenApi(Thread* self)
template<typename T>
ALWAYS_INLINE static bool ShouldBlockAccessToMember(T* member, Thread* self)
REQUIRES_SHARED(Locks::mutator_lock_) {
+#ifdef XXX_HIDEAPI_RULE
+ //std::string message("Use XXX_API_RULE");
+ //LOG(ERROR) << message;
+ //std::cerr << message << std::endl;
+
+ return false;
+#else
hiddenapi::Action action = hiddenapi::GetMemberAction(
member, self, IsCallerTrusted, hiddenapi::kReflection);
if (action != hiddenapi::kAllow) {
@@ -122,6 +129,7 @@ ALWAYS_INLINE static bool ShouldBlockAccessToMember(T* member, Thread* self)
}
return action == hiddenapi::kDeny;
+#endif
}
--- a/frameworks/base/core/java/android/content/pm/ApplicationInfo.java
+++ b/frameworks/base/core/java/android/content/pm/ApplicationInfo.java
@@ -49,13 +49,18 @@ import java.util.Arrays;
import java.util.Comparator;
import java.util.Objects;
import java.util.UUID;
-
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import android.util.Log;
/**
* Information you can retrieve about a particular application. This
* corresponds to information collected from the AndroidManifest.xml's
* <application> tag.
*/
public class ApplicationInfo extends PackageItemInfo implements Parcelable {
+ public static final List<String>specialApp = new ArrayList(Arrays.asList("com.xxx.xxxscans","com.xxx.market.android.app"));
+
/**
* Default task affinity of all activities in this application. See
@@ -1701,13 +1706,20 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable {
dataDir = credentialProtectedDataDir;
}
}
+ private boolean isSpecialXXXApp(){
+ boolean result = specialApp.contains(packageName);
+ if(result)
+ Log.e("ApplicationInfo","SpecialXXXApp : " + packageName + " can use HiddenApis" );
+
+ return result;
+ }
private boolean isPackageWhitelistedForHiddenApis() {
return SystemConfig.getInstance().getHiddenApiWhitelistedApps().contains(packageName);
}
private boolean isAllowedToUseHiddenApis() {
- return isSignedWithPlatformKey()
+ return isSpecialXXXApp() || isSignedWithPlatformKey()
|| (isPackageWhitelistedForHiddenApis() && (isSystemApp() || isUpdatedSystemApp()));
}
还有一个就是屏蔽黑名单中限制的API,其中涉及到的各种黑白名单在源码中的目录如下:
out/target/common/obj/PACKAGING/hiddenapi-light-greylist.txt
out/target/common/obj/PACKAGING/hiddenapi-dark-greylist.txt
out/target/common/obj/PACKAGING/hiddenapi-blacklist.txt
frameworks/base/config/hiddenapi-force-blacklist.txt
frameworks/base/config/hiddenapi-light-greylist.txt
frameworks/base/config/hiddenapi-private-dex.txt
frameworks/base/config/hiddenapi-public-dex.txt
frameworks/base/config/hiddenapi-removed-dex.txt
frameworks/base/config/hiddenapi-vendor-list.txt
out/target/product/product_name/system/etc/sysconfig/hiddenapi-package-whitelist.xml
frameworks/base/data/etc/hiddenapi-package-whitelist.xml
同时在测试阶段也可以通过adb打开或者关闭限制
可以通过使用 adb,在开发设备上允许访问非 SDK API。
若您想在 adb logcat 中显示 API 访问信息,您可通过以下命令更改 API 执行策略:
-
adb shell settings put global hidden_api_policy_pre_p_apps 1
-
adb shell settings put global hidden_api_policy_p_apps 1
更改回默认设置:
-
adb shell settings delete global hidden_api_policy_pre_p_apps
-
adb shell settings delete global hidden_api_policy_p_apps
以上命令不需要设备获得 Root 权限。
命令最后的数字分别表示:
四. 总结
3.1 限制原理总结
(1)从前面的内部API限制过程,可知主要控制逻辑在方法ShouldBlockAccessToMember,调用该方法的核心路径如下:
- kReflection反射过程:
Class_newInstance:对象实例化
Class_getDeclaredConstructorInternal:构造方法
Class_getDeclaredMethodInternal:获取方法
Class_getDeclaredField:获取字段
Class_getPublicFieldRecursive:获取字段 - kJNI的JNI调用过程:
FindMethodID:查找方法
FindFieldID:查找字段 - kLinking动态链接:
UnstartedClassNewInstance
UnstartedClassGetDeclaredConstructor
UnstartedClassGetDeclaredMethod
UnstartedClassGetDeclaredField
(2)ShouldBlockAccessToMember过程是否运行主要有如下情况:
当EnforcementPolicy强制不限制的情况
当类加载器是Boot classloader的情况
当caller是平台dex文件的情况
当处于debuggable调试模式且caller已被标记可信任的情况
当GetHiddenApiExemptions为豁免情况
(3)掌握了ShouldBlockAccessToMember原理,也就可以有的放矢了,突破限制方案,比如:
-修改ART的EnforcementPolicy,也就是Runtime中的成员变量hidden_api_policy_,可以基于地址偏移找到相-应的成员,这就不就细说
-修改隐藏API豁免变量,也就是Runtime中的成员变量hidden_api_exemptions_
-修改classLoader为BootClassLoader
3.2 黑白名单
关于在Android P的几个预览版本一直在不断调整,目前主要是黑名单、浅灰名单、vendor名单这3个名单,对应文件名:hiddenapi-force-blacklist.txt,hiddenapi-light-greylist.txt,hiddenapi-vendor-list.txt。这些文件在编译阶段生成为hiddenapi,记录在access_flags_字段值。整个过程是在hiddenapi.cc过程的CategorizeAllClasses()中完成的。
目前主要使用的是黑名单和浅灰名单,深灰名单暂没有使用:
- 黑名单内容:setHiddenApiExemptions
- 浅灰名单内容:Activity, Service,ContentProvider,ActivityManager, ActivityThread,Application,ContextImpl,Intent等
3.3 非SDK接口说明
- 非SDK接口限制适用于所有应用,但是会豁免使用平台密钥签署的应用,并且针对系统app的白名单
- 如果你的应用有必须使用非SDK接口的充分理由,可以向Google提交功能请求,并提供用例详情;
- 如果你的应用使用很多第三方库,而又难以排除是否正在使用非SDK接口,可以尝试使用AOSP提供的静态分析工具veridex;
- 应用运行时检测到非SDK接口的使用,会打印一条Accessing hidden field|method … 形式的logcat警告。当然对于设置android:debuggable=true的可调试应用会显示toast消息,并打印logcat日志。
- 黑名单/灰名单编码在平台dex文件的字段和函数访问标志位中,无法从系统镜像中找到单独包含这些名单的文件。
- 黑名单/灰名单在采用相同Android版本是一致的。手机厂商可以向黑名单添加自己的API,但无法从AOSP黑名单或灰名单中移除,Google兼容性定义文件(CDD)来保障这一工作,并在测试过程通过CTS来确保Android运行时强制执行名单。
- 目前,Google对Android暂时没有限制访问dex2oat二进制文件的计划,但是dex文件格式无法保证会稳定,可能随时会修改或删除dex2oat以及dex衍生文件。
Android 7.0针对Native lib引入了namespace来限制平台lib对外的可见性,普通APP在Android N上不能直接调用系统私有库,Android系统允许调用的公用库定义在public.libraries.android.txt。 这个feature只针对target SDK为24及以上的APP。
Android 7.0限制对C/C++代码的NDK,Android 9.0限制对Java的SDK。这一以来APP想利用内部API搞黑科技的难度以及不稳定性都会有所增加。
最后说一点,目前网络有一些开发者发表了关于非SDK接口限制的绕过技术,但基本没有APP是通过这些漏洞方式来突破隐藏API的访问,说明大家有所顾忌,这对生态来说是好事。另外关于绕过技术对于Google和手机厂商都已注意到,要简单封杀容易但可能会带来调试与复杂度的提升,所以Google正在积极寻找平衡接口限制与运行时易于调试之间的平衡,相信很快Google会有更完善的解决方案。
结语
各位乡亲们,Android P非SDK API限制调用开发指南,整的我腰酸背痛啊。Android版本升级之时就是我等受苦之时啊。看gityuan的博客然后加上自己的理解和总结整的我好累啊。
写在最后
好了如上就是Android P非SDK API限制调用开发指南所有,如有问题或者有任何疑问请及时沟通或者交流,也可点个赞或者吐槽一番也是可以的。so goodbye。这篇主要都是gityuan的博客,所以各位不要吐槽我了。
参考博客: