非 SDK インターフェースの制限を確認する veridex ツールについて

### 最初に

Android のプロジェクトを **targetSdkVersion 29** にアップデートした際に、
公式に記載の **[非 SDK インターフェースの制限](https://developer.android.com/about/versions/10/behavior-changes-10?hl=ja)** に引っかかるかどうかを確認しました。


※これに引っかかると、特定環境での実行時に例外が発生することになります。

### 公開SDKインターフェイスとは？
Android フレームワークの[パッケージ インデックス](https://developer.android.com/reference/packages?hl=ja)に記述されているインターフェースのことです。

今回の議題である「非 SDK インターフェース」とは **インターフェイス化されている詳細を実装するもの** です。

非SDK インターフェイスには、
利用するのは良くないブロックリスト、使っても問題ないグレイリストなどが定義されています。


|用語  |意味  |
|---|---|
|ブロックリスト|アプリがインターフェースのいずれかにアクセスしようとすると、システムによってエラーがスローされます。 |
|グレイリスト|いまは問題ないが、そのうちブロックリストになるかも|
|条件付きブロックリスト|Android 9（API レベル 28）以降、アプリの対象 API レベルごとに制限される非 SDK インターフェース。特定のAPI レベルまではセーフで、それ以降はアウト|

### 発生する例外

リフレクションや Dalvik によるフィールド・メソッド参照を行うと下記例外が発生します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/486398/bee5e9f8-a801-ddb0-7d29-a0ec3997dc02.png)
[>>公式より引用
](https://developer.android.com/about/versions/10/behavior-changes-10?hl=ja)

### 確認方法

今回は **veridex ツールを使用したテスト** で確認することにしました。
このツールを利用すると、サードパーティ製ライブラリのリンクも踏まえてチェックすることができます。

※環境構築は公式を参考にしました。私は WSL の Ubuntsu で実施しました。
https://developer.android.com/distribute/best-practices/develop/restrictions-non-sdk-interfaces?hl=ja#veridex-windows

コマンドは下記で実施します。

```
./appcompat.sh --dex-file=【apkファイル】
```


### 実施結果

個人アプリで試したところ、、
Android 8 まで OK なのが２個、Android 9 まで OK なのが５個という結果になりました。

```
#34: Reflection greylist-max-p Landroid/view/inputmethod/InputMethodManager;->mH use(s):
       Landroidx/activity/ImmLeaksCleaner;->initializeReflectiveFields()V
--
#36: Reflection greylist-max-p Landroid/widget/AutoCompleteTextView;->doAfterTextChanged use(s):
       Landroidx/appcompat/widget/SearchView$PreQAutoCompleteTextViewReflector;-><init>()V

#37: Reflection greylist-max-p Landroid/widget/AutoCompleteTextView;->doBeforeTextChanged use(s):
       Landroidx/appcompat/widget/SearchView$PreQAutoCompleteTextViewReflector;-><init>()V

#38: Reflection greylist-max-p Landroid/widget/AutoCompleteTextView;->ensureImeVisible use(s):
       Landroidx/appcompat/widget/SearchView$PreQAutoCompleteTextViewReflector;-><init>()V

#39: Reflection greylist-max-p Landroid/widget/TextView;->getHorizontallyScrolling use(s):
       Landroidx/appcompat/widget/AppCompatTextViewAutoSizeHelper$Impl;->isHorizontallyScrollable(Landroid/widget/TextView;)Z
--
#45: Reflection greylist-max-o Lcom/android/internal/view/menu/MenuBuilder;->removeItemAt use(s):
       Landroidx/core/widget/TextViewCompat$OreoCallback;->recomputeProcessTextMenuItems(Landroid/view/Menu;)V
--
#47: Reflection greylist-max-o Ljava/nio/file/Files;->copy use(s):
       Lorg/assertj/core/internal/bytebuddy/build/Plugin$Engine$Target$ForFolder$Dispatcher$CreationAction;->run()Lorg/assertj/core/internal/bytebuddy/build/Plugin$Engine$Target$ForFolder$Dispatcher;
--
51 hidden API(s) used: 6 linked against, 45 through reflection
        44 in greylist
        0 in blacklist
        2 in greylist-max-o
        5 in greylist-max-p
        0 in greylist-max-q
```

こちらの見方ですが、、例えば３７番目の例で解説すると、

```
#37: Reflection greylist-max-p Landroid/widget/AutoCompleteTextView;->doBeforeTextChanged use(s):
   Landroidx/appcompat/widget/SearchView$PreQAutoCompleteTextViewReflector;-><init>()V
```
（解説）
**・androidx の SearchView クラス内で AutoCompleteTextView.doBeforeTextChanged をリフレクションで呼び出している**
**・Android 9(P)まではブロックされないが Android10 以降はブロックされる**

ということになります。実際にそのようにソースコードがなっているかを確認したところ、
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/486398/69fb3567-2afc-e49f-db3e-ae95760da5bd.png)
たしかに **SearchView 内のクラスで、リフレクションでコールされています。**

▼まとめ
targetSdkVersion 29 のプロジェクトで SearchView を利用し、
そのクラスの forceSuggestionQuery() が呼び出されると AutoCompleteTextViewReflector.doBeforeTextChanged() が呼ばれ
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/486398/748c3ccd-1a64-1023-55f0-1d6e212ab94d.png)
**Android10 以降で NoSuchMethodException が発生するってことですね。**

ただその場合でも Exception で例外をキャッチをしているので、アプリそのものはクラッシュはしない実装になってます。


### おまけ

Android Studio のデバッガから SearchView を開いたところ、
API29 以降は doBeforeTextChanged() をリフレクションで呼び出されないように対策されていました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/486398/6b674bde-200b-ddfe-f31f-e18782d15473.png)
この辺りは androidx のビルドバージョンによって実装が異なっているのだと思います。