プログラマってこんなかんじ??

アプリ作ったり歌ったりしてます

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 から変わった様子。
ちなみに検証はこんなかんじで。

検証方法

  • ビルドには ICS(API Level14) を使用
  • 以下のパターンのapkを作成
    • AndroidManifest.xmlandroid:minSdkVersion を11〜14の間で変更したapkを作成
  • emulator(API Level14) 上で実行


では、GB相当の AsyncTask と同等の動きをさせるにはどうしたらいいか。
AsyncTask#executeJavadoc見るとこう書いてある。

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 の高速スクローラまわりをカスタマイズしてみました。
(ついでにタイトル変更)

環境

2.3.3(API Level10)

before
f:id:dai4649:20111220013528p:image

after
f:id:dai4649:20111220013527p:image

スクローラもいじったりましたw

ここで疑問。
ICSではどうなるのだろうか。
とりあえずソースそのままで API Level だけ変えて実験。

環境

4.0(API Level14)

before
f:id:dai4649:20111220014815p:image

after
f:id:dai4649:20111220014816p:image

ガイド(overlay)部分が3Dぽい感じに、スクローラは細身な感じに。

ソースはこちら。(前回エントリで使用した部分も合わせてます)
https://gist.github.com/1384757

ListView の高速スクロール時ガイドを編集する(AbsListView#setFastScrollEnabled)

Android Advent Calendar 12/18 担当の @daichan4649 です。
豪華メンバーがものすごく有意義なネタを記載されている中で、
空気を無視して ListView について誰得なネタを書いてみるとします。


ListViewには項目が多い場合に 「高速スクローラ」 を表示する機能がありますよね。
このとき 「スクロール中に項目のどのあたりを表示しているか」 を示す、
ガイドみたいなヤツを表示することができたりもします。


高速スクローラ、ガイド表示の実装に必要なのはこの2つ。

  1. AbsListView#setFastScrollEnabled(true)
  2. 1のListViewに設定するAdapterに SectionIndexer をimplementsする


1で高速スクローラを有効にし、
2でガイド表示内容を実装する、と。
実装してみると実際のイメージはこんなかんじになります。


初期実装

この例ではガイドに 「画面内先頭要素のindex値」 をそのまま表示してます。
右側に表示されているスクローラをドラッグすると、
リスト先頭項目に合わせてガイド表示内容も変わっていく、という感じです。


本題に入ります。


このガイド。
普通にいじろうとすると 指定した文字列を表示する、くらいしかできない。
濃いグレーの正方形 は編集できない。


編集できないと言われるといじりたいじゃないですか。


ということで、framework のソース読んで試してみました。
とりあえず今回はこのグレーの正方形を変えるところを実験してみるとします。
いつものドロイド君アイコン画像に差し替えてみたり。


カスタム実装

変更OK!!


では、どんなかんじで差し替えたかを書いてみます。


まず、基本から。
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
リフレクションでいじるポイント、タイミングは以下。

  1. AbsListView#setFastScrollEnabled(true) を呼び出す
  2. その直後にリフレクションで内部の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/10(木)

翌日に迫ったイベントに向けて最終打ち合わせ。

各担当の役割確認とか。

f:id:dai4649:20111110200130j:image

当日に配るステッカーを皆で切ったりとか。

f:id:dai4649:20111110221534j:image

f:id:dai4649:20111110222143j:image

11/11(金)

24Makeコンテスト。
場所は GuildCafe Costa
金曜夜から24時間でアプリ作って土曜に発表、というクレイジーなイベントw
仕事終わって皆様の頑張る様子をチラ見しにきてみた。
来年は作る側で参加しようかなー。すごく楽しそうだった。

参加者の皆様の様子。

f:id:dai4649:20111111235147j:image

11/12(土)

9:00
スタッフメンバー集合。
場所は 福岡県Rubyコンテンツ産業振興センター。
自分は間違えて金曜の会場、GuildCafe Costa へ行ってしまうという醜態をさらすw

会場設営やったり。

f:id:dai4649:20111112104925j:image

スタッフミーティングやったり。

f:id:dai4649:20111112110045j:image

f:id:dai4649:20111112121150j:image

11:00
ホモ弁に予約していたスタッフメンバー分の弁当を受け取り行ったり。
司会させていただくセッションの講師の皆様と打ち合わせしたり。

弁当食べながら司会メンバーで打ち合わせの図。

f:id:dai4649:20111112120506j:image

twicca作者の @R246氏(青山さん) とパシャリ。

f:id:dai4649:20111112115338j:image

あんざいゆきさん との打ち合わせ。

f:id:dai4649:20111112125753j:image

自分はあんざいさんの大ファンなのでサインいただいちゃいました。
うん、自分きもいですねw

イベント開始前にみんなで交代で弁当タイム。

f:id:dai4649:20111112121315j:image

鹿。どや。

f:id:dai4649:20111127020250j:image

世界の みよしさん 発見!

f:id:dai4649:20111127020258j:image

13:00
開場。
参加者が続々。
すぐにメイン会場が埋まる。

受付の様子

f:id:dai4649:20111112132002j:image

美女ぞろい。

f:id:dai4649:20111112121614j:image

ステッカーたくさん。

f:id:dai4649:20111112121235j:image

オープニング、基調講演。

f:id:dai4649:20111112130621j:image

ブースの様子。

f:id:dai4649:20111112131006j:image

f:id:dai4649:20111112142359j:image

f:id:dai4649:20111112124715j:image

f:id:dai4649:20111112123122j:image

f:id:dai4649:20111112175009j:image

f:id:dai4649:20111112181053j:image

Arduino体験講座。

f:id:dai4649:20111112140853j:image

f:id:dai4649:20111112142438j:image

レッドブル様との交流w

f:id:dai4649:20111112145117j:image

LT会場。大盛況。

f:id:dai4649:20111112161224j:image

個人的にどストライクだったのが、
ブリリアントサービス様のNFCクエスト体験。
@R246氏、サイバーエージェント三島木さん と超強力なパーティ組んで挑んだものの。。

f:id:dai4649:20111112134437j:image

敗北('A`)
魔王強い。

f:id:dai4649:20111112134459j:image

ブースの皆様と。

f:id:dai4649:20111112134922j:image

19:00
閉会式。

全員で手を繋いでスタッフ一同挨拶。

f:id:dai4649:20111112200435j:image

f:id:dai4649:20111112200436j:image

19:30 - 21:00
懇親会。

乾杯。

f:id:dai4649:20111112192850j:image

わいわい。

f:id:dai4649:20111112192729j:image

f:id:dai4649:20111112204233j:image

三島木さんとあんざいさん

f:id:dai4649:20111112212047j:image

後片付け後、残っていたメンバーで集合写真。

f:id:dai4649:20111112210847j:image

終了後。

スタッフ数名で一緒に明星和楽Gates会場へ行ったあと、途中離脱して屋台へ移動。
屋台の後は酔っぱらってひとりで中洲をうろうろして川沿いで寝てた。(ひどい)
で、結局タクシーで5時すぎに帰宅。

f:id:dai4649:20111113014412j:image

イベントを終えて。

正直なところ、スタッフメンバーはイベント屋ではないので普段の自分の業務もある。
そんな状況下でも各メンバーが責任もってタスクをちゃんとこなし、
結果、イベントは大成功っていうのはやっぱりすごいことじゃないかと。
イベント終了後になんだかイロイロと考えてしまった。

f:id:dai4649:20111113060727j:image

いやー!!!本当に楽しかった!!
スタッフとして参加できて本当によかった!!
次回もぜひスタッフとして参加したいし、何かを発信する側にもなりたい。
改善すべき、反省すべき点はもちろんたくさんあるけれども、
イベント運営の難しさと楽しさを味わうことができて、
すごく貴重ないい経験ができた、参加できてよかった、楽しかった、というのが本音。

運営スタッフの皆様、講演者の皆様、参加された皆様、
本当にお疲れさまでした!!

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. アプリケーションのパッケージ名
  2. 起動するクラス名


1は AndroidManifest.xmlタグのpackage属性指定値。
起動するクラス(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);
}


うーん。微妙。