月: 2022年4月

[Android] 不要なimportを自動で削除する設定

AndroidStudioをデフォルトのまま使っていると使わない一時的に使っていたimportが残っていたりして、そのままGitにコミットプッシュしてしまうなどあると思います。下記の設定を行うとファイル保存時や変更時に不要なimportを削除してくれるので設定することをオススメします。

AndroidStudioを起動する

設定画面を表示する

  1. AndroidStudioを起動する
  2. 設定画面を表示する
  3. ダイヤログ左上の設定を検索する箇所でimportと入力
  4. [Editor] – [General] – [Auto Import] – [Kotlin]の[Optimize imports on the fly]のチェックボックスをONにする

Editorのプラグインとかやり方が色々あるのですが、AndroidStudio標準で機能がついているので、この設定どおり使うのがおすすめです。


targetSdkVersionを31に上げるときに必要なPendingIntent.FLAG_IMMUTABLEとPendingIntent.FLAG_MUTABLEとは?

前回の続き

targetSdkVersionを31に上げるにはさらにPendingIntentにPendingIntent.FLAG_IMMUTABLEまたはPendingIntent.FLAG_MUTABLEのどちらかを必ず指定する必要があります。

これらのフラグはどういう作用をするのか、どう設定したら良いのか調べました。

https://developer.android.com/reference/android/app/PendingIntent#FLAG_IMMUTABLE
作成された PendingIntent が immutable であるべきであることを示すフラグ。これは、このインテントの未入力プロパティを埋めるために send メソッドに渡された追加のインテント引数が無視されることを意味します。
https://developer.android.com/reference/android/app/PendingIntent#FLA_MUTABLE
作成された PendingIntent が mutable であるべきであることを示すフラグ。このフラグは FLAG_IMMUTABLE と組み合わせて使用することはできません。

なるほど。わからんですね。実際にコードを書いて確認しました。

https://github.com/saitoyusuke/PendingIntentFlag

$ git clone git@github.com:saitoyusuke/PendingIntentFlag.git
   
package yusuke.saito.pendingintentflag

import android.app.PendingIntent
import android.content.Intent
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.widget.Toast
import yusuke.saito.pendingintentflag.databinding.ActivityMainBinding

class MainActivity : AppCompatActivity() {

    private lateinit var viewBinding: ActivityMainBinding

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        viewBinding = ActivityMainBinding.inflate(layoutInflater)
        setContentView(viewBinding.root)

        // 追加情報がある場合はトーストを表示する
        // 追加情報がない場合は何もしない
        val additionalInfo = intent.getStringExtra("additional_info")
        additionalInfo?.let {
            Toast.makeText(this, it, Toast.LENGTH_LONG).show()
        }

        // PendingIntentで起動されるActivity
        val activityIntent = Intent(
                this,
                MainActivity::class.java
        ).apply {
            flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
        }

        // FLAG_IMMUTABLEのPendingIntent
        val flagImmutablePendingIntent = PendingIntent.getActivity(
                this,
                0,
                activityIntent,
                PendingIntent.FLAG_IMMUTABLE
        )
        // FLAG_IMMUTABLEのIntent
        val flagImmutableIntent = Intent(
                this,
                SubActivity::class.java
        ).apply {
            putExtra("pending_intent", flagImmutablePendingIntent)
        }

        // FLAG_MUTABLEのPendingIntent
        val flagMutablePendingIntent = PendingIntent.getActivity(
                this,
                0,
                activityIntent,
                PendingIntent.FLAG_MUTABLE
        )
        // FLAG_MUTABLEのIntent
        val flagMutableIntent = Intent(
                this,
                SubActivity::class.java
        ).apply {
            putExtra("pending_intent", flagMutablePendingIntent)
        }

        viewBinding.flagImmutable.setOnClickListener {
            startActivity(flagImmutableIntent)
        }
        viewBinding.flagMutable.setOnClickListener {
            startActivity(flagMutableIntent)
        }
    }
}
package yusuke.saito.pendingintentflag

import android.app.PendingIntent
import android.content.Intent
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import yusuke.saito.pendingintentflag.databinding.ActivityMainBinding
import yusuke.saito.pendingintentflag.databinding.ActivitySubBinding

class SubActivity : AppCompatActivity() {

    private lateinit var viewBinding: ActivitySubBinding

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        viewBinding = ActivitySubBinding.inflate(layoutInflater)
        setContentView(viewBinding.root)

        viewBinding.subButton.setOnClickListener {
            val pendingIntent = intent.getParcelableExtra<PendingIntent>("pending_intent")
            val returnIntent = Intent().apply { putExtra("additional_info", "hello") }
            pendingIntent?.send(this, 0, returnIntent)
        }
    }
}

このコードをAndroidStudioで実行し、MainActivityでflagImmutableボタンを押下し、SubActivityでsubButtonを押下すると何も表示されませんが、MainActivityでflagMutableボタンを押下し、SubActivityでsubButtonを押下するとトーストが表示されます。

つまりどういうことかと言うとFLAG_IMMUTABLEを設定してPendingIntentを作成するとpendingIntent?.send(this, 0, returnIntent)のreturnIntentに設定したputExtraの内容が送られてきません。逆にFLAG_MUTABLEを設定してPendingIntentを作成するとpendingIntent?.send(this, 0, returnIntent)のreturnIntentに設定したputExtraの内容が送られてきます。

動きはわかりましたが、これをどのように使うかはいまいちわかっていません。呼び出し元がPendingIntentにデータを付加して欲しくないときに設定する?

https://developer.android.google.cn/about/versions/12/behavior-changes-12?hl=ja#pending-intent-mutability

公式の説明では使い分けの方法は書いていませんでしたが、基本的にはFLAG_IMMUTABLEを使って、必要に応じてFLAG_MUTABLEを使うのが良いかと思います。(想定外データ変更を発生させないため)


targetSdkVersionを31に上げ、android:exportedを全て指定してもandroid:exportedを指定してくださいとエラーが表示される

https://developer.android.com/about/versions/12/behavior-changes-12?hl=ja

targetSdkVersionを31に上げるとAndroidManifest.xmlの全てのactivity、service、receiverにandroid:exportedを指定する必要があります。

https://developer.android.com/about/versions/12/behavior-changes-12?hl=ja#exported

簡単に終わるなーと思ってandroid:exportedを指定したのですが、エラーが解消されず、同じエラーが表示されてしまいました。どうやらandroid:exportedが必要なのは使用しているOSSライブラリも含んでいるようです。私の環境ではandroidx.test:coreのバージョンを1.3.0から1.4.0にバージョンアップするだけで対応できたのですが、多くのOSSライブラリを使っている場合は大変ですね…。

targetSdkVersionは毎年バージョンアップを求められます

https://developers-jp.googleblog.com/2017/12/improving-app-security-and-performance.html