Android Instant Apps に対応するには？

この記事は LIFULL Advent Calendar 2017の25日目の記事です。
株式会社 LIFULL TechLead G の菊地です。
# はじめに
今回は、LIFULL HOME'S アプリで対応を行った [Android Instant Apps](https://developer.android.com/topic/instant-apps/index.html)  についてになります。
Android Instant Apps への対応を行うにあたって、考えたことやハマったこと中心について書いていこうと思います。

# Android Instant Apps とは
インストールすることなく Android アプリを実行することを可能にする技術です。
Android 5.0（API レベル 21） 以降を実行している端末のみで動作します。

# Android Instant Apps の開発に必要な環境
- Android Studio 3.0 以上
- Android Instant Apps SDK

# Android Instant Apps に対応するために考えたこと
## ユースケースについて考える
[Best Pactice for user experience](https://developer.android.com/topic/instant-apps/ux-best-practices.html) 
こちらを参考にどういった UX が推奨されているかをまず最初に調べました。

次に LIFULL HOME'S で対応を行う際、Android Instant Apps で起動されるモジュールを通常のアプリと同じにするのか、それとも別の機能を用意するのか？で検討を行いました。
既に対応されているアプリでは、Install 版と同等の機能を提供しているアプリや、一部機能のみ、全く別の機能を提供しているアプリなど様々なものが公開されております。

LIFULL HOME'S では、*Dynamic Links による物件URLの共有機能*があることや、*広告などから物件情報の閲覧*が多く行われるため、こちらのURL との親和性を考慮して、**物件情報の閲覧**を中心で考えました。

## モジュールの分割単位について
![aia_features_diagram.png](https://qiita-image-store.s3.amazonaws.com/0/47143/666f13bc-0d3f-ce21-0ea6-04ddaebcdbd6.png)

Android Instant Apps は、**共通となる base モジュール**と**機能モジュール**の組み合わせでアプリとして動作します。
そのため、アプリにおいて共有的に利用されるものを base に、機能として提供されるものを機能モジュールに分割する。というのが最初の手順になります。
この時、提供する機能が複数あり、別々に提供したい場合には機能モジュールを複数用意し、機能モジュール間を URL ベースでの遷移でつなぐことも可能です。

## Install 版のアプリへのデータの引き継ぎ
Android Instant Apps では、Install 版のアプリへデータを引き継ぐことが出来ます。
今回は、[Cookie API](https://github.com/googlesamples/android-instant-apps/tree/master/cookie-api)を用いて実装することにしました。

こちらを用いることで、Instant ⇄ Install の間だけでなく、複数の機能モジュール間もデータのやりとりを行うことが出来るようになります。

```java:データの取得
PackageManagerCompat packageManagerCompat = InstantApps.getPackageManagerCompat(context);
byte[] bytes = packageManagerCompat.getInstantAppCookie();
```

```java:データの保存
byte[] bytes = ....;
PackageManagerCompat packageManagerCompat = InstantApps.getPackageManagerCompat(context);
packageManagerCompat.setInstantAppCookie(bytes);
```

## default-url への対応
先日、公開された Google Play Store 上に表示される「今すぐ試す」ボタンへの対応になります。

メインの機能として**物件情報の閲覧**を考えていたため、「今すぐ試す」を押して表示するものをどうするか？という問題がありました。
既に対応されているアプリでは、動画ページを表示したりしているところもありました。もちろん対応を行わないという選択肢もあるのですが、**試すなら物件探しの流れを体験してもらいたい**ということで、対応を行いました。

## Digital Asset Links
Android Instant Apps では、Web サイトとアプリの発行元を確認する必要があるため、[Digital Asset Links](https://developers.google.com/digital-asset-links/?hl=ja)への対応が必要でした。

# Android Instant Apps に対応するために試したこと
## 既存の app モジュールを機能モジュールにする
まず最初に Android Instant Apps としての動きを確認するために、app モジュールを機能モジュールとして、実装を行いました。
※ 実際は、ライブラリの対応状況や、その他の問題でビルドできなかったんですが、開発の仕方を確認する手順としてはありだと思います。

## ライブラリの選定
Android Instant Apps には容量の制限があります。
これは base モジュール + 機能モジュールの合計が4MB以下である必要があるのですが、ライブラリをたくさん使っているアプリだとこの容量制限の壁が辛くなります。
そのため、似たような機能のライブラリは事前に統合や代用できないかというのを調査し、対応を行いました。
Proguard をかけていても、結構な容量差が出てくる部分になります。

## Instant と Install の場合で、機能の出し分けをどうするか
例えば、Instant と Install で同じ画面を使うけど、インストールへの導線などの Instant の場合にだけ表示されていてほしいものについて、どうやって出し分けるのか？そもそも出し分けれるのか？というところが気になりました。

こちらは、API が用意されていて比較的容易に対応が可能です。

```java:
if (InstantApps.isInstantApp(context)) {
    ...
}
```

もちろん設計によってはこれを使わずにクラスを分けてもいいとは思いますが、同じ画面で・・・ということであれば、こちらを使った分岐の方が良いかと思います。

## Crash の検知や判別について
これは対応途中で Crashlytics が動作するようになったため、下記の手順で実装を行うことで、クラッシュログに Android Instant Apps で発生したクラッシュかどうか？がわかるようになりました。
※ 序盤では動作していなかったり、途中で動作しなくなったりしていました。

```java:base/build.gradle
crashlytics {
    instantAppSupport true
}
```

```java:App.java
@Override
public void onCreate() {
    Crashlytics.setBool("InstantApp", InstantApps.isInstantApp(this);
}
```

# Android Instant Apps でハマったこと
## DataBinding の対応状況
現時点では、**base モジュールでしか動作しません**
機能モジュールに分けたい部分に対して使ってしまっている場合は、丸ごと base モジュールに置くか、対応されるまで DataBinding の実装を別のものにするか。になってしまいます。

## Google Analytics が Android OS 7 で NPE を起こす
```
java.lang.NullPointerException: Attempt to invoke interface method 'void android.app.job.IJobScheduler.cancel(int)' on a null object reference at android.app.JobSchedulerImpl.cancel(JobSchedulerImpl.java:60)
```
こちらの現象は、com.google.android.gms:play-services-analytics の下記バージョンで発生しています
- 11.6.0
- 11.4.2

~~今のところ対応方法としては、play-services-analytics の更新が配布されるまで、11.2.1 にダウングレードするしかありません~~

~~原因としては、Android 8 からはOS に InstantApps が組み込まれましたが、Android 7 以下のOS では Google Play Services for InstantApps により実現されています。
また、Google Play Services for InstantApps のバージョンによっては、JobScheduler が対応しておらず今回の問題が起きるようです。
こちらは、Google Play Services for InstantApps の問題のようですので、そのうち発生しなくなる問題のようです。~~
#### ※ 修正されたようです。

## xml からのリソースへのアクセスに癖がある
~~こちらは、機能モジュール内おける xml からのリソースアクセス（R.drawable, R.string, etc...）は base モジュールの R を参照してしまうという地味ですが、厄介な問題がありました。
こちらは、ビルド時は問題なく通りますが、実行時になって、feature.R.drawable.icon を参照してほしいところが base.R.drawable.icon を見に行ってしまうため、別の画像が表示されたり、Resource Not Found でアプリがクラッシュしてしまうということがありました。~~

```feature/layout/hoge.xml
<FrameLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@id/layout"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@drawable/bg_image" />
```

~~今のところ対応方法としては、機能モジュールのソースコードから feature.R.drawable.icon を取得して、xml 内の View に適用しなければなりません。~~

```java:MainActivity.java
FrameLayout layout = findViewById(com.example.feature.R.layout);
layout.setBackgroundResource(com.example.feature.R.drawable.bg_image);
```
~~
#### ※ 修正されたようです

## 公開時に気がつく versionCode
Instant Apps の apk の versionCode は、Installed よりも低くなければなりません。
そのため、何も考えずにいると versionCode をあげて公開してしまうと、Instant Apps の apk の修正をリリースするために Installed も versionCode をあげて公開してし直さなければならなくなります。

そのため、Android Instant Apps への対応をする際には、versionCode のルールを先に決めておいたほうがいいです。

## Google Play Console で Android Instant Apps の APK 公開時に、ドメインにマップされている有効な APK がないと言われる。
既存アプリの公開されている xml でに対応したものが公開されていないと下記のエラーが出ます。 
```
ウェブ要素「intent-filter」を介してサイト「xxx.xxx.xx.xx」にマップされている有効な APK が少なくとも１つ必要です
```

このままでは、Instant Apps がリリースできないので、対応させたアプリを公開する必要があるのですが、これは段階公開などで配布率0%でも回避することができるようです。


## Android OS 7 で、WebView は現状使えない（！？）
~~現状、使えないと言い切って問題ないと思います。
というのも、Android OS 7 で WebView を一度でも表示してしまうと、その後、アプリ内のリソースへのアクセスがおかしくなり、Resource Not Found などが発生してクラッシュするようになってしまいます。
Web からアプリに流しているのに、WebView を使うというのもどうかというのはあるんですが、まだ使っているところも多いと思いますので、こちらは修正されるまで使えないと思っていたほうがいいと思います。
代案としては、Custom Tabs を使うなどになります。~~
#### ※ 修正されたようです

# 最後に
Android Instant Apps は、**アプリにとって一番の難関**である**インストールしてもらえなければ、どんなにいい機能を作ったとしてもユーザに使ってもらえない**という点を**解決してくれる可能性**があります。
また、**url がトリガーとなる**ため、**広告などへの相性もいい**です。
実際に広告を踏んで、ストアに行くけどインストールはちょっと・・・というユーザーに対して今までよりもアプローチがしやすいと思います。

Android Instant Apps の対応に必要とする期間は、アプリの規模やユースケースにより様々となるため一概にこのくらいと見積もりにくい点はあります。
対応するかどうか？というのは、ユースケースをよく練った上で判断するのが重要だと思います。
そもそもユースケースを考えると無理に対応しなくても・・・という判断や、PWA でという判断もあると思います。

まだ、**サクッと対応するか！というレベルではない**ものの、今後のアップデートで開発も楽になると思いますし、何よりも対応アプリも増えていくと思いますので、**自分が作っているアプリにあっているのか？だけでも検討**しておいてもいいかと思います。


参考
[googlesamples/android-instant-apps](https://github.com/googlesamples/android-instant-apps)