【Android／Java】OkHTTPでDigest認証 

## やりたいこと
AndroidアプリからDigest認証で通信したい
OkHTTPを使用することとしたらSingletonで作らないと、メモリを食いまくるようなので、それを意識する


## Step1．実装方法
#### app¥build.gradle
~~~gradle
android {
       :(略)
}
dependencies {
       :(略)
    implementation 'com.squareup.okhttp3:okhttp:4.11.0'
    implementation 'io.github.rburgst:okhttp-digest:3.1.0'
}
~~~~

#### AndroidManifest.xml
~~~xml
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools">
    
    <uses-permission android:name="android.permission.INTERNET" />
~~~~

#### OkHttpSingleton.java
Singleton用のクラス
~~~java
public final class OkHttpSingleton {
    private static OkHttpClient mInstance = null;

    /***
     * インスタンス生成(起動時1回)
     * @param context
     */
    public static void createInstance(Context context) {
        if (null == mInstance) {
            OkHttpClient.Builder okHttpBuilder = new OkHttpClient.Builder();
            // 接続タイムアウト時間
            okHttpBuilder.readTimeout(30, TimeUnit.SECONDS);
            okHttpBuilder.connectTimeout(60, TimeUnit.SECONDS);

            // ファイルキャッシュ設定
            long currentTimeMillis = System.currentTimeMillis();
            currentTimeMillis -= 3 * 60 * 60 * 1000;     // キャッシュ有効期限（3時間前のデータ削除）

            int MAX_CACHE_SIZE = 100 * 1024 * 1024;      // 100 MiB
            File cachedFile = new File(context.getCacheDir(), "responses");
            if (cachedFile.exists() && cachedFile.lastModified() < currentTimeMillis) {
                cachedFile.delete();
            }
            Cache cache = new Cache(cachedFile, MAX_CACHE_SIZE);
            okHttpBuilder.cache(cache);

            // Digest認証
            if (context.getResources().getBoolean(R.bool.http_auth)) {
                final String authUser = "your user name";
                final String authPass = "your password";
                final DigestAuthenticator authenticator = new DigestAuthenticator(new Credentials(authUser, authPass));
                final Map<String, CachingAuthenticator> authCache = new ConcurrentHashMap<>();

                okHttpBuilder.addInterceptor(new AuthenticationCacheInterceptor(authCache));
                okHttpBuilder.authenticator(new CachingAuthenticatorDecorator(authenticator, authCache));
            }

            mInstance = okHttpBuilder.build();
        }
    }

    /***
     * インスタンス 取得
     * @return okHttpClientオブジェクト
     */
    public static OkHttpClient getInstance() {
        return mInstance;
    }
}
~~~~

#### OkHttpClientWorker.java
各通信の親クラス定義をWorkerで作成
~~~java
// WorkerManager)
// https://developer.android.com/topic/libraries/architecture/workmanager?hl=ja
// https://qiita.com/naoi/items/e08f081fe42ef3694046
public abstract class OkHttpClientWorker extends Worker {
    private final String TAG = this.getClass().getSimpleName();
    protected Context mContext;
    private String mBaseUrl;

    public OkHttpClientWorker(@NonNull Context context, @NonNull WorkerParameters workerParams) {
        super(context, workerParams);
        mContext = context;
        mBaseUrl = "https://www.hogefuga.com";
    }

    /**
     * HTTP通信要求
     */
    protected abstract void requestHttp();

    /**
     * API Responseに応じた内部処理 実行
     */
    protected abstract void exeWork(JSONObject respJson);


    /***
     * GET通信 パラメータ 生成/取得
     * @param params 通知パラメータ
     * @return URI   例)http://xxxx.co.jp/hoge/fuga
     *                                   ~~~~~~~~~~
     */
    private String getQuery(JSONArray params) {
        StringBuilder retQuery = new StringBuilder();
        try {
            for (int cnt = 0; cnt < params.length(); cnt++) {
                Iterator<String> keys = params.getJSONObject(cnt).keys();
                while (keys.hasNext()) {
                    String key = keys.next();
                    String value = params.getJSONObject(cnt).getString(key);
                    retQuery.append("/").append(value);
                }
            }
        } catch (Exception e) {
            retQuery = new StringBuilder();
        }
        return retQuery.toString();
    }

    /***
     * POST通信 パラメータ 生成/取得
     * @param params 通知パラメータ
     * @return ResponseData
     */
    private RequestBody postQuery(JSONArray params) {
        RequestBody requestBody = null;
        try {
            MediaType MIMEType = MediaType.parse("application/json; charset=utf-8");
            // JSONArray → String化
            StringBuilder postData = new StringBuilder();
            for (int cnt = 0; cnt < params.length(); cnt++) {
                postData.append(params.get(cnt)).append(",");
            }
            if (1 < postData.length()) {
                // POSTデータがない場合は、削除しない
                postData.deleteCharAt(postData.length() - 1);   // 最後のカンマ削除
            }
            requestBody = RequestBody.Companion.create(postData.toString(), MIMEType);
        } catch (Exception e) {
        }
        return requestBody;
    }

    /**
     * GET通信
     *
     * @param url   リクエストURL
     * @param param リクエストパラメータ
     */
    protected void requestGet(String url, JSONArray param) {
        final String urlStr = mBaseUrl + url + getQuery(param);
        Request request = new Request.Builder()
                .url(urlStr)
//              .addHeader("User-Agent", "hoge")
                .get()
                .build();
        exeRequestHttp(request);
    }

    /**
     * POST通信
     *
     * @param url   リクエストURL
     * @param param リクエストパラメータ
     */
    protected void requestPost(String url, JSONArray param) {
        url = mBaseUrl + url;
        final RequestBody postData = postQuery(param);
        Request request = new Request.Builder()
//              .addHeader("User-Agent", "hoge")
                .url(url)
                .post(postData)
                .build();

        exeRequestHttp(request);
    }

    /***
     * HTTP通信 実行
     * @param request リスエストデータ
     */
    private void exeRequestHttp(Request request) {
        OkHttpClient client = OkHttpSingleton.getInstance();
        // 非同期通信
        client.newCall(request).enqueue(new Callback() {
            @Override
            public void onFailure(Call call, IOException e) {
                // ResponseをJSON形式へ
                JSONObject respJson = new JSONObject();
                respJson.put("result", false);
                respJson.put("message", e.getMessage());

                exeWork(respJson);
            }

            @Override
            public void onResponse(Call call, Response response) {
                try {
                    // ResponseをJSON形式へ
                    final String resp = response.body().string();
                    JSONObject tmp = new JSONObject(resp);


                    JSONObject respJson = new JSONObject();
                    respJson.put("result", true);
                    respJson.put("data", tmp.getString("data"));

                    exeWork(respJson);
                } catch (IOException | NullPointerException e) {
                }
            }
        });
    }
}
~~~~

#### SampleRequest.java
OkHttpClientWorkerを継承した各通信用のクラス
~~~java
public class SampleRequest extends OkHttpClientWorker {
    private final String TAG = this.getClass().getSimpleName();
    private Integer mSearchId;

    /**
     * コンストラクタ
     */
    public SampleRequest(@NonNull Context context, @NonNull WorkerParameters workerParams) {
        super(context, workerParams);
    }

    /**
     * HTTP要求
     */
    @Override
    protected void requestHttp() {
        try {
            JSONArray request = new JSONArray();
            JSONObject tmp = new JSONObject();
            tmp.put("searchId", mSearchId);
            request.put(tmp);

            String url = "/search/user";
            requestGet(url, request);
        } catch (Exception e) {
        }
    }

    /**
     * API Responseに応じた内部処理 実行
     *
     * @param respJson
     */
    @Override
    protected void exeWork(JSONObject respJson) {
        try {
            do {
                if (respJson.getBoolean("result")) {
                    if (respJson.isNull("data")) {
                        break;
                    }
                    JSONObject jsonObject = respJson.getJSONObject("data");

                    String value1 = jsonObject.getString("value1");
                    String value2 = jsonObject.getString("value2");
                }
            } while (false);
        } catch (Exception e) {
        }
    }


    /**
     * WorkerThread HTTP通信
     */
    @NonNull
    @Override
    public Result doWork() {
        mSearchId = getInputData().getString("searchId");
        requestHttp();  // HTTP通信
        return Result.success();
    }
}
~~~~
#### MainActivity.java
~~~java
public class MainActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        // HTTP通信
        WorkManager manager = WorkManager.getInstance(this);
        Data inputData = new Data.Builder()
            .putString("searchId", 1)
            .build();

        WorkRequest SampleRequest =
            new OneTimeWorkRequest.Builder(SampleRequest.class)
                .setInputData(inputData)
                .build();
         manager.enqueue(SampleRequest);
    }
}
~~~~

### 解説
1. OkHttpSingleton.java: OkHTTPのSingletonクラスを作成。このままコピペで使えると思う
1. OkHttpClientWorker.java: 各通信に対しての親クラス。abstractメソッドで実装の強制化している。exeRequestHttp()メソッド内のResponse制御は、各通信に合わせてカスタマイズすれば使えると思います
1. SampleRequest.java: 上記クラスを継承した各通信に特化したクラス。通信する内容ごとに、このようなクラスを作成する。WorkManagerを継承しており、以下の順序で呼ばれる
【呼び出し順】
・SampleRequest.java:doWork()
・SampleRequest.java:requestHttp()
・OkHttpClientWorker.java:requestGet()/requestPost()
・OkHttpClientWorker.java:exeRequestHttp()
　　　　↓
・SampleRequest.java:exeWork()
1. MainActivity.java: 実際に上記をクラスを使った通信の実行


### 最後に
おじさんは、Digest認証でめちゃくちゃハマッたので何かの役に立てたら幸いです。