技術と趣味の亜空間

主にゲームプログラミングとその周辺に関する記事を不定期で上げていきます

ネイティブアプリのAPI更新対応備忘録 〜Android編〜

Android Robot Logo

概要

iOSに引き続きAndroidの方も修正の備忘録を記す。
AndroidのminSDKは元々14で、今回はminSDK19まで引き上げることにした。(本当は21まで上げれば色々と幸せになるが、もろもろ諸事情を天秤にかけた結果なので仕方なし)
対応時のAndroid Studioバージョン : 4.0

gradle更新

まずはこれをしないと始まらない。
バージョンが3.1.4だったので、4.0.0まで引き上げた。特別な対応はしておらず、Android Studioの指示に従って更新をかけた。
更新後はAndroid ManifestでminSDKバージョン記載するなと警告が表示されたので削除してGradleファイルの方に記載。
また、 .idea フォルダに jarRepositories.xml なるファイルが自動生成されたが、gitに上げるかどうかは調べた限り不要そうだったので gitignore に追加。

FacebookSDK更新

こちらも version 3 時代と相当古く、かつフレームワークがローカルフォルダ参照だったのでこれを削除し、最新の公式ドキュメントに沿って mavenCentral から動的にインポートする形式に変更。
また、更新によってコード内にて宣言されているimport文の名前空間が変わっていたので修正。

Before

import com.facebook.Settings;

// 中略

if (Settings.isDebugEnabled()) {
    // hogehoge
}

After

import com.facebook.FacebookSdk;

// 中略

if (FacebookSdk.isDebugEnabled()) {
    // hogehoge
}

またSDKの初期化方法も変化していたので修正。
具体的には AppEventsLogger.activateApp()onResume() から onCreate() で呼び出すように変更し、AppEventsLogger.deactivateApp() は呼び出す必要が無くなったので削除。

Before

@Override
protected void onResume() {
    AppEventsLogger.activateApp(this.getApplicationContext());
    AppEventsLogger.deactivateApp(this);
}

After

@Override
public void onCreate(final Bundle savedInstanceState) {
    AppEventsLogger.activateApp(getApplication());
}

Facebook ダッシュボードの設定更新

Facebookダッシュボードもv3で設定したっきりで、色々と警告が表示されていた。(主にセキュリティ面)
いきなり本番アプリの設定をいじるのはやばいので、サンボックス版で設定を更新して検証。
Facebookログイン設定において「リダイレクトURIに制限モードを使用」がOFFになったのでONにしたところ、OFFに戻すことができなくなってしまった。
どうやら現在では強制的にONになるらしく、当プロジェクトは単純に設定が古すぎたのが原因。
気をつけなければいけないのは、ONにしたら「有効なOAuthリダイレクトURI」という項目にURI設定を記載しないといけなくなる点。
これが設定されてないとFacebookでのアカウント連携や引き継ぎ等はエラーでできなくなるので注意。

AndroidXへ移行

すでにサポートが終了しているAndroidサポートライブラリをAndroidXへ移行させた。
Android Studioには自動で移行してくれる機能があるのでそれを利用した。
プロジェクトによっては破壊的変更もあるだろうが、当プロジェクトではそんなつ使っておらず、影響は微小だった。

developer.android.com

傾き探知

端末の傾きを探知するAPIが deprecated だったので修正。
いろいろ調べたが、正直よく分わからんまず地磁気センサと加速度センサの値から、回転行列を作成。
その値をシステムに合った座標軸系へ行列変換させ(下図では outRotationMatrix に変換後の値が渡る)、最後の getOrientation メソッドで傾き情報を取得という方法で対処。

Before

private SensorManager sensorManager;
private Sensor sensor;

@Override
public void onCreate(final Bundle savedInstanceState) {
    sensorManager = (SensorManager)getSystemService(Context.SENSOR_SERVICE);
    sensor = sensorManager.getDefaultSensor(Sensor.TYPE_ORIENTATION);
    sensorManager.registerListener(this, sensor, SensorManager.SENSOR_DELAY_NORMAL);
}

/**
 * センサーの値が変化すると呼ばれる
 * @param event センサー値
 */
public void onSensorChanged(SensorEvent event) {
    int type = event.sensor.getType();
    if (type == Sensor.TYPE_ORIENTATION) {
        float[] mOrientationData = new float[3];
        mOrientationData = event.values.clone();
        float pitch = mOrientationData[2];
    }

     // pitchで傾き判定処理
}

After

private SensorManager sensorManager;

// 回転行列
private float[] rotationMatrix = new float[9];
private float[] outRotationMatrix = new float[9];
private float[] gravity = new float[3];
private float[] geomagnetic = new float[3];
private float[] attitude = new float[3];

@Override
public void onCreate(final Bundle savedInstanceState) {
    sensorManager = (SensorManager)getSystemService(Context.SENSOR_SERVICE);
}

@Override
public void onCreate(final Bundle savedInstanceState) {
    if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
        Sensor accelerometer = sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
        if (accelerometer != null) {
            sensorManager.registerListener(this, accelerometer, SensorManager.SENSOR_DELAY_NORMAL, SensorManager.SENSOR_DELAY_UI);
        }
        Sensor magneticField = sensorManager.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD);
        if (magneticField != null) {
            sensorManager.registerListener(this, magneticField, SensorManager.SENSOR_DELAY_NORMAL, SensorManager.SENSOR_DELAY_UI);
        }
    } else {
        sensorManager.registerListener(this, sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER), SensorManager.SENSOR_DELAY_UI);
        sensorManager.registerListener(this, sensorManager.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD), SensorManager.SENSOR_DELAY_UI);
    }
}

/**
 * センサーの値が変化すると呼ばれる
 * @param event センサー値
 */
public void onSensorChanged(SensorEvent event) {
    switch(event.sensor.getType()){
        case Sensor.TYPE_MAGNETIC_FIELD:
            geomagnetic = event.values.clone();
            break;
        case Sensor.TYPE_ACCELEROMETER:
            gravity = event.values.clone();
            break;
    }

    // 端末画面の向きを計算する
    if (geomagnetic != null && gravity != null) {

        SensorManager.getRotationMatrix(
            rotationMatrix, null,
            gravity, geomagnetic);
        // アプリケーション座標に変換
        SensorManager.remapCoordinateSystem(rotationMatrix, SensorManager.AXIS_Y, SensorManager.AXIS_X, outRotationMatrix);
        SensorManager.getOrientation(
            outRotationMatrix,
            attitude);

        int pitch = radianToDegree(attitude[1]);
        //Debug.out("方位角: " + attitude[0] + ", 傾斜角: " + attitude[1] + ", 回転角: " + attitude[2] + ", pitch: " + pitch);
        
        // pitchで傾き判定処理
    }
}

/**
 * ラジアンを度に変換する
 * @param rad ラジアン
 * @return
 */
int radianToDegree(float rad){
    return (int) Math.floor( Math.toDegrees(rad) ) ;
}

参考サイト:

Handler派生クラスのリーク警告修正

Handlerクラスで、以下のような警告が表示されていたので修正。

This Handler class should be static or leaks might occur.

コード内容は正確ではないが、だいたい以下のような作りを修正しました。

Before

public class TestActivity {
    Handler handler = new Handler() {
          @Override
          public void handleMessage(@NonNull Message msg) {
            Debug.out("check hoge");
          }
    };


    private void hoge {
        Message msg = new Message();
        Bundle b = new Bundle();                      
        b.putString("o___O nothing", "to say");
        msg.setData(b);

        handler.sendMessage(msg);
    }
}

After

import java.lang.ref.WeakReference;

public class TestActivity {
    private final static int MSG_POST_LOGIN = 1;
    private TestHandler handler = new TestHandler(this);

    private static class TestHandler extends Handler {
        private final WeakReference<TestActivity> mTestActivity;

        TestHandler(TestActivity testActivity) {
            mTestActivity = new WeakReference<>(testActivity);
        }
        @Override
        public void handleMessage(@NonNull Message msg) {
            TestActivity mTest = mTestActivity.get();
            if (mTest != null) {
                if (msg.what == MSG_POST_LOGIN) {
                    Debug.out("check hoge");
                }
            }
        }
    }

    private void hoge {
        handler.sendEmptyMessage(MSG_POST_LOGIN);
    }
}

参考サイト:
Handlerのリーク警告を解決するには - outcesticide

ハードウェアID取得の廃止

TelephomyManagerクラスで取れるハードウェア周りのAPI自体は結構前にdeprecatedになっていたが、Android 10 (targetSDK = 29)でいよいよ完全に取得できなくなった。
具体的には下記テーブルを参照してほしいが、アプリ側のtargetSDKが29以上の場合、ハードウェア系のAPIを呼び出すと SecurityException を吐くようになる。
(まぁ、アプリ側のターゲットビルドが28以下でもインストールした側が29以上だと null を返すのでアレなのは変わりない…)

API targetSDK >= 29 targetSDK < 29
getImei SecurityException null
getSubscriberId SecurityException null
getDeviceId SecurityException null
getMeid SecurityException null
getSimSerialNumber SecurityException null

ストア側のtargetSDKは現時点でまだ28だが、おそらく例年通りなら11月あたりに29になるので、それまでに対応しないといけない。
自プロジェクトではハードウェアIDをキーとしてUUIDを生成していたので、この方法がいよいよ完全に使えなくなってしまうので、その代替案としてFirebaseのインスタンスIDで対応することにした。
この方法はGoogleも推奨しているので大丈夫だろう。
ただ、このインスタンスIDはハードウェアIDと違ってアプリをアンインストールすると変わってしまうため、認証処理周りは作り変える必要がある。

developer.android.com

参考サイト:

Android ロボットは、Google が作成および提供している作品から複製または変更したものであり、クリエイティブ・コモンズ表示 3.0 ライセンスに記載された条件に従って使用しています