AsyncTaskのStatusを理解したくてFrameworkのコードを読んだメモ
RX全盛期のいま、こんなことに需要があるのか…ということは気にしない。
AsyncTaskのライフサイクルのstatusがぼんやりとしか理解できずググっても出てこなかったので調べたメモ。
具体的な疑問としては、taskの実行が終わったあと、初期状態(PENDING
)に戻るのか?それともFINISH
のまま?という点。
APIにもこのように書いてあるんだけどなんとなく釈然としない。taskのlifetimeってどういうことなんだろう。
Each status will be set only once during the lifetime of a task.
それではFrameworkのコードを読んでいきます。 読んだコードは6.0のもの。
初期値
当然ながら初期値は PENDING
.
synchronizeよりコストの低いvolatileを使ってるんですね。へぇぇ。
private volatile Status mStatus = Status.PENDING;
タスクの実行
タスクの実行命令がくるとまずは状態チェック。
PENDING
以外は許容しません。
チェックが終わるとRUNNING
状態に遷移して、onPreExecute()
が呼ばれます。
@MainThread public final AsyncTask<Params, Progress, Result> executeOnExecutor(Executor exec, Params... params) { if (mStatus != Status.PENDING) { switch (mStatus) { case RUNNING: throw new IllegalStateException("Cannot execute task:" + " the task is already running."); case FINISHED: throw new IllegalStateException("Cannot execute task:" + " the task has already been executed " + "(a task can be executed only once)"); } } mStatus = Status.RUNNING; onPreExecute(); mWorker.mParams = params; exec.execute(mFuture); return this; }
タスクの完了・キャンセル
AsyncTaskは内部でhandlerを持っておき、タスクが完了もくしはキャンセルされた時に終了処理を実行するためのMessage(MESSAGE_POST_RESULT
)を投げます。
private static class InternalHandler extends Handler { public InternalHandler() { super(Looper.getMainLooper()); } @SuppressWarnings({"unchecked", "RawUseOfParameterizedType"}) @Override public void handleMessage(Message msg) { AsyncTaskResult<?> result = (AsyncTaskResult<?>) msg.obj; switch (msg.what) { case MESSAGE_POST_RESULT: // There is only one result result.mTask.finish(result.mData[0]); break; case MESSAGE_POST_PROGRESS: result.mTask.onProgressUpdate(result.mData); break; } } }
ここで呼ばれるfinish()
の中で状態が FINISHED
になります。
このあとはもう状態操作を行わないので、FINISH
のまま。
private void finish(Result result) { if (isCancelled()) { onCancelled(result); } else { onPostExecute(result); } mStatus = Status.FINISHED; }
ということでわかったのは
onPreExecute()
の直前にRUNNING
になって、onCancelled()
かonPostExecute()
が完了したらFINISHED
になる- statusは循環するわけではなく、一方通行。一度
FINISH
になったらそこから遷移はしない - メンバ変数として持っておいて何度もそのままタスクを使い回すのも無理そう
まぁActivityのonPause()とかでcancelするためにメンバで参照保持しないとなんですけどね。
executeする前に状態チェックして FINISH
だったらnewしなおしてやるということで。
最近読んだAsyncTaskのメモリリークの話も一緒にどうぞ。
meta-dataで数値だけのStringを渡したい
前回manifestPlaceholders最高!٩( 'ω' )و みたいな記事かいといてアレですが、ハマりどころがあったので記事に残しておきます。 某SDKを使うためにAndroidManifestのmeta-dataにidを記載する必要があり、ここにmanifestPlaceholdersを使っていました。
<meta-data android:name="hoge" android:value="${fuga}"/>
しかしこれだとなぜかnull扱い…
ただし、 .foo
を足すとなぜかfugaの中身も .foo
も取れます。
<meta-data android:name="hoge" android:value="${fuga}.foo"/>
何が起こっていたかというと、あくまで予想なのですが、
おそらくSDKの中身が ActivityInfoオブジェクトの metaData
に対して getString()
してるんじゃないかなぁと思います。
だから .foo
をつけると文字列になるので問題なくfugaの中身も取れたっぽい。
※予想コード
ActivityInfo info = getPackageManager().getActivityInfo(getComponentName(), PackageManager.GET_META_DATA); String fuga = info.metaData.getString("hoge");
型が違うものをgetしようとするとこんなエラーが出ます。
Bundle : Key hoge expected String but value was a java.lang.Integer. The default value <null> was returned.
自分でかいたコードなら getInt()
に変えれば解決するけれどSDKの中身なので値を渡す側でどうにかするしかない。
ちょっとググってみたらバックスラッシュを一つ入れれば何故か解決するらしい。
回答つけた人もこんなこと言っちゃってます。
I have no idea quite how this works, if I'm honest.
そんなわけでこれでうまく動きました٩( 'ω' )و
<meta-data android:name="hoge" android:value="\ ${fuga}"/>
Build typeによってアプリアイコンを分ける
はじめに
この記事は諸事情によりRibbonizerライブラリが使えない人たち向けに書いたものです。 Ribbonizerが使える人はそちらを使ったほうが良いです。
やりたいこと
Debug buildの時とRelease buildの時にapp idやapp nameを変えて一つの端末の中に両者を共存させていたのですが、 アイコンが同じだと分かりづらいのでアイコンも分けることに。
どうするか
Ribbonizerという素敵なライブラリがあります。これをつかえば2行ほどbuild.gradleに書き足すだけでやりたいこと達成です。
どうするか その2
以下、諸事情により↑が使えない人向けです。 自分の場合だと、ローカルでbuildする場合は何の問題もないのですが、 Circle CIでbuildするときにCircle CIの4G制限に引っかかってしまいdied unexpectedly…(´・_・`)
とりあえずライブラリをつかわず自前でどうにかする方向で考えます。 こちらの記事がとても参考になりました。
typeの縛りが激しいresValueと違って、placeholderはdrawableも扱えます。
まずはこんな感じでdefaultConfigに追記。
defaultConfig { manifestPlaceholders = [appIcon:"@drawable/your_app_icon"] }
debug版のみアイコンを変えたいのでbuildtypeがdebugの時はdebug用のアイコンを指定します。 (自分の場合はRibbonizerが生成したdebug用のアイコンをbuild/ディレクトリ下から拾ってリネームして指定しました笑)
buildTypes { debug { manifestPlaceholders = [appIcon:"@drawable/your_debug_app_icon"] } }
最後にManifestでこれを指定すればOK.
<application android:icon="${appIcon}"
この方法だとお手軽に動くのですが、debug用のアイコンリソースがapkパッケージの中に含まれてしまいます。 それが耐えられない人はやっぱりRibbonizerでどうにかする方法で頑張ってください。
何番目のRadioButtonがチェックされているかうっかり取得されてしまう話
RadioGroup#getCheckedRadioButtonId() が、チェックが入ったviewのidではなく、何番目にチェックが入っているかを取得できると勘違いして、実際その勘違い通りの振る舞いをするように見えるケースが存在した調査ログです。
結論から言ってしまうと、RadioButtonにidを振らないとView#generateViewId() が1から順に勝手に新規idを振ってしまい、それを返してしまうという話です。
現象
RadioButtonを3つ配置したRadioGroupが存在しており、そのうち1番目にチェックをつけた状態でRadioGroup#getCheckedRadioButtonId()が1を返すケースがある。 この振る舞いになる条件は、
- RadioButtonにidをふらない
- 初回起動時のみ(n回目に対象Activityを起動した際には3n-2が返ってくる)
具体的にはこんなレイアウト
<RadioGroup android:layout_width="match_parent" android:layout_height="wrap_content" android:id="@+id/radiogroup_sample"> <RadioButton android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/sample1"/> <RadioButton android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/sample2"/> <RadioButton android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/sample3"/> </RadioGroup>
何がおこったか
RadioButtonがviewのhierarchyに追加される時点で、idが存在するかチェックし 存在しない場合は新規に追加します。
RadioGroup.java
private class PassThroughHierarchyChangeListener implements ViewGroup.OnHierarchyChangeListener { private ViewGroup.OnHierarchyChangeListener mOnHierarchyChangeListener; /** * {@inheritDoc} */ public void onChildViewAdded(View parent, View child) { if (parent == RadioGroup.this && child instanceof RadioButton) { int id = child.getId(); // generates an id if it's missing if (id == View.NO_ID) { id = View.generateViewId(); child.setId(id); }
View.java
private static final AtomicInteger sNextGeneratedId = new AtomicInteger(1); public static int generateViewId() { for (;;) { final int result = sNextGeneratedId.get(); // aapt-generated IDs have the high byte nonzero; clamp to the range under that. int newValue = result + 1; if (newValue > 0x00FFFFFF) newValue = 1; // Roll over to 1, not 0. if (sNextGeneratedId.compareAndSet(result, newValue)) { return result; } } }
generateViewId() は1で初期化したAtomicIntegerから順にインクリメントした値を返すので、それぞれn番目がチェックされているかのように振舞っていたのです。(returnするのがnewValueではなくresultなので初回は1が返ってきます。) ただし、例えばActivityを再起動するなどして再度viewのhierarchyに追加されるときは1からではなく続きからインクリメントされるので、初回起動時のみn番目がチェックされているかのように見えていたというオチでした。
余談
何番目がチェックされているか返すmethodはRadioGroupを拡張したクラスでaddView() をoverrideしてやれば作れそう。 ただし、「何番目がチェックされているか」ではなく「何がチェックされているか」の方がどう考えても重要なのであまり使い道はないかもしれない。
public class CustomRadioGroup extends RadioGroup { private List<RadioButton> mChildren = new ArrayList<>(); public CustomRadioGroup(Context context) { super(context); } public CustomRadioGroup(Context context, AttributeSet attrs) { super(context, attrs); } @Override public void addView(View child, int index, ViewGroup.LayoutParams params) { if (child instanceof RadioButton) { mChildren.add((RadioButton) child); } super.addView(child, index, params); } public int getSelectedPosition() { int selectedViewId = getCheckedRadioButtonId(); if (selectedViewId == -1 || mChildren.isEmpty()) { return -1; } for (int i = 0; i < mChildren.size() -1; i++) { if (mChildren.get(i).getId() == selectedViewId) { return i + 1; } } return 0; } }
読書の習慣化に成功した話
今年に入って割と効率よくインプットが進んでいるので知見を共有します。
きっかけ
バレンタインにバズったこの記事に影響され、毎日コツコツと小さなインプットをしようと決意。
何をやったか
昨年からダラダラと読んでいたSoft Skillsを読み切ることに決めました。
Soft Skills: The Software Developer's Life Manual
- 作者: John Z. Sonmez,Scott Hanselman,Robert C. Martin
- 出版社/メーカー: Manning Pubns Co
- 発売日: 2014/12/29
- メディア: ペーパーバック
- この商品を含むブログを見る
こちらはまだ翻訳版が出ておらず、英語版を読むしかないのでなかなか読み進められずにいました。 (英語自体はとても簡単な表現を使って書かれているので、英語苦手な人にもオススメです。) こちらの本を1日1章ずつ読むことにしました。
どうやったか
まずはこれから先一週間で毎日どの章を読むか決めます。 決めたらTo Do Listに登録していきます。私はこのアプリを使っています。
このアプリはマテリアルデザインが割と良くでおり、シンプルで使いやすいので気に入っています。 またリマインダー機能があるのと、その日のタスクが全て終わったらちょっとほっこりメッセージをくれるのが好きです。
一週間が経過するか、もしくはその週のタスクを消化しきったらまたその日から一週間先のタスクを登録していきます。 なぜ一週間ごとにやるかというと、自分の性格上週によって進捗が異なることがわかったからです。
結果
1ヶ月ほどで無事読み終わりました。(たしか40章ほどあったので結構かかりました。) 読み終えたのももちろん良かったのですが、毎日何かしらのインプットもするという習慣がついたのがおおきな成果でした。 現在は次の本を読んでいます。
おまけ
上記To Do Listは読書だけでなく、植物の水やりなど毎日繰り返すタスクの管理も大変便利なので 我が家のパクチーも元気です。
Android N からMulti Language and Multi Locale になるらしい
※本エントリは2016/4/8 に書いたもので、Android N Preview 1 時点で公開されている情報に基づいています。
昨日「まったりAndroid Framework Code Reading #3」に参加しました。
mandroidfcr.doorkeeper.jp
結局Frameworkのソースコードを読まずにAndroid N Previewを読んだというオチ。
NからMulti Language and Multi Locale になるらしい。
間違ったこと書いてたら指摘して欲しいです。
公式資料はこちら
http://developer.android.com/intl/ja/preview/features/multilingual-support.html
概要の翻訳版はこちら
http://qiita.com/tnagao3000/items/562e81e8ac9bb581afcc#multi-locale-support-more-languages
Nから設定で複数言語選べるようになる
M以前ではラジオボタンで単一でしか選択できなかった言語設定ですが、Nからは複数言語選択できるようになるらしい。
複数言語を選択する場合、選択する言語には優先度がつきます。
(例:1.日本語、2.Englishなど)
Nから端末がサポートするLocaleが大幅に増える
残念ながら各言語が具体的にどれくらい増えるかどうかは言及されていません。
ただ、例として出てくるar_EG (Arabic - EGYPT) は27もArabicなLocaleに対応するそうです。
NからLocaleに階層の概念が持ち込まれる
例えばM以前ではLocaleクラスのen_CA(CANADA), fr_CA(CANADA_FRENCH), en(ENGLISH), fr(FRENCH)の間に階層概念はありませんでしたが、NではLocaleに階層概念が持ち込まれました。これにより、より柔軟にユーザに適切な言語を提供できるようになります。
Nからシステムがどの言語を選択するかルールが変わる
M以前
ユーザが設定できる言語は一つで、アプリ側でその言語リソースが用意されている場合はそのリソースが使われます。
(例: 日本語設定している人がFacebookを使うと日本語をサポートしているFacebookで日本語表示になる)
アプリ側がユーザが設定している言語をサポートしていない場合、アプリがサポートするデフォルトの言語表示になります。
(例: 日本語設定している人がAWS Consoleを使うと日本語をサポートしていないAWS Consoleで英語表示になる)
N以降
ユーザが設定できる言語は複数。また、Localeに階層概念が持ち込まれているため、より柔軟に言語を選択することができる。
例えば、提供するアプリのサポートする言語が以下で:
- en_US (US English) <- default
- es_ES (Spanish)
端末の設定言語が以下の場合:
- es_MX(Spanish mexico)
M以前ではen_USが選択されていましたが、Nだと es が一致するes_ESの方が選択されます。
また、複数言語設定した場合の例。
提供するアプリのサポートする言語が以下で:
- en_US (US English) <- default
- de_DE (GERMANY)
- es_ES (Spanish)
- it_IT (ITALY)
端末の設定言語が以下の場合:
- fr_CH (French - SWITZERLAND)
- it_IT (Italian - SWITZERLAND)
まず、fr_CHを探しに行って見つからず、次にfrを探しに行って見つからず、
次にfrの子供を探しに行って見つからず、次にit_ITを探しに行って見つからず、
次にitを探しに行って見つからず、次にitの子供を探しに行ってついに晴れてマッチしたと判定されます。
もしここで見つからない場合はアプリデフォルトのen_USになります。
パフォーマンス注意点
今までのLocaleが階層関係の子になる場合が多いので、そこを注意しないとパフォーマンスで問題が出ます。
上の例のように、子供でぴったり一致するものがいなければ親を見て、さらにその子供を探しに行くからです。
例えば、今までen-GB(UK) だったものはen-001(international English)にリネームするべきと記載されています。
en-GBの最も一般的な親はen-001になるからです。
NからAdditional Locale を設定できるようになるらしい
Nから追加されたLocaleList.GetDefault()を使うと端末に設定されているLocaleのリストを取得できるようになります。
これによりより洗練された言語表示ができるようになるらしい。
たとえば、翻訳かける時もより適切な言語にできるとか。
// 正直どう使うのか想像できなかった…
Formattersについて
M以前だと主要な言語(en, es, ar, fr, ru)でも1,2種類しかLocaleは用意されていませんでした。
そのせいで数字や日時がハードコードされてしまうことがあり、ユーザに混乱を与えることがあったけれど
Nからは数字や日時をハードコードする代わりにformatterを使うことが推奨されています。
主要な例はアラビア言語。Nでは`ar_EG`が**27**もアラビックロケールをサポートするそうです。
formatterの使い方の例は以下の通り。
format(locale, "Choose a %d-digit PIN", 4)
// RTL言語の読めない数字周りの表示確認つらそう…
AndroidでChromeの共有メニューにアプリを追加する
試したこと
まずは公式ドキュメントを参照。
Receiving Simple Data from Other Apps | Android Developers
Intent FilterをAndroidManifest.xml に登録します。
URLを受け取りたかっただけなのでmimeTypeがtextの物のみ指定。
サンプルを同様の記載をしたところ、Chromeの共有メニューにテストアプリのアイコンが無事出てきました。
ただし、これだとActivity名が表示されてしまうので、アプリ名に変えるためIntentFilterにlabelを指定します。
frameworksのこのへんのコードを参考にしました。
Cross Reference: /packages/apps/UnifiedEmail/AndroidManifest.xml
<activity android:name=".YourActivity" android:label="@string/your_app_name_lable" > <intent-filter android:label="@string/your_app_name"> <action android:name="android.intent.action.SEND" /> <category android:name="android.intent.category.DEFAULT" /> <data android:mimeType="text/plain" /> </intent-filter> </activity>
次に上記で指定したActivity側のコード。URLを受け取る側です。
`Intent.EXTRA_TEXT にリンク情報が入っています。
if (TextUtils.equals(getIntent().getAction(), Intent.ACTION_SEND)) { Bundle extras = getIntent().getExtras(); String extraText = extras.getCharSequence(Intent.EXTRA_TEXT).toString(); if (!TextUtils.isEmpty(extraText)) { doSomething(extraText); } }