fresh digitable

めんどくさかったなってことを振り返ったり振り返らなかったりするための記録

VideoViewとその周辺のコードを読んだり調査した #m_android_fcr

今作っているツイッタークライアントが、N(Android 7.0)になった辺りからContextをリークさせるようになってしまったのでいろいろと調査した。

コードーディング会にもお邪魔して集中して取り組んだ。主催者の@operandoOSさん、会場を提供してくださったメルカリさん、ありがとうございました。

mandroidfcr.connpass.com

背景

このあたり

UdonRoad/MediaViewActivity.java at master · akihito104/UdonRoad · GitHub
UdonRoad/VideoMediaFragment.java at master · akihito104/UdonRoad · GitHub

MediaViewActivityViewPagerを持っており、FragmentPagerAdapterを通して動画を再生するためのViewMediaFragementViewPagerにセットする*1ViewMediaFragmentではlayout resourceからViewGroupをinflateしているのだが、ここでinflateしたVideoViewからContextが漏れている様子。leakcanaryのスタックトレースは次の通り。

* GC ROOT android.media.PlayerBase$1.this$0 (anonymous subclass of com.android.internal.app.IAppOpsCallback$Stub)
* references android.media.MediaPlayer.mSubtitleController
* references android.media.SubtitleController.mAnchor
* references android.widget.VideoView.mContext
* leaks com.freshdigitable.udonroad.MediaViewActivity instance
* Retaining: 59KB.

コードを読んで調べたこと

次の順で追いかけると、スタックトレースの3段目にあるSubtitleController.mAnchorVideoView自身であるということがわかる。

Cross Reference: /frameworks/base/core/java/android/widget/VideoView.java
=> Cross Reference: /frameworks/base/media/java/android/media/MediaPlayer.java
=> Cross Reference: /frameworks/base/media/java/android/media/SubtitleController.java

スタックトレースの一番上のやつはPlayerBaseのコンストラクタの中でAppOpsServiceにセットされているのだが、

Cross Reference: /frameworks/base/media/java/android/media/PlayerBase.java

リソースをリリースする時にこれがなぜか解放されないためにリークしているのではないか、と理解した。

Cross Reference: /frameworks/base/media/java/android/media/PlayerBase.java

PlayerBaseはNで追加されたクラスであるようす。

github.com

ぐぐったこと

同じようなリークの現象で困っている人がいたのだが、ButterKnifeのUnbinder.unbind()をしていないからだと指摘されており、これを適用していい感じになったよありがとう!というやりとりで閉じられている。

stackoverflow.com

ここにきて、僕が気づいていないだけで、僕も同じように参照をうまく解放できていないんじゃないかと思い始めてしまいふりだしへ。

その他のVideoView情報

M(Android 6.0)より前のVideoViewAudioManagerContextを掴んで離さない(強参照している)ためにリークする。応急処置のワークアラウンドとしてつぎのようなコードが紹介されている。

Fixes a leak caused by AudioManager using an Activity context, see https://github.com/square/leakcanary/issues/205 · GitHub

Mの間だけは平穏だった様子。

ちなみに7.0以前のコードにもお茶目があったようで、これは7.1で修正された*2。issue trackerをみればわかることだが気が向いた人は探してみてほしい。

まとめ

SurfaceView + ExoPlayerで君だけのVideoViewを作ろう。根本的によくないコードを書いているんじゃないかという気もしていて、単に取り替えただけでは勉強にならないのでもう少し取り組むことにする。何か分かったら続きを書くかもしれない。

*1:この辺がそもそもセンス無いという気もする

*2:これで漏れないのがよくわからない

ViewAnimationUtils.createCircularReveal()にはアニメーションさせたいviewの左上を基準にした座標を渡す

ViewAnimationUtils.createCircularReveal()はcircular reveal animatorのオブジェクトを作成するためのユーティリティクラスで、 API Level 21(Lollipop)で追加された。

ViewAnimationUtils | Android Developers

アニメーションさせたいviewと、円の中心座標、円の最初の半径と最後の半径を渡す。Android公式のチュートリアル(日本語) には

// get the center for the clipping circle
int cx = (myView.getLeft() + myView.getRight()) / 2;
int cy = (myView.getTop() + myView.getBottom()) / 2;

というように親viewの左上を基準にした座標で円の中心(この例ではたまたまmyViewの中心をアニメーションの円の中心としている)を決めているが、これで良かったのはAPI Level 22までのようで、23(Marshmallow)からは

int cx = myView.getWidth() / 2;
int cy = myView.getHeight() / 2;

というようにアニメーションさせたいviewの左上を基準にした円の中心座標を渡さなければならない。ちなみに英語版のチュートリアルこのコミット で修正されたようだが、その他の言語のチュートリアルはそのままになっている様子。

2016年に見たアニメ

去年も書いたので今年の分も書いて振り返る。読みが五十音順になるよう並べていますが、そうなっていないところがもしあればそれは間違いです。

冬クール

アクティヴレイドとか昭和元禄落語心中は放送が終わった頃にようやく録画したやつを一気に見たけど素晴らしかった。他にもグリムガルとかGATE、デュラララ、僕だけがいない街、だがしかし、亜人暗殺教室、しょこめざ、ディメンションW、ルパン、などなど好きな感じのやつが多かったクールだったと思う。

春クール

カバネリ、ヒロアカ、クロムクロジョジョうしおととらなど熱い展開の話が多かったかな。クロムクロは自分のよく知っている風景がアニメに出てくるのをみるとなんとなく嬉しい感じになってよかった。忍殺はニコニコで1話を見たとき全然理解できなくてそれ以降見てなかったけど、今回改めて見たら最高にいかれててよかった。

夏クール

アクティブレイドが王道を突き進んで堂々と終わったのが最高に良かった。アルデラミンは二期を早く見たいなあ。ぞい!とかReLIFEは今の自分に鋭利に刺さる感じの話しで個人的にはつらかったけどおもしろかった。斉木楠雄のΨ難は純度の高い良質な神谷浩史成分を摂取できるアニメだった。

秋クール

ユーフォが3話ごとぐらいに神回をもってきてすごかったし黒沢ともよの演技もすごかった。イゼッタ、ドリフ、オカン、SBR#、舟を編む、ブレイブウィッチーズあたりもすごく好きで、いっせいに終わってしまうのが寂しくもある。わたモテは完全に女性向けだと思うんだけど、振り切れた小林ゆうとそれに付き合って振り切れる沢城みゆきを楽しめたのでよかった。

再放送枠

ボトムズに関してはtvk GJ!というより他ない。

Web

最後に宮沢賢治の「星めぐりの歌」が流れて無事に涙腺崩壊した。

劇場版枠

もっと見た気がしてたけど去年より2本多いぐらいだった。アニメ以外ではシン・ゴジラと聖の青春を見ているので例年よりは映画を見ていると思われる。どれも最高に面白かった。MJPは二期も見てみたい。

参考:2016年度の人気アニメランキング【あにこれβ】