Android 非SDK接口

1,605 阅读6分钟

背景:在APM项目中,我们经常需要通过反射获取一些字段。在卡顿分析的过程中,我想要通过反射获取Choreographer的Object mLock字段:

 public static <T> T reflecObject(Object instance,String name, T defaultValue){
   try{
   Class<?> clazz = instance.getClass();
   Field field = clazz.getDeclaredField(name);
   filed.setAccessiable(true);
   return (T) field.get(instance);
   }catch(Exception e){
      Log.e(TAG,e.toString();
      return defaultValue;
   }
 
 }
 
 ReflectUtil.reflect(choreographer,"mLock",null);

看上去这就是一个非常简单的反射工具类,一段简单的反射方法,但是我发现它并不能反射到这个字段。 有一个更奇怪的现象,就是我把targetSdkVersion改到22之后,它又可以work了。(不要问我为什么会想到去改targetSdkVersion,其实我第一开始就知道原因了,只是场景模拟一下)。 如果大家对targetSdkVersion的原理还不是很清楚,可以查一下官方文档以及AndroidManifest.xml merge的原理,在这里就不过多解释了。 ok,下一步,我就看一下API 22 与API 31在Choreographer#mLock这个字段有什么区别。

/// API 22
private final Object mLock = new Object();
/// API 31
@UnsupportedAppUsage(maxTarget = Build.VERSION.CODES.P)
private final Object mLock = new Object();

OK,这里看上去field name并没有什么区别,那看来玄机就在@UnsupportedAppUsage 这个注解上了。接下来,就会介绍非SDK 接口。

什么是非SDK接口?

一般而言,公共SDK接口是在Android框架软件包索引中记录的那些接口,非SDK接口的处理是API抽象出来的实现细节,因此这些接口可能会在不另行通知的情况下随时发生更改。为了避免崩溃和意外行为,应用应仅使用SDK种经过正式记录的类。这也意味着当你的应用通过反射等机制与类互动时,不应访问SDK中未列出的方法或字段。   这段话来自于Android 官方文档,我在这通俗的翻译一下,公共SDK一般就是Android SDK定义的一些public的method,field,你的代码里可以自由调用这些API,这些API相对比较稳定。而非SDK接口就是一些实现细节,这些实现细节可能会官方不做声明的情况下有改动,所以你的代码里应该没有与这些非SDK接口的交互,进而避免一些意料之外的崩溃。  

Android什么时候开始针对非SDK接口限制?

  从Android9(API级别28)开始,Android平台对应用能使用的非SDK接口实施了限制,只要应用引用非SDK接口或尝试使用反射机制JNI来获取method的句柄,这些限制就将适用。 这也就解释了为什么我们之前调整targetSdkVersion 会让结果发生改变 其实Android 加了这个限制还是为了app的稳定性,毕竟这些实现细节可能随时发生变化,所以用反射或者JNI拿这些字段并不稳定. 

非SDK接口限制如何实现?

@UnsupportedAppUsage

Indicates that this non-SDK is used by apps. A non-SDK interface is a class member(field or method) that is not part of the public SDK. Since the member is not part of the SDK, usage by apps is not supported.
For Android developer:
This annotation indicates that you may be able to access the member at runtime, but that this access is discouraged and not supported by Android. If there is a value for {@link #maxTargetSdk()} on the annotation, access will be restricted based on the targetSdkVersion value set in your manifest. Fields and methods annotated with this are likely to be restricted,changed or removed in future Android releases. For Android OS developer: This annotation acts as a heads up that changing a given method or filed may affect apps, potentially breaking them when the next Android version is released. In some cases, for members that are heavily used, this annotation may imply restrictions on changes to member. Which apps can access the member is determined by the value of {@link #maxTargetSdk()}.

@Retention(Class)
@Target({CONSTRUCTOR, METHOD, FIELD, TYPE})
@Repeatable(UnsupportedAppUsage.Container.class)
public @interface UnsuportedAppUsage{
   long trackingBug() default 0;
   int maxTargetSdk() default Integer.MAX_VALUE;
   String implicitMember() default "";
   ...
}

我再翻译一下这个注解的意思,这个注解其实就表明了这个字段是一个非SDK接口,你在运行时能不能获取到这个字段,需要根据注解的maxTargetSdk的值和你的app的targetSdkVersion 共同决定.

  int maxTargetSdk() default Integer.MAX_VALUE;
  /**
  * 表明这个API的访问受到target SDK version的限制
  * 只有targetSdkVersion 不大于这个值,app才能在运行时
  * 访问这个字段
  * possible values:
  * (1) 比如31 在这种情况下,这个API只有在 targetSDKVersion 小于 或者等于这个值的时候才能访问,否则访问会被阻止
  * (2) 如果这个值缺省(默认最大值), 所有的app都可以访问这个API, 但是这个行为在未来可能会被限制 如果是0 那么所有app都无法访问这个API
  */
  @UnsupportedAppUsage   不受限制灰名单
  @UnsupportedAppUsage(maxTargetSdk = 0) 黑名单
  @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.O) 受限制的灰名单,仅供Android 8.1 Oreo 27 或更低的targetSdkVersion访问

UnsupportedAppUsageProcessor.java

 //  tools/platform-compat/java/android/processor/compat/unsupportedappusage/UnsupportedAppUsageProcessor.java

这个就是UnsupportedAppUsageProcessor的APT,它会生成一个CSV文件hiddenapi-flags.csv 这个csv文件可以在Android developer官网上分版本下载,具体它是怎么work,我们会在后续的源码分析中讲解

Android 反射

  接下来,我们浅浅看一下Android反射源码,看一下这个约束是怎么工作的.

static jobject Class_getDeclaredMethodInternal(JNIEnv* env, jobject javaThis, jstring name, jobjectArray args){
  ///... 版本重复的逻辑就不写了 ShouldDenyAccessToMember 是关键
  if(result == nullptr||ShouldDenyAccessToMember(result->GetArtMethod(),soa.Self())){
    return nullptr;
  }
}

ALWAYS_INLINE static bool ShouldBlockAccessToMember(T* member, Thread* self)
    REQUIRES_SHARED(Locks::mutator_lock_) {
  hiddenapi::Action action = hiddenapi::GetMemberAction(
      member, self, IsCallerTrusted, hiddenapi::kReflection);
  if (action != hiddenapi::kAllow) {
    hiddenapi::NotifyHiddenApiListener(member);
  }
  /// kDeny 就拒绝access
  return action == hiddenapi::kDeny;
}
/// hidden_api.cc GetMemberAction

inline Action GetMemberAction(T* member,
                              Thread* self,
                              std::function<bool(Thread*)> fn_caller_is_trusted,
                              AccessMethod access_method)
    REQUIRES_SHARED(Locks::mutator_lock_) {
  DCHECK(member != nullptr);
  // Decode hidden API access flags.
  // NB Multiple threads might try to access (and overwrite) these simultaneously,
  // causing a race. We only do that if access has not been denied, so the race
  // cannot change Java semantics. We should, however, decode the access flags
  // once and use it throughout this function, otherwise we may get inconsistent
  // results, e.g. print whitelist warnings (b/78327881).
  HiddenApiAccessFlags::ApiList api_list = member->GetHiddenApiAccessFlags();
  Action action = GetActionFromAccessFlags(member->GetHiddenApiAccessFlags());
  if (action == kAllow) {
    // Nothing to do.
    return action;
  }
  // Member is hidden. Invoke `fn_caller_in_platform` and find the origin of the access.
  // This can be *very* expensive. Save it for last.
  if (fn_caller_is_trusted(self)) {
    // Caller is trusted. Exit.
    return kAllow;
  }
  // Member is hidden and caller is not in the platform.
  return detail::GetMemberActionImpl(member, api_list, action, access_method);
}

/// 这里有三个return 语句 我们先对大概的流程有一个概念,明确access deny大概在哪里发生,后续将详解这三个return 语句是如何返回的
 
 

总而言之,非SDK接口的限制就是通过@UnsupportedAppUsage 注解, UnsupportedAppUsageProcessor.java 以及反射中的ShouldDenyAccessMemeber函数等模块共同实现的,具体的源码我们会在之后的文章中具体分析,但其中最重要的还是理解@UnsupportedAppUsage 这个注解.

总结

这篇文章中我们介绍了什么是Android非SDK API,以及它为什么会存在,以及限制实现的几个关键组成,包括@UnsupportedAppUsage, UnsupportedAppProcessor.java, 以及反射过程中限制工作的函数. 后续,我们会介绍如何绕开非SDK API限制, 非SDK API 限制的源码分析.