- 投稿日:2021-10-31T18:26:57+09:00
Android11対応と2021年対応でやったこと (RSSリーダーアプリ編)
前回 はじめに 今日はハロウィン?ですが明日11/1は毎年恒例のAndroidアプリの要件変更日となります。 2021 年 11 月より、既存アプリのアップデートでターゲットAPIレベル30以上およびAndroid 11の動作の変更点への対応が必須になります。 - ターゲットAPIレベル30(Android 11)以上、および動作の変更点に対応する。 - Android App Bundle 形式で公開する - ダウンロードサイズが150MBを超えるアセットや機能は、Play Asset Delivery または Play Feature Delivery を使って配信 今年は課金ライブラリ締め切りも同日です。 Google Play Billing Library バージョン 3 のご紹介 2021年8月2日より新規リリースのアプリでは最新の Billing Library バージョン3への対応が必要となり、11月1日までにすべてのアプリにその対象が広がるため、再度掲載します。 対象アプリ この記事は以下のアプリを対象に1年でやったことのメモ書きをまとめたものです。 Android11変更内容の全てを網羅しているわけではありません。 項目 内容 通信機能 あり. 外部ホストのhttp,https通信もあり 課金 定期購読 あり 通知 Local Notification のみ データ管理 Sqlite, Shared Preference ファイル操作 Storage Access Framework のみ 主なライブラリ AndroidX, Firebase, Dagger, Retrofit, Espresso, Truth etc 状況 2020年末時点でtargetSdkVersion:29, apkで配布 targetSdkVersion:30対応 パッケージ公開設定 まず動作しなくなったのがIntent周りでした。パッケージ公開設定をAndroidManifestに追加しました。 https://developer.android.com/training/package-visibility/use-cases?hl=ja AndroidManifest.xml <queries> : <intent> <action android:name="android.intent.action.OPEN_DOCUMENT" /> </intent> <intent> <action android:name="android.intent.action.CREATE_DOCUMENT" /> </intent> <intent> <action android:name="android.intent.action.TTS_SERVICE" /> </intent> </queries> AsyncTask非推奨 targetSdkVersion 30からAsyncTaskは非推奨になりました。 アプリで一部残っており、全てCoroutineで充分だったのでCoroutineに移行しました。 その他 他、以下資料読んで対応ないことを確認しました。 対応必須のアプリ多くあると思うのでリンクを貼っておきます。 動作の変更点: Android 11 をターゲットとするアプリ 動作の変更点: すべてのアプリ App Bundle対応 モジュール化未対応より、ビルド時にApp Bundleを変更して完了しました。 https://developer.android.com/guide/app-bundle/test 念の為上記テストを行うため bundletool をインストールしました。 abbはそのまま端末にインストールできないため、以下apksにする必要があります。 bundletool install-apks --apks=/MyApp/my_app.apks 現在開発版は引き続きapkを使用しています。 Billing Library 対応 1.0から3.0に更新 基本的には以下手順に沿っただけです。 AIDL→v1のときより資料が充実していたのでv1対応のときよりスムーズに対応可能でした。 https://developer.android.com/google/play/billing?hl=ja ktx版があったのでそちらを使用しました。 build.gradle - implementation 'com.android.billingclient:billing:1.0' + implementation 'com.android.billingclient:billing-ktx:3.0.1' その他の対応として、アプリから「定期購入を管理」に遷移できるようにアプリからリンクを貼りました。 **Config.kt const val SUBSCRIPTION_URL = "https://play.google.com/store/account/subscriptions"; const val SUBSCRIPTION_DEEPLINK_URL = "https://play.google.com/store/account/subscriptions?sku=%s&package=%s"; 3.0から4.0に更新 Google Play Billing Library リリースノート 2021年5月にBilling Library 4.0が公開されたのでさらに対応しました。 Purchaseクラスのskuがリスト形式になったのでStringからList<String>に修正したのみで済みました。 Purchase#getSkus()とPurchaseHistoryRecord#getSkus()を追加しました。これらは、削除されたPurchase#getSkuとPurchaseHistoryRecord#getSkuに代わるものです。 DaggerをHiltに変更 2021年5月にHilt 1.0がリリースが公開されたので置き換えました。 Android Studio を数回更新した ViewBinding 定義方法が少し変わりました。 https://developer.android.com/topic/libraries/view-binding/migration build.gradle - viewBinding { - enabled = true + buildFeatures { + viewBinding true 2020.3で日本語フォントがTOFUになった Preference > Appearance > User Custom Font をONにして解決 2020.3でコマンドラインでタスク実行できなくなった コマンドライン実行時のJavaが8になっていたので環境変数でJDK11を設定しました 2020.3でユニットテストが通らなくなった What's new in Android testing tools テスト実行方式が変更になりました。前バージョンで作成したユニットテストのRun Configurationを削除して新規に作成しました。 Kotlin 1.5 にした https://blog.jetbrains.com/ja/kotlin/2021/05/kotlin-1-5-0-released/ Hiltのエラーがでたので対応 error.log error: [Hilt] Unsupported metadata version. Check that your Kotlin version is >= 1.0: java.lang.IllegalStateException: Unsupported metadata version. Check that your Kotlin version is >= 1.0 at dagger.internal.codegen.kotlin.KotlinMetadata.metadataOf(KotlinMetadata.java:206) at Hiltのバージョンを最新版に更新したら解決しました build.gradle // Hilt implementation 'com.google.dagger:hilt-android:2.35.1' kapt 'com.google.dagger:hilt-android-compiler:2.35.1' kaptTest 'com.google.dagger:hilt-android-compiler:2.35.1' jcenter廃止対応 古いAndroidプロジェクトだとデフォルトで設定されています。 対象アプリではサードパーティ製のjcenterのライブラリ使用していなかったので以下で完了でした。 Android Studio 4.2だとエラーがでましたが2020.3更新で行うと正常に移行しました。 https://jfrog.com/blog/into-the-sunset-bintray-jcenter-gocenter-and-chartcenter/ https://developer.android.com/studio/releases/gradle-plugin?hl=ja build.gradle - jcenter() + mavenCentral() その他ライブラリの警告対応 RecyclerView#notifyDataSetChanged リストの追加・変更・削除のどれが行われたのか明確にしました。 AndroidXのRecyclerViewのライブラリを更新したら警告出たので対応しました。 **Fragment.kt - recyclerAdapter.notifyDataSetChanged() + recyclerAdapter.notifyItemRangeChanged(positionStart, itemCount) アニメーションが不要だったので消して使用しています。 **Fragment.kt (binding.recyclerView.itemAnimator as SimpleItemAnimator).supportsChangeAnimations = false Activity#onActivityResult アクティビティの結果を取得する AndroidXのActivityのライブラリを更新したら警告出たので対応しました。 **Activity.kt val getContent = registerForActivityResult(GetContent()) { uri: Uri? -> // Handle the returned Uri } Espresso 3.4.0にしたらDuplicateエラー Truthを使っており、バージョンを最新にしたら解決しました。 build.gradle // Truth testImplementation 'com.google.truth:truth:1.1.3' androidTestImplementation 'com.google.truth:truth:1.1.3' Google Play対応 アプリ内容の申告 今年、認証情報が不要なこと、ニュースアプリでないこと、COVID-19アプリではないことを申告しました。 サービス手数料15%申請 Play Consoleヘルプ/ポリシーセンター/サービス手数料 手数料が30%から15%になる申請をしました。2022年1月に自動的適用となるそうです。 今後 Google Play PolicyBytes - 2021年10月のポリシーの更新 (Japanese) Android DeveloperのYouTubeチャンネルに動画が上がっていましたが絵文字互換性対応、プライバシーポリシーの必須化やデータセーフティ申請も近づいているようです。(対応中です!) さいごに 今年は個人的にはAndroid11対応は少なめでしたがその分非推奨のライブラリ対応を行ったイメージです。Android12対応を今やっていますがバックアップ対応、PendingIntent対応、Material You対応など盛りだくさんでだいぶ大変になる予感です。。
- 投稿日:2021-10-31T11:59:06+09:00
JNIでNullableなint値を受け取り、jobjectからint値に変換する方法
概要 JNIを使用したAndroidのアプリをKotlinで作成しているとき、JNIの関数にNullableのKotlinのInt値を渡したいということがあった。 Nullableでない値を渡す場合であればネイティブ側でjintとして受け取るところなのだが、Nullableの場合だとエラーが発生してjintは使用できなかった。 そのため、Nullableな値をネイティブ側でjobjectとして受け取った後にjobjectからint値に変換するということを行ったのでその方法を記載する。 JNIでjobjectからint値に変換する方法 以下のようにすることでjobjectをint値に変換することができる。 // JNI関数の定義はKotlin側の別の場所で以下のように定義されている external fun sampleFunc(value: Int?): Int // ネイティブ側のコード // サンプルなので関数名は適当かつ、int値を返すだけのコード JNIEXPORT jint JNICALL con_example_app_sampleFunc(JNIEnv *env, jobject nullableValue) { if (nullableValue != nullptr) { auto intClass = env->FindClass("java/lang/Integer"); auto intValueMID = env->GetMethodID(intClass, "intValue", "()I"); auto intValue = env->CallIntMethod(nullableValue, intValueMID); return intValue } return -1; } また、今回はKotlinのInt値が渡したが応用としてCallIntMethodをCallFloatMethodやCallBooleanMethodなどの別の関数にすることでFloat値やBool値の場合でも同じような事ができる。
- 投稿日:2021-10-31T11:35:24+09:00
Androidでキーボードを閉じられたのを検知する方法
概要 AndroidのEditTextを入力時、戻るボタンを押してキーボードを閉じたときに特定の処理を行いたいことがあった。 そのため、Androidで表示されているキーボードが閉じられたのを検知する方法を記載する。 言語はKotlin キーボードが閉じられたのを検知する方法 以下のようにすることで、キーボードが戻るボタンで閉じられたのを検知できる。 // EditTextを継承したクラスで以下を定義 override fun onKeyPreIme(key_code: Int, event: KeyEvent): Boolean { if(key_code == KeyEvent.KEYCODE_BACK && evnet.action == KeyEvent.ACTION_UP) { // TODO キーボードを閉じたときに実行したい処理を書く } return super.onKeyPreIme(key_code, event) }
- 投稿日:2021-10-31T11:27:29+09:00
Androidでアプリ起動時にEditTextにフォーカスがあたらないようにする方法
概要 Androidでアプリ作成時にアプリ起動時にEditTextにフォーカスがデフォルトであたっていた。 そのため、特定の端末ではアプリ起動時にキーボードが表示されてしまうという現象が発生した。 この現象を修正するために行った、起動時にEditTextにフォーカスがあたらないようにする方法を記載する。 アプリを作成する際に使用していた言語はKotlin アプリ起動時にEditTextにフォーカスがあたらないようにする方法 AndroidのLayoutのxmlでEditTextの親のレイアウトに以下のコードを追加する。 こうすることでアプリ起動時にEditTextにフォーカスはあたらなくなる。 android:descendantFocusability="beforeDescendants" android:focusableInTouchMode="true"
- 投稿日:2021-10-31T11:16:04+09:00
AndroidのEditTextからフォーカスを外す方法
概要 AndroidのEditTextで入力を確定したあともフォーカスがそのまま残ることがあった。 入力確定後はEditTextからフォーカスは外れてほしい。 そのためAndroidでEditTextからフォーカスを外す方法を記載する。 言語はKotlin フォーカスを外す 以下のコードを入力確定時のイベントで実行するようにすることでフォーカスが外れるようにすることができる。 val parent = parent as View parent.isFocusable = true parent.isFocusableInTouchMode = true parent.requestFocus()
- 投稿日:2021-10-31T02:13:12+09:00
Flutter Flavor対応(パッケージ未使用)
内容 Flutter開発時にFlavorが導入されたプロジェクトに参画したり、他のメンバーがFlavorを導入した後にFlutterを触ったりすることはあったが、自分でFlavorを導入したことがなかったので、導入までの流れをまとめる。 やり方は様々あるようですが、今回は、main_dev.dartとmain_prod.dartの2ファイルを作成する方法で行います。 開発環境 PC:macOS Big Sur エディター:Visual Studio Code, Android Studio, Xcode Dart:2.13.1 Flutter:2.2.1 Visual Studio Code:1.16.2 Android Studio:4.1.2 Xcode:13.1 その他 Flavorパッケージは未使用 今回は開発環境と本番環境を作成する。 それぞれの名前は以下のように統一している。(Android, iOS共に同じ名前を使用する。) 開発環境:dev 本番環境:prod 準備 以下のファイルを作成する。 ・main_dev.dart ・main_prod.dart ・app_config.dart ・main_common.dart ・home_page.dart main_dev.dart import 'package:flutter/material.dart'; import 'app_config.dart'; import 'main_common.dart'; void main() { var configuredApp = AppConfig( appDisplayName: "Dev App", child: MyApp(), ); runApp(configuredApp); } main_prod.dart import 'package:flutter/material.dart'; import 'app_config.dart'; import 'main_common.dart'; void main() { var configuredApp = AppConfig( appDisplayName: "Prod App", child: MyApp(), ); runApp(configuredApp); } app_config.dart import 'package:flutter/material.dart'; class AppConfig extends InheritedWidget { AppConfig({ required this.appDisplayName, required Widget child, }) : super(child: child); final String appDisplayName; static AppConfig? of(BuildContext context) { return context.dependOnInheritedWidgetOfExactType<AppConfig>(); } @override bool updateShouldNotify(InheritedWidget oldWidget) => false; } main_common.dart import 'package:flutter/material.dart'; import 'app_config.dart'; import 'home_page.dart'; class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { var config = AppConfig.of(context); return _buildApp(config!.appDisplayName); } Widget _buildApp(String appName) { return MaterialApp( title: appName, theme: ThemeData( primaryColor: Color(0xFF43a047), accentColor: Color(0xFFffcc00), primaryColorBrightness: Brightness.dark, ), home: HomePage(), ); } } home_page.dart import 'package:flutter/material.dart'; import 'app_config.dart'; class HomePage extends StatefulWidget { const HomePage({Key? key}) : super(key: key); @override State<HomePage> createState() => _HomePageState(); } class _HomePageState extends State<HomePage> { int _counter = 0; void _incrementCounter() { setState(() { _counter++; }); } @override Widget build(BuildContext context) { final config = AppConfig.of(context); return Scaffold( appBar: AppBar( title: Text(config!.appDisplayName), ), body: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[ const Text( 'You have pushed the button this many times:', ), Text( '$_counter', style: Theme.of(context).textTheme.headline4, ), ], ), ), floatingActionButton: FloatingActionButton( onPressed: _incrementCounter, tooltip: 'Increment', child: const Icon(Icons.add), ), ); } } Androidの設定 android/app/src/build.gradleファイルを開き、android{}内にdev、prodという名前でflavorの設定を追加する。 build.gradle android { compileSdkVersion 30 sourceSets { main.java.srcDirs += 'src/main/kotlin' } defaultConfig { applicationId "com.sample.flutter_flavor_sample_app" minSdkVersion 16 targetSdkVersion 30 versionCode flutterVersionCode.toInteger() versionName flutterVersionName } // ここから flavorDimensions "flutter-flavor" productFlavors { dev { dimension "flutter-flavor" applicationId "com.example.dev" resValue "string", "app_name", "Dev App" } prod { dimension "flutter-flavor" applicationId "com.example.prod" resValue "string", "app_name", "Prod App" } } // ここまで追加 buildTypes { release { signingConfig signingConfigs.debug } } } 次にandroid/app/src/main/AndroidManifest.xmlファイルのlabelを変更する。 AndroidManifest.xml android:label="hogehoge_app_name" <-ここのアプリ名を変更する(プロジェクト作成時の名前が記載されている) ↓ android:label="@string/app_name" @string/app_nameにはresValueに記入した値が代入される。 dev環境で実行すると、アプリ名は「Dev App」に、prod環境実行すると、アプリ名は「Prod App」になる。 Android Studioからエントリーポイントを設定する。 以下の画像のような流れで設定をする。 「Edit Configurations...」をクリックする。 表示されたダイアログの左上にある+ボタンをクリックして、Flutterを選択する。 NameとDart entrypoint、Build flavorを入力する。 devとprodの2つを以下のように設定する。 ・dev Name: dev Dart entrypoint: hogehoge/lib/main_dev.dart Build flavor: dev ・prod Name: prod Dart entrypoint: hogehoge/lib/main_prod.dart Build flavor: prod ※Dart entrypointはmain.dartからコピーして、main.dartの部分を変更する。 iOSの設定 Runner.xcworkspaceを開いて、メニューバーのProduct/Scheme/New Scheme...をクリックする。 以下のようなダイアログが表示されるので、devとprodを追加する。 メニューバーのProduct/Scheme/Manage Scheme...をクリックすると、追加したSchemeが確認できる。 (今回はdevとprodを追加している。その他のSchemeもあるが、関係ない。) PROJECTのRunnerのInfoタブを表示する。 +ボタンをクリックして、ConfigurationにDegug-dev、Degug-prod、Release-prodを追加する。 追加の際には下記のようなダイアログが表示されるので、対応するものを選択する。 参考
- 投稿日:2021-10-31T01:11:57+09:00
Pixel6シリーズでRootを取る
注意 自己責任でお願いします Root権限やBootloaderUnlockしている端末をセキュリティ上ブロックしてしまうアプリはまだ使えません。 以下確認してるもの - 銀行系 - Google Pay - torne mobile - その他ゲーム Android12のBoot.imgをパッチできるMagiskのバージョンがCanaryのみなのですが、Magisk hideを削除されてしまっています。 また以下のパッチをAndroid8(Xperia XZ Premium)とAndroid12(Pixel6 Pro)に当てているのですが、Android12ではSafetyNetをBypassできていません。 MagiskHide Props Config Riru Universal SafetyNet Fix 11/1追記 Universal Safetynet fixがアップデートされたようで、Android12に対応したようです。 詳しくは参考リンクをご覧下さい 準備 AndroidStudio、またはコマンドラインツールをダウンロード、インストールしておき、ドライバーやAndroidSDK Toolsをインストールしておく。 ※事前に入っている人もアップデートしておくと良い。またアップデートする際に、C:\AndroidSDKに普通はインストールされますが~\AppData\Local\Android\Sdkに打ち込まれることあるので注意。 Rootのとり方 Pixel6またはPixel6 Proのファクトリーイメージをダウンロードする。 リンク PixelからMagiskのCanary版のAPKをダウンロード、インストールする。 リンク ※Canary版でないとBoot.imgのパッチ当てで失敗します 1.ダウンロードしたファイルからBoot.imgとvbmeta.imgを取り出して、Boot.imgのみPixelにコピーする。 Magiskでインストールを選択、"パッチするファイルの選択"を選んでBoot.imgを選ぶ。出力されたファイルをPCに転送する。(magisk_patched-xxxxx_xxxxx.img) USBデバッグモードにしPCからadb reboot bootloaderコマンドを実行する。または電源オフにして電源ボタン + ボリュームダウンボタンを同時押下してメニューが現れたらボリュームボタンでFastbootを選び電源ボタンを押しPCとつなぐ。 fastboot flash --disable-verity --disable-verification vbmeta vbmeta.imgコマンドを実行 fastboot boot magisk_patched-xxxxx_xxxxx.imgコマンドを実行 起動したらMagiskを起動してインストール、直接インストールを選択して完了したら再起動 ※ 自分はfastboot boot magisk_patched-xxxxx_xxxxx.imgではなくfastboot flash magisk_patched-xxxxx_xxxxx.imgをやったあとにFactory resetしました。 余談 Android11以降/systemをマウントできなくなりAdawayはhostsを編集できなくなった。Adaway使いたければMagiskの設定にあるSystem hostsモジュールを入れると使えるようになる 参考 How To Guide Firmware is out! Get your root on! Universal SafetyNet Fix 2.2.0、Magisk CanaryのZygiskに対応。Android 12でも動作可能
- 投稿日:2021-10-31T01:11:57+09:00
Pixel6・Pixel6 ProでRootを取る
注意 自己責任でお願いします 初期化されますのでバックアップ必須です Root権限やBootloaderUnlockしている端末をセキュリティ上ブロックしてしまうアプリはまだ使えません。 以下確認してるもの - 銀行系 - Google Pay - torne mobile - その他ゲーム Android12のBoot.imgをパッチできるMagiskのバージョンがCanaryのみなのですが、Magisk hideを削除されてしまっています。 また以下のパッチをAndroid8(Xperia XZ Premium)とAndroid12(Pixel6 Pro)に当てているのですが、Android12ではSafetyNetをBypassできていません。 MagiskHide Props Config Riru Universal SafetyNet Fix 11/1追記 Universal Safetynet fixがアップデートされたようで、Android12に対応したようです。 詳しくは参考2をご覧下さい Rooting 1. ダウンロード ドライバとAndroidSDK Platform-Toolsを入れるためにAndroidStudioをダウンロードしインストール。 ※ 他にもCLIツールやドライバー単体で配布されていますので自分の環境と好みに合わせてダウンロードしてください(参考3,4) AndroidStudioを起動し、SDKManagerを起動して以下をインストール Google USB Driver Android SDK Platform-Tools ※上記のツールをアップデートする場合、C:\AndroidSDKに通常はインストールされますが、たまに~\AppData\Local\Android\Sdkにぶっ込まれることあります 念の為PCを再起動し、adb versionコマンドを実行してVersionが31.0.3以上になっていることを確認する Pixel6またはPixel6 Proのファクトリーイメージをダウンロードする。 リンク PixelからMagiskのCanary版のAPKをダウンロード、インストールする。 リンク ※Canary版でないとBoot.imgのパッチ当てで失敗します 2. パッチ当て ダウンロードしたZIPからBoot.imgとvbmeta.imgを取り出して、Boot.imgのみPixelにコピーする。 Magiskでインストールを選択、"パッチするファイルの選択"を選んでBoot.imgを選ぶ。出力されたファイルをPCに転送する。(magisk_patched-xxxxx_xxxxx.img) 3.BootloaderUnlockとBoot.imgの書き込み USBデバッグモードにしPCからadb reboot bootloaderコマンドを実行する。または電源オフにして電源ボタン + ボリュームダウンボタンを同時押下してメニューが現れたらボリュームボタンでFastbootを選び電源ボタンを押しPCとつなぐ。 fastboot flashing unlockを実行し、BootloaderUnlockを行う ※ここで初期化されます fastboot flash --disable-verity --disable-verification vbmeta vbmeta.imgコマンドを実行 fastboot boot magisk_patched-xxxxx_xxxxx.imgコマンドを実行 起動したらMagiskを起動してインストール、直接インストールを選択して完了したら再起動 ※ 自分はfastboot boot magisk_patched-xxxxx_xxxxx.imgではなくfastboot flash boot magisk_patched-xxxxx_xxxxx.imgをやったあとにFactory resetしました。 余談 Android11以降/systemをマウントが難しくなりました。 Adawayを使用したい場合、Magiskの設定にあるSystem hostsモジュールを入れる シャッター音をオフにしたい場合、参考5のツールを導入する 参考 How To Guide Firmware is out! Get your root on! Universal SafetyNet Fix 2.2.0、Magisk CanaryのZygiskに対応。Android 12でも動作可能 Google USB ドライバを入手する SDK Platform-Tools リリースノート AndroPlus-org/magisk-module-pixel6 - Github.com