【Android開発】webviewからajaxでアクセスして動かないとき

Androidのwebviewを使って開発サーバーにajaxでアクセスして、サーバーからもらったデータを画面に表示させるアプリを作ることにした。
が、ローカルでは動いているのにjs側から下記のようなエラーが出ていてアクセスできない。

D/MyApplication★: {"readyState":0,"status":0,"statusText":"error"} -- From line 40 of 

結論からいうと、js、ajaxの設定きちんとしていてもhttpでのアクセスを許可していないと上記のエラーが出て通信が切断されるようだ。

参考

Android の WebView 内で ajax が動かない!4つの対策 | PisukeCode - Web開発まとめ
Androidでアプリ開発しててWebView内で "なぜか" Ajax が動かないトラブルに遭遇。でも解決策が分かったので、動かない原因と対処法を色々まとめてみました。
QuickAnswer
コンピュータプログラミング関連の備忘録。同じ問題でお困りの方の手助けになれば幸いです。
Android PでtargetSdkVersionを28に指定した場合にHTTP通信が失敗する - Qiita
弊社優秀なエンジニアが解決していただいて自分が代わりに書きます。 HTTP通信に失敗する APIを叩いた時に下記のエラーが出力されました。 java.net.UnknownServiceException: CLEARTEXT...

jsを許可してajax通信するまでのコード

AndroidManifest.xml

src\main\AndroidManifest.xml
インターネットアクセスのパーミッションの設定は追加してある、ないとネットアクセスできないらしい。

<uses-permission android:name="android.permission.INTERNET"/>

MainActivity.java

myWebViewという名前のWebViewを作って表示させている、jsの有効化、consoleでのログをAndroidStudio側への吐き出しも設定。

    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Intent intent = getIntent();

        myWebView = (WebView) findViewById(R.id.webView);
        myWebView.setWebViewClient(new WebViewClient());
        // assetsフォルダ内のindex.htmlファイルを指定
        myWebView.loadUrl("file:///android_asset/index.html");

        // WebView内のJavaScriptの実行を許可
        myWebView.getSettings().setJavaScriptEnabled(true);
        myWebView.getSettings().setDomStorageEnabled(true);

        // jsのconsole.logの中身をAndroid側のコンソール(Runタブ)に書き出す設定(見つけやすくするために★マークを入れた)
        myWebView.setWebChromeClient(new WebChromeClient() {
            public boolean onConsoleMessage(ConsoleMessage cm) {
                Log.d("MyApplication★",
                        cm.message() + " -- From line " + cm.lineNumber()
                                + " of " + cm.sourceId());
                return true;
            }
        });
    }

ajax

サーバーは前回作ったvagrantのLAN内公開サーバーなのでそのURLを使っている。

【Vagrant】LAN内の別のパソコン、スマホからアクセスできるようにする
前似たような記事を書いた気がするんだけど見つからないのでメモ。 せっかくなので環境ごと作成してアクセスできるまでを書いた。 環境 【OS】Microsoft Windows 10 Home 【Vagrant】Vagrant2.2...

src\main\assets\script.js

jQuery(function($){
    //アクセスするURL
    const URL = "http://192.168.0.111/api.php";
    
    let menuList = null;
    $.ajax({
        url: URL, 
        type: 'get',
        cache: false,
        dataType:'json',
        data: null
    }).done(function(res) {
        // 通信成功時の処理
    }).fail(function(xhr) {
        // xhrの中身はJavaScript のオブジェクトなので、JSON文字列に変換して中身が見えるようにする
        console.log(JSON.stringify(xhr));
        alert("E001:通信時にエラーが発生しました");
    }).always(function(xhr, msg) {
        //通信完了時の処理
    });
});

サーバー側php(api.php)

jsonでステータスとメッセージ返すだけの内容になっている。

<?php
header("Access-Control-Allow-Origin: *");
echo json_encode(["status"=>"OK","message"=>"hoge"]);
die();

試したこと

ajaxを同期通信にすると動いたという記事を見つけたので「async: false」としてみる。

 
       $.ajax({
            url: URL, //アクセスするURL
            type: 'post',
            cache: false,
            dataType:'json',
            data: jsonObj,
            async: false, //非同期通信をオフ
            timeout: 1000 //タイムアウト1秒
        }).done(function(res) {

これをやると下記エラーが出る。
Androidはメインスレッドでの同期通信は非推奨らしいので、やらないほうがいいようだ。

D/MyApplication: Synchronous XMLHttpRequest on the main thread is deprecated because of its detrimental effects to the end user's experience. For more help, check https://xhr.spec.whatwg.org/. -- From line 2 of file:///android_asset/lib/jquery-3.5.1.min.js
D/MyApplication: {"readyState":0,"status":0,"statusText":"NetworkError: Failed to execute 'send' on 'XMLHttpRequest': Failed to load 'http://192.168.0.111/api.php'."} -- From line 41 of file:///android_asset/script.js

jsは動いているし、応答の速さ的にサーバーに行く前にAndroid内で切断されているみたいだったので、webview内直接apiのURLに飛ばすことにした

jQuery(function($){
    location.href = "http://192.168.0.111/api.php";
});

するとAndroid本体の方で下記のエラーが出る。

ウェブページへのアクセス不可
ウェブページ(http://192.168.0.111/api.php)は次の理由で読み込めませんでした:

net::ERR_CLEARTEXT_NOT_PERMITTED

これはhttpへのアクセスだから起きる問題らしい。
暗号化されていない通信(httpsでない通信)はデフォルトで拒否されるようだ。
下記のように修正すると、httpsでもアクセスできるらしいので変更してみる。

が、変わらずエラーが出る。(この方法APIレベルによって効かないみたい、今回はAPIレベル30だった)
src\main\AndroidManifest.xml

<application
            android:usesCleartextTraffic="true">

解決

「network_security_config.xml」にドメインレベル(IPそのまま書いてるけどドメインでも大丈夫)で個別許可を書くことにする。
(ファイルがない場合は作成する)
src\main\res\xml\network_security_config.xml

<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
    <domain-config cleartextTrafficPermitted="true">
        <domain includeSubdomains="true">192.168.0.111</domain>
    </domain-config>
</network-security-config>

無事動いた…。

おわり。