Android Studioで表示される警告の正体、知っていますか?
はじめに
NTTレゾナントテクノロジーの西添です。モバイルアプリエンジニアをやっています。
Android Studioで開発していると、コードが赤や黄色やグレーに色づいてエラーや警告を知らせてくれますよね。
例えば下図のようなものです。
この正体、知っていますか?IDEの裏側で何が動いてあのようなメッセージを出してくれているのでしょうか?
私は正直のところ「IDEが出してくれるエラーや警告」としか認識していませんでした。コンパイラのエラーや警告がリアルタイムにわかるし、不要なコードがすぐに見つかるし、よりKotlinらしく簡潔な書き方にするためにアドバイスしてくれるし、この機能を使わずには開発できないほどの重要な存在ですが、意外とよくわかっていませんでした。
そこで今回はこれが何という機能で、裏で何が動いているのか調査しました。その際に発見した意外な落とし穴も合わせてご紹介したいと思います。
Code Inspections
早速答えを言ってしまうと、この機能はIntelliJ IDEAの「Code Inspections」と呼ばれる静的コード解析機能です。
冒頭のスクリーンショットのように、現在エディタで開いているファイルのコードをリアルタイムに解析して、検出した問題をハイライトしてくれます。また、手動で実行することも可能で、その場合は検査対象の範囲してプロジェクト全体の問題点を一覧化することもできます。
しかし、どうやら手動で実行する場合はエディタに表示される問題が全て検出できるわけではなく、一部の問題のみしか検出できないようです。この点については具体例を交えて後述します。
Code Inspections機能の詳しい使い方については次の公式ドキュメントに載っていますので、一読しておくとよいでしょう。
Code Inspectionsの裏で何が動いているのか?
では、Code Inspectionsで表示されるメッセージはどこから来たものでしょうか?Androidアプリを開発する上で私が見たことあるものは、以下のツールが出力したものでした。あくまでも私の経験上ですので、他にもあるかもしれない点についてご了承ください。
A. Kotlinコンパイラが出力するerror / warningのメッセージ
A-c. @Deprecatedアノテーション
B. IntelliJ Platform PluginのCustom code inspectionsが出力するメッセージ
B-a. Kotlinプラグイン
B-b. JVM Analysisプラグイン
B-c. Spell Checkerプラグイン
それぞれについて具体例を見ていきましょう。
A. Kotlinコンパイラが出力するerror / warningのメッセージ
A-a. Kotlinコンパイラに組み込まれたerror / warning
※ Code Inspectionsの手動実行時にも検出されました。
※ Code Inspectionsの手動実行時には検出されませんでした。
A-b. @RequiresOptInアノテーション
※ Code Inspectionsの手動実行時には検出されませんでした。
これはOpt-inが必要なものを使用した場合にKotlinコンパイラが出力するメッセージです。この例の場合は GlobalScope の定義に @DelicateCoroutinesApi アノテーションが付いており、さらにそのアノテーションの定義には @RequiresOptIn アノテーションが付いています。@RequiresOptIn アノテーションのmessageに上記のメッセージが定義されています。
ソースコード:
A-c. @Deprecatedアノテーション
※ Code Inspectionsの手動実行時には検出されませんでした。
これは @Deprecated アノテーションが付いた関数等を使用するとコンパイラが出力するメッセージです。
B. IntelliJ Platform PluginのCustom code inspectionsが出力するメッセージ
B-a. Kotlinプラグイン
※ Code Inspectionsの手動実行時にも検出されました。
※ Code Inspectionsの手動実行時にも検出されました。
B-b. JVM Analysisプラグイン
※ Code Inspectionsの手動実行時にも検出されました。
B-c. Spell Checkerプラグイン
※ Code Inspectionsの手動実行時にも検出されました。
C. Android Lint
※ Code Inspectionsの手動実行時にも検出されました。
プロジェクト全体の問題点を一覧化したいときはどうすればよいのか?
これまで見てきたとおり、Code Inspectionsを手動で実行するとKotlinコンパイラのerror / warningについては一部しか検出されません。ではどうすればいいかというと、Code Inspectionsを手動で実行するのに加えて、プロジェクト全体をビルドしましょう。
[Rebuild Project] を実行すると画面下部にある [Build] タブが自動で開き、ビルドの出力結果が表示されます。左ペインにある[:app:compileDebugKotlin](「Debug」の部分にはBuild Variant名が入ります)を選択すると、Kotlinコンパイラが出力したerror / warningが一覧表示されます。
少し面倒ですが、①Code Inspectionsの手動実行と②プロジェクト全体のビルドの両方を実行することで、プロジェクト全体の問題点を一覧化することができます。
参考情報
最後に、今回の検証で作成した警告盛りだくさんなソースコードを掲載しておきます。
package com.example.staticanalysislab
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.os.Parcelable
import android.view.View
import kotlinx.android.parcel.Parcelize
import kotlinx.coroutines.*
// Unused import directive
// --> Kotlin plugin for IntelliJ IDEA & Android Studio [KotlinUnusedImportInspection]
import androidx.recyclerview.widget.RecyclerView
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
sampleFunction("hoge", "fuga")
}
// Function "fizz" is never used
private fun fizz() {
println("buzz")
}
// Function 'sampleFunction' could be private
// --> Kotlin plugin for IntelliJ IDEA & Android Studio [MemberVisibilityCanBePrivateInspection]
// Parameter 'arg1' is never used
// --> Kotlin compiler [UNUSED_PARAMETER]
fun sampleFunction(arg1: String, arg2: String) {
// This variable must either have a type annotation or be initialized
val hello
// Unnecessary safe call on a non-null receiver of type String. This expression will have nullable type in future releases
// --> Kotlin compiler [UNNECESSARY_SAFE_CALL]
val aaa = ""
aaa?.length
// Variable 'foo' is never used
// --> Kotlin compiler [UNUSED_VARIABLE]
// --> @Suppress("UNUSED_PARAMETER") で警告を無視できる
val foo = "Foo"
// The expression is unused
// --> Kotlin compiler [UNUSED_EXPRESSION]
if (arg2 == "Fuga") {
println("true")
true
} else {
println("false")
false
}
// This is a delicate API and its use requires care. Make sure you fully read and understand documentation of the declaration that is marked as a delicate API.
// --> Kotlin compiler [RequiresOptIn annotation]
GlobalScope.launch(Dispatchers.Main) {
// Inappropriate blocking method call [Android Studio 2020.3.1]
// Possibly blocking call in non-blocking context could lead to thread starvation [Android Studio 2021.3.1]
// --> JVM analysis plugin of IntelliJ Community
Thread.sleep(4000)
}
// 'SYSTEM_UI_FLAG_FULLSCREEN: Int' is deprecated. Deprecated in Java
// --> Kotlin compiler [Java deprecated annotation]
View.SYSTEM_UI_FLAG_FULLSCREEN
// Variable is never modified and can be declared immutable using 'val' [Android Studio 2020.3.1]
// Variable is never modified so it can be declared using 'val' [Android Studio 2021.3.1]
// --> Kotlin plugin for IntelliJ IDEA & Android Studio [CanBeValInspection]
var bar = "Bar"
println(bar)
// Typo: In word 'Wrld'
// --> IntelliJ IDEA's Spellchecking
println("Hello Wrld!")
// It will always be more efficient to use more specific change events if you can. Rely on `notifyDataSetChanged` as a last resort.
// --> Android Lint [NotifyDataSetChanged]
// --> @SuppressLint("NotifyDataSetChanged") で警告を無視できる
val adapter = CustomAdapter()
adapter.notifyDataSetChanged()
}
}
// Parcelize annotations from package 'kotlinx.android.parcel' are deprecated. Change package to 'kotlinx.parcelize'
// --> Kotlin compiler [DEPRECATED_ANNOTATION]
// --> @Suppress("DEPRECATED_ANNOTATION") で警告を無視できる
@Parcelize
data class Foo(val bar: Int) : Parcelable
package com.example.staticanalysislab
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
// Remove empty primary constructor
class CustomAdapter() :
RecyclerView.Adapter<CustomAdapter.ViewHolder>() {
// Redundant empty class body
class ViewHolder(view: View) : RecyclerView.ViewHolder(view) {
}
override fun onCreateViewHolder(viewGroup: ViewGroup, viewType: Int): ViewHolder {
// Create a new view, which defines the UI of the list item
val view = LayoutInflater.from(viewGroup.context)
.inflate(R.layout.activity_main, viewGroup, false)
return ViewHolder(view)
}
override fun onBindViewHolder(viewHolder: ViewHolder, position: Int) {
}
override fun getItemCount() = 1
}
宣伝
NTTレゾナントテクノロジーでは一緒に働いてくれるAndroid/iOSアプリエンジニアを募集中です。もし興味がありましたら採用ページを是非ご覧ください。