AsyncTask の挙動が異なる(AsyncTask#executeOnExecutor)
AsyncTask の挙動が Gingerbread と ICS で違う、という話を聞いて調べてみた。
複数AsyncTask実行=バックグラウンドで並行処理 と考えてると、実は並行処理になってなくて処理が遅い、ってこと。
結論を言うと、ソースをここだけ修正すればよさそう。
修正方法
- AsyncTask#execute を AsyncTask#executeOnExecutor に変更する
- AsyncTask#executeOnExecutor の引数に AsyncTask.THREAD_POOL_EXECUTOR を指定する
1行で済みそうな修正。よかったw
とはいえ気になったので、AsyncTask#execute の挙動の違いについてまとめてみる。
↓はSDKのバージョンを変えて検証した結果。
検証結果
複数AsyncTask の AsyncTask#execute を実行した場合
- API Level
1012まで- 複数バックグラウンド処理がパラレルで実行される
- API Level
1113以降- 複数バックグラウンド処理がシリアルで実行される
3.2 から変わった様子。
ちなみに検証はこんなかんじで。
検証方法
では、GB相当の AsyncTask と同等の動きをさせるにはどうしたらいいか。
AsyncTask#execute のJavadoc見るとこう書いてある。
After HONEYCOMB, it is planned to change this back to a single thread to avoid common application errors caused by parallel execution. If you truly want parallel execution, you can use the executeOnExecutor(Executor, Params...) version of this method with THREAD_POOL_EXECUTOR; however, see commentary there for warnings on its use.
Honeycomb以降では 複数のAsyncTackのバックグラウンド処理 をパラで実行したいなら
AsyncTask#executeOnExecutor + THREAD_POOL_EXECUTOR を使用してください、と。
今回使用したサンプル。
(ボタン押したら AsyncTask#execute か AsyncTask#executeOnExecutor を実行するだけ)
実行結果(emulator(API Level14))
AsyncTask#execute 01-25 02:58:37.899: D/AsyncTaskTest(613): [doInBackground]start 78 01-25 02:58:39.950: D/AsyncTaskTest(613): [doInBackground]end 78 01-25 02:58:39.950: D/AsyncTaskTest(613): [doInBackground]start 79 01-25 02:58:41.970: D/AsyncTaskTest(613): [doInBackground]end 79
AsyncTask#executeOnExecutor 01-25 02:59:11.889: D/AsyncTaskTest(613): [doInBackground]start 80 01-25 02:59:11.929: D/AsyncTaskTest(613): [doInBackground]start 81 01-25 02:59:13.919: D/AsyncTaskTest(613): [doInBackground]end 80 01-25 02:59:13.989: D/AsyncTaskTest(613): [doInBackground]end 81
AsyncTask#executeOnExecutor の方はちゃんとパラで動いてる。
参考
@findup さんのエントリ。
[http://www.swingingblue.net/mt/archives/003629.html:title=きままな日記帳 - GingerbreadとICSでのAsyncTaskの挙動の違い
ListViewの高速スクローラまわりをカスタマイズ(AbsListView#setFastScrollEnabled)
前回 のエントリに引き続き、さらに ListView の高速スクローラまわりをカスタマイズしてみました。
(ついでにタイトル変更)
環境
4.0(API Level14)
ガイド(overlay)部分が3Dぽい感じに、スクローラは細身な感じに。
ソースはこちら。(前回エントリで使用した部分も合わせてます)
https://gist.github.com/1384757
ListView の高速スクロール時ガイドを編集する(AbsListView#setFastScrollEnabled)
Android Advent Calendar 12/18 担当の @daichan4649 です。
豪華メンバーがものすごく有意義なネタを記載されている中で、
空気を無視して ListView について誰得なネタを書いてみるとします。
ListViewには項目が多い場合に 「高速スクローラ」 を表示する機能がありますよね。
このとき 「スクロール中に項目のどのあたりを表示しているか」 を示す、
ガイドみたいなヤツを表示することができたりもします。
高速スクローラ、ガイド表示の実装に必要なのはこの2つ。
- AbsListView#setFastScrollEnabled(true)
- 1のListViewに設定するAdapterに SectionIndexer をimplementsする
1で高速スクローラを有効にし、
2でガイド表示内容を実装する、と。
実装してみると実際のイメージはこんなかんじになります。
この例ではガイドに 「画面内先頭要素のindex値」 をそのまま表示してます。
右側に表示されているスクローラをドラッグすると、
リスト先頭項目に合わせてガイド表示内容も変わっていく、という感じです。
本題に入ります。
このガイド。
普通にいじろうとすると 指定した文字列を表示する、くらいしかできない。
濃いグレーの正方形 は編集できない。
編集できないと言われるといじりたいじゃないですか。
ということで、framework のソース読んで試してみました。
とりあえず今回はこのグレーの正方形を変えるところを実験してみるとします。
いつものドロイド君アイコン画像に差し替えてみたり。
では、どんなかんじで差し替えたかを書いてみます。
まず、基本から。
AbsListView#setFastScrollEnabled(true) を呼び出すと
「高速スクローラとガイド」 を管理している FastScrollerインスタンス が
AbsListView内で生成されます。
しかし、ガイドのインスタンスが FastScrollerクラス 内に隠蔽されていて、
外部から編集できないようになっていたりします。
↓の FastScrollerインスタンス の中をいじりたいのですが。。。
// AbsListView.java public void setFastScrollEnabled(boolean enabled) { mFastScrollEnabled = enabled; if (enabled) { if (mFastScroller == null) { mFastScroller = new FastScroller(getContext(), this); } } else { if (mFastScroller != null) { mFastScroller.stop(); mFastScroller = null; } } }
とまぁ、どうしようもなかったのでリフレクションでいじりましたw
リフレクションでいじるポイント、タイミングは以下。
- AbsListView#setFastScrollEnabled(true) を呼び出す
- その直後にリフレクションで内部のFastScrollerインスタンスを差し替える
実際のソースはこんな感じになります。
https://gist.github.com/1384757
// 今回のポイントになるリフレクション部分だけ貼りつけてみます @Override protected void onCreate(Bundle savedInstanceState) { (抜粋) final ListView listView = (ListView) findViewById(R.id.listview); // 高速スクローラ有効化設定 listView.setFastScrollEnabled(true); // AbsListView#setFastScrollEnabled 直後に実行 Drawable overlay = getResources().getDrawable(R.drawable.ic_launcher); customizeFastScroller(listView, overlay); } private void customizeFastScroller(AbsListView listView, Drawable overlay) { try { // 新FastScrollerインスタンス生成 Class<?> clazz = Class.forName("android.widget.FastScroller"); Constructor<?> constructor = clazz.getConstructor(Context.class, AbsListView.class); Object newFastScroller = constructor.newInstance(this, listView); // ガイド用drawable(グレーの四角)を上書き Field fieldOverlay = clazz.getDeclaredField("mOverlayDrawable"); fieldOverlay.setAccessible(true); fieldOverlay.set(newFastScroller, overlay); // FastScrollerインスタンス(オリジナル)を上書き Field orgFastScroller = AbsListView.class.getDeclaredField("mFastScroller"); orgFastScroller.setAccessible(true); orgFastScroller.set(listView, newFastScroller); } catch (Exception e) { e.printStackTrace(); } }
見てわかるとおり、強引な手法ですw
この方法を使えば 「スクローラ画像」 自体も差し替え可能ですね。
それについてはまた後日書こうと思います。(gistにはコード載せてます)
最後に。
このような素晴らしい企画に参加させていただきまして、
@youten_redo さん、本当にありがとうございました!
明日(12/19)の担当は @patorash さんです!
Android Make Days in 明星和楽 にスタッフとして参加してきました
11/11金 - 12土 で行われた Android Make Days in 明星和楽。
今回自分はスタッフとして参加させていただきました。
メイン作業は司会。
部屋が複数あるので3人で交代しつつ、とのことだけども。
こんなでかいイベントで司会とか!!不安でガクブル。
と、自分のことはさておき、まずはスタッフ視点を含めつつレポート書いてみようかと。
各セッションについての素晴らしいエントリはもういくつもあるし。
というわけで早速。
(写真多めですー)
11/11(金)
24Makeコンテスト。
場所は GuildCafe Costa。
金曜夜から24時間でアプリ作って土曜に発表、というクレイジーなイベントw
仕事終わって皆様の頑張る様子をチラ見しにきてみた。
来年は作る側で参加しようかなー。すごく楽しそうだった。
参加者の皆様の様子。
11/12(土)
9:00
スタッフメンバー集合。
場所は 福岡県Rubyコンテンツ産業振興センター。
自分は間違えて金曜の会場、GuildCafe Costa へ行ってしまうという醜態をさらすw
会場設営やったり。
スタッフミーティングやったり。
11:00
ホモ弁に予約していたスタッフメンバー分の弁当を受け取り行ったり。
司会させていただくセッションの講師の皆様と打ち合わせしたり。
弁当食べながら司会メンバーで打ち合わせの図。
twicca作者の @R246氏(青山さん) とパシャリ。
あんざいゆきさん との打ち合わせ。
自分はあんざいさんの大ファンなのでサインいただいちゃいました。
うん、自分きもいですねw
イベント開始前にみんなで交代で弁当タイム。
鹿。どや。
世界の みよしさん 発見!
13:00
開場。
参加者が続々。
すぐにメイン会場が埋まる。
受付の様子
美女ぞろい。
ステッカーたくさん。
オープニング、基調講演。
ブースの様子。
Arduino体験講座。
レッドブル様との交流w
LT会場。大盛況。
個人的にどストライクだったのが、
ブリリアントサービス様のNFCクエスト体験。
@R246氏、サイバーエージェント三島木さん と超強力なパーティ組んで挑んだものの。。
敗北('A`)
魔王強い。
ブースの皆様と。
19:00
閉会式。
全員で手を繋いでスタッフ一同挨拶。
19:30 - 21:00
懇親会。
乾杯。
わいわい。
三島木さんとあんざいさん
後片付け後、残っていたメンバーで集合写真。
終了後。
スタッフ数名で一緒に明星和楽Gates会場へ行ったあと、途中離脱して屋台へ移動。
屋台の後は酔っぱらってひとりで中洲をうろうろして川沿いで寝てた。(ひどい)
で、結局タクシーで5時すぎに帰宅。
イベントを終えて。
正直なところ、スタッフメンバーはイベント屋ではないので普段の自分の業務もある。
そんな状況下でも各メンバーが責任もってタスクをちゃんとこなし、
結果、イベントは大成功っていうのはやっぱりすごいことじゃないかと。
イベント終了後になんだかイロイロと考えてしまった。
いやー!!!本当に楽しかった!!
スタッフとして参加できて本当によかった!!
次回もぜひスタッフとして参加したいし、何かを発信する側にもなりたい。
改善すべき、反省すべき点はもちろんたくさんあるけれども、
イベント運営の難しさと楽しさを味わうことができて、
すごく貴重ないい経験ができた、参加できてよかった、楽しかった、というのが本音。
運営スタッフの皆様、講演者の皆様、参加された皆様、
本当にお疲れさまでした!!
Intentの作り方
Activity/Service起動用のIntentを作るとき、
皆どうやってるのだろう、とふと疑問。
例えば Activity#onCreate 内で
「Service(test.intent.TestService)起動用のIntent」 を作るとする。
基本的な作り方はこんなかんじになるのかな?
@Override public void onCreate(Bundle savedInstanceState) { 〜 // コンストラクタのみ で作る場合 Intent intent1 = new Intent(this, TestService.class); Intent intent2 = new Intent(getApplicationContext(), TestService.class); // コンストラクタ(引数なし)+setClassName で作る場合 String packageName = getPackageName(); String className = TestService.class.getCanonicalName(); Intent intent3 = new Intent(); intent3.setClassName(packageName, className); }
自分の場合は intent1、2 みたいにコンストラクタ1行で済ませることが多い。
web上のサンプル見てもこのパターンが多い気がする。
通常はこれでいいはず。楽だし。
ここで出た疑問。
ApplicationContext を渡す必要があるのだろうか。
Intent のソースを確認してみたところ、
本来は ApplicationContext を引数に渡す必要などなく、
最低限必要なのはこれ↓↓だけだった。
Intent作成に必要な情報
- アプリケーションのパッケージ名
- 起動するクラス名
1は AndroidManifest.xml の
起動するクラス(TestService)のパッケージ名ではない。
2は 起動するActivity/Service等のクラス名(パッケージ名まで含む)。
引数に渡された ApplicationContext は、内部で
「アプリケーションのパッケージ名」 を取得するために使ってるだけ。
まーつまり intent1、2、3 はどれも同じなので、
作りたい作り方でおk、ということでしたw
Intent.java(抜粋)
public Intent() { } public Intent(Context packageContext, Class<?> cls) { mComponent = new ComponentName(packageContext, cls); } public Intent setClassName(Context packageContext, String className) { mComponent = new ComponentName(packageContext, className); return this; }
ComponentName.java(抜粋)
public ComponentName(String pkg, String cls) { if (pkg == null) throw new NullPointerException("package name is null"); if (cls == null) throw new NullPointerException("class name is null"); mPackage = pkg; mClass = cls; } public ComponentName(Context pkg, String cls) { if (cls == null) throw new NullPointerException("class name is null"); mPackage = pkg.getPackageName(); mClass = cls; } public ComponentName(Context pkg, Class<?> cls) { mPackage = pkg.getPackageName(); mClass = cls.getName(); }
話変わって。
intentって文字列定義みたいなもんだから、
できれば 「static final」 定義で、static初期化子 内で作っておきたいなー、
なんて自分は思ってたりする。後から編集しないようなintentは特に。
でも 「アプリケーションのパッケージ名」 は
static初期化子 内で動的に取得できない。
ということは、こんなかんじで固定値定義するしかないのか??
private static final Intent intent; static { String packageName = "test.intent"; String className = TestService.class.getCanonicalName(); intent = new Intent(); intent.setClassName(packageName, className); }
うーん。微妙。