Android LintのDetectorを読む Day5: CallSuperDetector
ソースコード
背景
メソッドがoverrideされるときに、親クラスのメソッドを呼ぶように示すアノテーション @CallSuper
が存在する。
ただしく実装されているかをチェックするLint。
適用例
以下の条件をすべて満たしているときにエラーが出る。
- @CallSuperがついているメソッドをoverrideする
- 親メソッドを呼ぶべきとアノテーションされているもの
- View.onDetachedFromWindow
- Android 5からこのメソッドでの実際の処理は
onDetachedFromWindowInternal
で行われるようになったため、onDetachedFromWindow
をsuperを呼ばずにoverrideしても問題なくなった。実際View.onDetachedFromWindow
の実装は空になっている。 - が、それ以前では
View.onDetachedFromWindow
に実装が入っておりsuperを呼ぶ必要があり、また@CallSuper
は存在していない時代だったため、特殊ケースとして条件が存在する。
- Android 5からこのメソッドでの実際の処理は
- android.support.wearable.watchface.WatchFaceService.Engine.onVisibilityChanged
- サポートライブラリ側で
@CallSuper
対応するまでは特殊ケースとしてチェックしている。
- サポートライブラリ側で
また、onCreate内でsuperを複数回呼ぶとクラッシュの危険性があるので、その場合もチェックする。 この際、複数回呼ばれない構造であればエラーが検出されない。
例えば以下のようなケースでは検出されない
override fun onCreate(savedInstanceState: Bundle){ if(hoge){ super.onCreate(savedInstanceState) } else { super.onCreate(savedInstanceState) } }
気づき
SuperCallVisitorで superの呼び出しをチェック
特定のメソッドの中で呼び出したsuperのメソッドのついての情報を収集している。
skipParenthesizedExprUp
は簡単に言うとsuperのメソッドを取得しており、targetとなるメソッドと名前が一致していれば配列に入れておくようになっている。この配列が0ならsuperが呼ばれていないのでエラーが出る。
override fun visitSuperExpression(node: USuperExpression): Boolean { anySuperCallCount++ val parent = skipParenthesizedExprUp(node.uastParent) if (parent is UReferenceExpression) { val resolved = parent.resolve() if (resolved == null || // Avoid false positives for type resolution problems targetMethod.isEquivalentTo(resolved) ) { superCalls.add(node) } } return super.visitSuperExpression(node) }
その他感想
CallSuperDetector
という名前的には合っているが、 CallSuper
のメソッドでちゃんとsuperが呼んでいるか、とonCreateが複数回呼んでいないかは別のクラスに責務分けたほうが良くないか?とは思う。
また、onCreate複数回呼んでいないかについて、クラスの判定を行っていない。複数回呼んでクラッシュするというのは Activity
での話であり、これを条件に入れたほうが良さそう。