Android 9 (P)非SDK API限制调用开发指南

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,除此之外还有其他几种模式,如下:

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,如下所示:

纵轴/横轴kWhitelistkLightGreylistkDarkGreylistkBlacklist
kNoCheckskAllowkAllowkAllowkAllow
kJustWarnkAllowkAllowButWarnkAllowButWarnkAllowButWarn
kDarkGreyAndBlackListkAllowkAllowButWarnkDenykDeny
kBlacklistOnlykAllowkAllowButWarnkAllowButWarnAndToastkDeny

策略图解如下:

  • 当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());
    }

2.7.1 IsCallerTrusted

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;
}

2.8.1 GetHiddenApiExemptions

runtime.h

class Runtime {
    ...
    // SetHiddenApiEnforcementPolicy()可修改该值
    hiddenapi::EnforcementPolicy hidden_api_policy_;

    //SetHiddenApiExemptions()可修改该值
    std::vector<std::string> hidden_api_exemptions_;
    ...
}

GetHiddenApiExemptions()是获取Runtime里面的成员变量hidden_api_exemptions_

2.8.2 WarnAboutAccess

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
  * &lt;application&gt; 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的博客,所以各位不要吐槽我了。

参考博客:

理解Android P内部API的限制调用机制

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值