注意! この記事はQiitaにて公開されていた内容をimportしたものです。
これらの内容は場合によっては陳腐化していて役に立たなくなっていたり、有害であったり、現在の著者の主張と異なることがあります。
皆様の判断の上でご利用いただけますと幸いです(度を超してヤバいものは著者に連絡して頂ければ対応します m(_ _)m)


はじめに

これまでAndroidネイティブアプリを主に担当していた自分が、紆余曲折を経てReact Nativeを担当することとなりました。 まず、今年(2018年)11月までに targetSdkVersion 26 対応をする必要があったので、すぐさま対応することとなりました。

ただ、問題がないならできるだけtargetSdkVersionをあげておきたかったので、targetSdkVersion 28にしつつ、諸々の対応をしていくこととなりました。

Pie環境でデバッグが動かない

ひとまずAndroid9環境をエミュレーターで用意し、 react-native run-android しましたが、以下のエラーが表示されます。

    unknown:ReactNative  E  Exception in native call
                         E  java.lang.RuntimeException: Unable to load script from assets 'index.android.bundle'. Make
                             sure your bundle is packaged correctly or you're running a packager server.
                         E      at com.facebook.react.bridge.CatalystInstanceImpl.jniLoadScriptFromAssets(Native Metho
                            d)
                         E      at com.facebook.react.bridge.CatalystInstanceImpl.loadScriptFromAssets(CatalystInstanc
                            eImpl.java:216)
                         E      at com.facebook.react.bridge.JSBundleLoader$1.loadScript(JSBundleLoader.java:33)
                         E      at com.facebook.react.bridge.CatalystInstanceImpl.runJSBundle(CatalystInstanceImpl.jav
                            a:234)
                         E      at com.facebook.react.ReactInstanceManager.createReactContext(ReactInstanceManager.jav
                            a:1137)
                         E      at com.facebook.react.ReactInstanceManager.access$900(ReactInstanceManager.java:113)
                         E      at com.facebook.react.ReactInstanceManager$5.run(ReactInstanceManager.java:944)
                         E      at java.lang.Thread.run(Thread.java:764)

おおーん?これでかなり悩みました。 チームメンバーはこの問題にめったに遭遇しないと言うし、別の実機(あとで気づいたのですが実機はAndroid8以下です)では正常に動作する。 しかし、Pie環境でどうしても動作確認をしておきたかった。

react-native bundle コマンドで対応する方法 もあるんですが、これは実質React Nativeのビルドチェーンでやってくれていることを自分でやっているだけだったので、とりあえず無理矢理動かす時だけ使いました。

結論: networkSecurityConfig の未指定が原因

Android Nからの機能で Network Security Configuration があります。 当初はクリアテキスト トラフィック(非HTTPS通信)を認めていたのですが、Android Pieからめでたく(不幸にも?w)クリアテキストをデフォルトでブロックするようになりました。

Network Security Configurationのデフォルトの値を見比べると、確かにPie (API 28)から cleartextTrafficPermitted="false" となっています。

そして、React Nativeのデバッグビルドでは、スマホの実機(エミュレーター)と母艦(パソコン)間で通信をします。 その通信がクリアテキストで行われるため、通信がブロックされている、という状況です。

なお、この状態は(↑でもちらっと書きましたが)、現時点においては、Android Pie環境かつアプリが targetSdkVersion 28 となっているときに発動します。

解決策: networkSecurityConfigを実装する

コードです。

...

<application
        android:name=".MainApplication"
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:theme="@style/AppTheme"
        android:largeHeap="true"
        android:networkSecurityConfig="@xml/network_security_config"
        >

...

android:networkSecurityConfig<application>に記述します。

<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
    <domain-config cleartextTrafficPermitted="true">
        <!-- for React Native Hot-reloading system -->
        <domain includeSubdomains="true">localhost</domain>
    </domain-config>

    <base-config cleartextTrafficPermitted="false" />
</network-security-config>

network_security_config.xml の中身はこれ。 includeSubdomainsはtrueにしてありますが、falseでも良いかも知れません。

他にもクリアテキストな通信が発生するなら、いろいろ定義を足す必要があります。 (今回の対応の際に、やむを得ずクリアテキストな通信が発生する箇所がブロックされていることに気がつきましたが、iOSでは先んじてクリアテキストの制限があったため、迂回するためのホスト定義をコピーして対応出来ました)

まとめ

まさか networkSecurityConfig が原因でダメになっているとは夢にも思わず、原因究明がめちゃ遅れました。 React Nativeはバージョン毎に対応しているtargetSdkVersionが明示されているので、そこから逸脱しないようにしていれば大やけどしない・・・かも知れない。