やりたいこと
AndroidアプリからDigest認証で通信したい
OkHTTPを使用することとしたらSingletonで作らないと、メモリを食いまくるようなので、それを意識する
Step1.実装方法
app¥build.gradle
android {
:(略)
}
dependencies {
:(略)
implementation 'com.squareup.okhttp3:okhttp:4.11.0'
implementation 'io.github.rburgst:okhttp-digest:3.1.0'
}
AndroidManifest.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用のクラス
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で作成
// 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を継承した各通信用のクラス
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
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);
}
}
解説
- OkHttpSingleton.java: OkHTTPのSingletonクラスを作成。このままコピペで使えると思う
- OkHttpClientWorker.java: 各通信に対しての親クラス。abstractメソッドで実装の強制化している。exeRequestHttp()メソッド内のResponse制御は、各通信に合わせてカスタマイズすれば使えると思います
- SampleRequest.java: 上記クラスを継承した各通信に特化したクラス。通信する内容ごとに、このようなクラスを作成する。WorkManagerを継承しており、以下の順序で呼ばれる
【呼び出し順】
・SampleRequest.java:doWork()
・SampleRequest.java:requestHttp()
・OkHttpClientWorker.java:requestGet()/requestPost()
・OkHttpClientWorker.java:exeRequestHttp()
↓
・SampleRequest.java:exeWork() - MainActivity.java: 実際に上記をクラスを使った通信の実行
最後に
おじさんは、Digest認証でめちゃくちゃハマッたので何かの役に立てたら幸いです。