ページ

2013/12/30

2013年に @wada811 が読んだ技術書まとめ

2013年も終わりに近づいているので@wada811が読んだ技術書を振り返ってみようかと思います。
2012年はこちら→2012年に @wada811 が読んだ技術書まとめ | DevAchieve(約40冊)

2013/03/02 [書評]正規表現辞典 | DevAchieve

正規表現辞典は本当に良い。
通読しても良し、かいつまんで読んでも良し、辞書としても良しの三拍子揃った素晴らしい書籍です。
正規表現が使えれば大抵のテキスト処理は簡単にできるのでエンジニアもそうでない人も読んだほうが良いです。
ついでに僕の Amazon リンクから買うとアフィリエイト料が入ってなお良しです。
(最近、アフィリエイト料率が下がって全然儲からないです。つらい。)

2013/05/31 [書評]モバイルデザインパターン | DevAchieve

デザイン系の書籍は [書評]iPhoneアプリ設計の極意 ―思わずタップしたくなるアプリのデザイン に続いて2冊目。
会社にあったので読んでみた。内容的にはiPhoneアプリ設計の極意とそんなに変わらないかも。
それぞれのデザインパターンについて簡潔な説明でまとめた付録がわかりやすかった。どちらか一冊で良さそう。
もう一冊読むなら一味変えてインタフェースデザインの心理学を読んだほうが得るものありそう。
あと、ユーザビリティで有名っぽいニールセン博士のモバイル・ユーザビリティも微妙に気になってる。
へいしゃー!買ってくれー!間に合わなくなっても知らんぞー!!

2013/08/25 [書評]HTTPの教科書 | DevAchieve

概要を知るには良い感じの本です。挿絵とか文体とか読みやすくてわかりやすかったです。
Web 関連の技術や必要な知識についてHTTPの教科書のテイストで解説している本があったら
Web 系プログラマの新人教育に使えそうな気がします。
Web への攻撃技術という章でセキュリティ関連の解説もわかりやすく行われているので大変良いです。
(引越に際して売ろうかと思っていたけど取っておこうかな)

2013/08/29 [書評]達人に学ぶDB設計 徹底指南書 初級者で終わりたくないあなたへ | DevAchieve

データベース初心者がSQLを極めるためのオススメ書籍6冊の本のうち、読んだ本3冊目です。
ちなみに達人に学ぶ SQL徹底指南書は読んでません。
結構 SQL は書けるようになって複雑なのとか行けるようになったから
そこまで切羽詰まってないので達人に学ぶ SQL徹底指南書はいいかなぁって感じです。
それより DB 設計とかパフォーマンスチューニング的な所を身につけていきたいです。
DB 設計については達人に学ぶDB設計 徹底指南書が良かったので何とかなりましたが、
パフォーマンスチューニングはどこから知識を得ればいいんでしょうね?
数十万件規模のテーブルをジョインジョイントキィするとサービスをKOしてしまって
開発第二ラウンドが発生してしまうので勘弁願いたいんですよねぇ。

1年で書籍4冊の書評を書いた!

2012年は約40冊読んで25冊くらい書評書いたのに今年少なすぎじゃないか?アカン…。
まぁその分DevCameraの開発にかなりの時間を割いたし、
書籍を読む以上に Androider としての技術力が上がったから良いかな?
Android の NDK とか OpenGL, OpenCV のような特殊な事以外だったら
書籍無くてもリファレンスとか見てだいたい実装できそうな気がする。
まぁアニメーションとか UX 的な気持ちよさみたいな部分の実装能力はまだまだだけど。

読んだけど書評を書いていないのは以下の7冊かな。
【改訂新版】 Linuxコマンド ポケットリファレンス
良かった。手元においておきたい感じだけど
普段使うコマンドってそんなに変わらないから使用機会が少ないのが難点。
しかも家に置いてあるという。会社だったらググって解決なので使い所が難しい。
入門UNIXシェルプログラミング
まだ読み途中で積ん読になってる…。
興味はあるし、ちょっとしたシェルスクリプトで手間が省けたら嬉しいから学びたいけど
いかんせん使用機会の少なさがモチベーションの低さになってしまう。
入門 Androidアプリケーションテスト
UnitTest やろうというのが2013年の目標だったので買ってみた。
しかし、開発したアプリがカメラ機能がメインなのでテストがやりにくくて全然やってない。
仕事でカメラとか端末機能を使わないアプリでは UnitTest をやれたので良かったと思う。
Smashing Android UI レスポンシブUIとデザインパターン
デザイン系の書籍3冊目で、珍しく Android をテーマにしたデザインパターン本です。
凄く良い本だったし、ちゃんと書きます。放置しててごめんなさい…!
書きました→[書評]Smashing Android UI レスポンシブUIとデザインパターン | DevAchieve
OpenGL ES 2.0 Androidグラフィックスプログラミング
OpenGL ES を Android から呼び出す方法を解説している本です。コレも記事書きます…!
年をまたいでしまう…!ごめんなさい…!
書きました→[書評]OpenGL ES 2.0 Androidグラフィックスプログラミング | DevAchieve
Effective Java 第2版
ピアソン桐原社の技術書などが在庫限りという事で入手困難に陥る前に買いました。
記事書きます…!読んだらすぐに書評書かないとダメだ…!
書きました→[書評]Effective Java 第2版 | DevAchieve
プレゼンテーションZen 第2版
こちらもピアソン桐原社の書籍で、会社にあったので読みました。
4月になる前にこのへんの書籍の書評を書こう…!

Android も良いけど他のことももっと色々出来るようになっていこう!
そしてちゃんと書評記事を書こう!速さが足りない!

2013/12/23

[Android]Apache Commons Codec で NoSuchMethodError がでる

軽く SHA1 ハッシュするかー、どうせまた使うだろうから Util クラス作るかーと思って
実装方法調べるとアルゴリズム指定する所で文字列で指定するみたい。
このへん決まりきっているんだから定数欲しいよなー
もう作ってる人いるんじゃないのかー?と調べてみたら
Apache Commons Codec があったので使ってみた。

そしたら DigestUtils でエラーが出たので調べると StackOverflow の質問があった。
apache - Method not found using DigestUtils in Android - Stack Overflow
eclipse - NoSuchMethodError using commonc codec in Android application - Stack Overflow
読むのが面倒なのでコピーしてメソッド削ってメソッド名変えて
MessageDigest ってあまり馴染みがないのでクラス名を HashUtils にした。
add HashUtils · 6bb32f1 · wada811/AndroidLibrary-wada811
codec.StringUtils はクラス名衝突が起きる可能性が高かったので EncodingUtils にしておいた。

/*
 * Copyright 2013 wada811<at.wada811@gmail.com>
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package at.wada811.utils;
/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import at.wada811.codec.EncodingUtils;
import at.wada811.codec.Hex;
import at.wada811.codec.MessageDigestAlgorithms;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;

public class HashUtils {

    private static MessageDigest getDigest(final String algorithm){
        try{
            return MessageDigest.getInstance(algorithm);
        }catch(final NoSuchAlgorithmException e){
            throw new IllegalArgumentException(e);
        }
    }

    private static MessageDigest getMd5Digest(){
        return getDigest(MessageDigestAlgorithms.MD5);
    }

    private static MessageDigest getSha1Digest(){
        return getDigest(MessageDigestAlgorithms.SHA_1);
    }

    private static MessageDigest getSha256Digest(){
        return getDigest(MessageDigestAlgorithms.SHA_256);
    }

    private static MessageDigest getSha512Digest(){
        return getDigest(MessageDigestAlgorithms.SHA_512);
    }

    public static byte[] md5(final byte[] data){
        return getMd5Digest().digest(data);
    }

    public static byte[] md5(final String data){
        return md5(EncodingUtils.getBytesUtf8(data));
    }

    public static String toMD5(final byte[] data){
        return Hex.encodeHexString(md5(data));
    }

    public static String toMD5(final String data){
        return Hex.encodeHexString(md5(data));
    }

    public static byte[] sha1(final byte[] data){
        return getSha1Digest().digest(data);
    }

    public static byte[] sha1(final String data){
        return sha1(EncodingUtils.getBytesUtf8(data));
    }

    public static String toSHA1(final byte[] data){
        return Hex.encodeHexString(sha1(data));
    }

    public static String toSHA1(final String data){
        return Hex.encodeHexString(sha1(data));
    }

    public static byte[] sha256(final byte[] data){
        return getSha256Digest().digest(data);
    }

    public static byte[] sha256(final String data){
        return sha256(EncodingUtils.getBytesUtf8(data));
    }

    public static String toSHA256(final byte[] data){
        return Hex.encodeHexString(sha256(data));
    }

    public static String toSHA256(final String data){
        return Hex.encodeHexString(sha256(data));
    }

    public static byte[] sha512(final byte[] data){
        return getSha512Digest().digest(data);
    }

    public static byte[] sha512(final String data){
        return sha512(EncodingUtils.getBytesUtf8(data));
    }

    public static String toSHA512(final byte[] data){
        return Hex.encodeHexString(sha512(data));
    }

    public static String toSHA512(final String data){
        return Hex.encodeHexString(sha512(data));
    }
}
byte[] を使うか返すかするメソッド使うかなぁ?使用タイミングがわからないけど一応残しておいた。

2013/12/22

[Android]PRNGFixes makes SecureRandom more random

夏に Some SecureRandom Thoughts | Android Developers Blogという記事や
Androidの乱数生成機能の問題、36万以上のアプリに影響する恐れという記事があり、
へーとか思いながらスルーしていたんだけど SecureRandom を使う機会があったので
対策コードを適用した Application クラスを作ってみた。

add PRNGFixes · 1c3d52c · wada811/AndroidLibrary-wada811

/*
 * Copyright 2013 wada811<at.wada811@gmail.com>
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package at.wada811.app;

import android.app.Application;
import at.wada811.security.PRNGFixes;
import java.security.SecureRandom;

/**
 * Fixes for the output of the default PRNG having low entropy.
 * {@link PRNGFixes} makes {@link SecureRandom} more random.
 * 
 * @see <a href="http://android-developers.blogspot.jp/2013/08/some-securerandom-thoughts.html">Some
 *      SecureRandom Thoughts | Android Developers Blog</a>
 */
public class AppFixedPRNG extends Application {

    @Override
    public void onCreate(){
        super.onCreate();
        PRNGFixes.apply();
    }

}

こんな感じで使おう!
public class App extends AppFixedPRNG {

}
<application android:name=".App" >

アプリ内課金のノンスとかも SecureRandom を使っているらしいし、忘れないようにしよう。

2013/12/21

[Android]Toastをカスタマイズして表示時間を変更したりアニメーションを変更したり即時非表示にしたり消えたことを検知したりしたい

この記事は Android Advent Calendar 2013 の 12月21日の記事ではありませんし、
よく考えたら私は Android Advent Calendar 2013 に参加もしていませんでしたが、
Androider の役に立つかもしれない Toast をカスタマイズしたものを公開します。

経緯

Toast の表示時間を Toast#LENGTH_LONG(約4秒),
Toast#LENGTH_SHORT(約2秒)から変更したい!
じゃあ探してみるか、と見つけたのが以下の2つ。
琴線探査: AndroidでToastの表示時間を自由に設定したりアニメーションなしで即非表示したい!
Android Toastの表示時間を長く/短くする 【Android TIPS】
前者はサイズ指定とかが面倒だったのと Toast#makeText の気軽さがなかったので微妙でした。
後者は Toast#makeText 限定でカスタム View を表示できなかったので使いませんでした。

もっと Toast ライクで自由の効く拡張的な実装はないのか!と憤ってしまったので
Toast の実装を眺めて Toast の拡張である Toaster を作ってみました。
Toaster | wada811/AndroidLibrary-wada811

Toast と Toaster の比較

Toast Toaster
任意の表示時間の設定 ×
表示/非表示のアニメーション ×
一括キャンセル ×
非表示時のイベント通知 ×
表示時間にマイナスを設定すれば明示的にキャンセルするまで Toast を消さないようにすることもできます。
Web でよくあるチュートリアルで出てくるポップアップのように使えるかと思います。
アニメーションにこだわりたい開発者のためにアニメーションを設定できるようにしました。
アプリがバックグラウンドに回ったのに投稿ボタンを連打したせいで
「あれ?何かがおかしいようです。」「あれ?何か「あれ?「あれ?「あれ?何かがおかしいようです。」
のように Toast が残り続けることがないように一括キャンセルできるようにしました。
ToastOnDismiss の需要もありそうなのでリスナーをセットできるようにしました。

Toast と Toaster の対応

Toast Toaster
Toast 管理用 Service の起動・実行 システム Toaster
Toast 自体 Toast ToastBread
Toast の表示/非表示 TN Bread
ToastQueue の管理・表示/非表示のイベント通知 NotificationManagerService ToasterService
Toast はキュー管理の Service とその起動がシステム側に組み込まれているので
再現するには自分でキュー管理用の Service を実装する必要がありました。
また、その Service の起動も自分で行う必要があります。

Toaster の使い方

AndroidManifest.xml に Toast 管理用 Service を追加します。

<service android:name="at.wada811.widget.ToasterService" />

Toaster のインスタンス化

@Override
protected void onCreate(Bundle savedInstanceState){
    super.onCreate(savedInstanceState);
    mToaster = new Toaster(this);
}
自動的に Toast 管理用 Service が bind されます。
※ ServiceConnection が確立する僅かな時間の間に表示しようとした Toast は無視されます。

Toaster の電源を切る

@Override
protected void onPause(){
    super.onPause();
    mToaster.unplug();
}
自動的に Toast が全てキャンセルされ、Toast 管理用 Service が unbind されます。

動作サンプル

ToasterActivity.java | wada811/AndroidLibrary-wada811 を見てください。

Toaster ができるまで

Toast ってどうやって表示されているの?

システムに常駐している NotificationManagerService でキュー管理されています。
参考: Toast の表示時間を変更する | アンドロイドな日々
フレームワークのソースコードを見に行くと何を行っているかなんとなくわかります。
services/java/com/android/server/NotificationManagerService.java - platform/frameworks/base
OSS だとこういうのが簡単に確認できて素晴らしい。
Toast と Notification の処理が混ざっていたので Toast の部分を切り出したのがこちら。
ToasterService.java | wada811/AndroidLibrary-wada811

Toast は IPC ( Interprocess Communication: プロセス間通信) をしなければならないので
AIDL ( Android Interface Definition Language: Android インターフェイス定義言語 ) で
インターフェースを定義しています。
ITransientNotification.aidl - android-source-browsing - Android Source Browsing Test
Toaster では IPC をする必要がないので 普通にインターフェース ToasterCallback を定義しています。
Toaster$ToasterCallback | wada811/AndroidLibrary-wada811

Toast#makeText のデザインで表示したい

com.android.internal.R.layout.transient_notification を使っているようなので再現しましょう。
Cross Reference: /frameworks/base/core/res/res/layout/transient_notification.xml
<?xml version="1.0" encoding="utf-8"?>
<!--
/* //device/apps/common/res/layout/transient_notification.xml
**
** Copyright 2006, The Android Open Source Project
**
** Licensed under the Apache License, Version 2.0 (the "License");
** you may not use this file except in compliance with the License.
** You may obtain a copy of the License at
**
**     http://www.apache.org/licenses/LICENSE-2.0
**
** Unless required by applicable law or agreed to in writing, software
** distributed under the License is distributed on an "AS IS" BASIS,
** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
** See the License for the specific language governing permissions and
** limitations under the License.
*/
-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:background="?android:attr/toastFrameBackground">
    <TextView
        android:id="@android:id/message"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_weight="1"
        android:layout_gravity="center_horizontal"
        android:textAppearance="@style/TextAppearance.Toast"
        android:textColor="@color/bright_foreground_dark"
        android:shadowColor="#BB000000"
        android:shadowRadius="2.75"
        />
</LinearLayout>
上からAndroidソースコード検索サービス - Developer Collaboration Projectで検索していきましょう。
Search android:attr/toastFrameBackground
/frameworks/base/core/res/res/values/
themes.xml  208 <item name="toastFrameBackground">@android:drawable/toast_frame</item>
@android:drawable/toast_frame を SDK から画像リソースをコピーしてきます。
/{AndroidHome}/platforms/android-x/data/res/drawable-x/toast_frame.9.png
以下のページを TextAppearance.Toast で検索。
Diff - 1962e26^^2..1962e26^ - platform/frameworks/base - Git at Google
/core/res/res/values/styles.xml
+    <style name="TextAppearance.Toast">
+        <item name="android:fontFamily">sans-serif-condensed</item>
+    </style>
Search @color/bright_foreground_dark
/packages/apps/Stk/res/values/
colors.xml  19 <color name="bright_foreground_dark">#ffffffff</color>

そこそこ大変でしたがここまで調べてまとめたのが以下のファイルです。
AndroidLibrary@wada811/res/layout/toast.xml | wada811/AndroidLibrary-wada811
Toaster では makeText で Toast 標準のデザインと変わらない表示を出すことができるようになっています。

表示/非表示のアニメーションを Toast 標準に合わせたい

プログラムから使うだけなら android.R.style.Animation_Toast を使えばいいです。
改造するなら以下を調査・変更する必要があります。
core/res/res/values/styles.xml - platform/frameworks/base を Animation.Toast で検索。
<style name="Animation.Toast">
    <item name="windowEnterAnimation">@anim/toast_enter</item>
    <item name="windowExitAnimation">@anim/toast_exit</item>
</style>
toast_enter.xml, toast_exit.xml は /{AndroidHome}/platforms/android-x/data/res/anim/ にあります。
<?xml version="1.0" encoding="utf-8"?>
<!--
/* //device/apps/common/res/anim/fade_in.xml
**
** Copyright 2007, The Android Open Source Project
**
** Licensed under the Apache License, Version 2.0 (the "License"); 
** you may not use this file except in compliance with the License. 
** You may obtain a copy of the License at 
**
**     http://www.apache.org/licenses/LICENSE-2.0 
**
** Unless required by applicable law or agreed to in writing, software 
** distributed under the License is distributed on an "AS IS" BASIS, 
** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
** See the License for the specific language governing permissions and 
** limitations under the License.
*/
-->

<alpha xmlns:android="http://schemas.android.com/apk/res/android"
        android:interpolator="@interpolator/decelerate_quad"
        android:fromAlpha="0.0" android:toAlpha="1.0"
        android:duration="@android:integer/config_longAnimTime" />
<?xml version="1.0" encoding="utf-8"?>
<!--
/* //device/apps/common/res/anim/fade_out.xml
**
** Copyright 2007, The Android Open Source Project
**
** Licensed under the Apache License, Version 2.0 (the "License"); 
** you may not use this file except in compliance with the License. 
** You may obtain a copy of the License at 
**
**     http://www.apache.org/licenses/LICENSE-2.0 
**
** Unless required by applicable law or agreed to in writing, software 
** distributed under the License is distributed on an "AS IS" BASIS, 
** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
** See the License for the specific language governing permissions and 
** limitations under the License.
*/
-->

<alpha xmlns:android="http://schemas.android.com/apk/res/android"
        android:interpolator="@interpolator/accelerate_quad" 
        android:fromAlpha="1.0" android:toAlpha="0.0"
        android:duration="@android:integer/config_longAnimTime" 
/>

Toast 管理用 Service を作りたい

以下のサイトが参考になりました。
Androidでの常駐型Serviceを使う方法(LocalServiceによる常駐アプリ) | Tech Booster
Androidのバックグラウンド処理に使うServiceのまとめメモ - リア充爆発日記
Service を使ったことがほとんどなかったので使いこなせるようになりたいですが
いまいちよくわからないので詳しく色々解説している書籍など欲しいです。

その他

基本全部コピペでリネーム、Toaster 独自機能の追加でなんとかなりました。

次の人?

Android Advent Calendar 2013じゃないのでいませんでした。。。
もし使ってみてもらって問題があったり、微妙なところがあったら PR してください。
よろしくお願いします。

2013/12/17

[Android]SectionedIndexerを使ってIndex付きListFragmentを作る

電話帳によくある右側のアレを実装してみたくなって検索してみたら
SectionedIndexer がそれっぽいということがわかりました。
じゃあ作ってみようということで作ってみたのがこちら↓です。
add IndexingListFragment · bd3ef29 · wada811/AndroidLibrary-wada811

IndexingListAdapter が implements している SectionIndexer のメソッドの
getSections, getPositionForSection, getSectionForPosition がキモです。
SectionIndexer | Android Developers

そして作ってみたものの動画がこちらです。

スクロールするとスクロールバーのつまみが画面外へ消えていくという現象に悩まされています。
つまみを持ってスクロールすればセクションヘッダーがそれに合って変わっているのがわかりますが、
普通にスクロールするとつまみは画面外へ消えていってしまいます。

ゴチャゴチャ書き換えていたら、スクロールしてもつまみは画面外へ消えないけど
逆につまみを持ってスクロールした時にセクションヘッダーとフキダシが合わないという現象が起きました。
あちらを立てればこちらが立たず…。
誰か解決できたら教えて下さい…。

2013/12/16

[Android]端末が振られたことを加速度センサーから検出する

せっかく SensorFragment を作ったんだから何かサンプルを作りたかったのだけれど
ようやくそれっぽいものができたので公開してみる。
[Android]加速度センサーのノイズ除去とSensorFragment | DevAchieve
もうちょっと面白みがあると良いんだけど何も思いつかなかったから
android.hlidskialf.com/blog/code/android-shake-detection-listener参考に
パクってシェイクを検知する Activity を作った。
SensorActivity.java | wada811/AndroidLibrary-wada811

public class SensorActivity extends FragmentActivity implements SensorCallbackProvider {

    final SensorActivity self = this;

    private static final int SPEED_THRESHOLD = 25;
    private static final int SHAKE_TIMEOUT = 500;
    private static final int SHAKE_DURATION = 1000;
    private static final int SHAKE_COUNT = 3;
    private int mShakeCount = 0;
    private long mLastTime = 0;
    private long mLastAccel = 0;
    private long mLastShake = 0;
    private float mLastX, mLastY, mLastZ = 0;

    @Override
    protected void onCreate(Bundle savedInstanceState){
        super.onCreate(savedInstanceState);

        ArrayList<Integer> sensorTypes = new ArrayList<Integer>();
        sensorTypes.add(Sensor.TYPE_LINEAR_ACCELERATION);
        SensorFragment sensorFragment = SensorFragment.newInstance(sensorTypes);
        getSupportFragmentManager().beginTransaction().add(sensorFragment, SensorFragment.TAG).commit();
    }

    @Override
    public SensorCallback getSensorCallback(){
        return new SensorCallback(){
            @Override
            public void onLinearAccelerationSensorChanged(float x, float y, float z){
                LogUtils.v("x: " + String.format("%+01.2f", x) + ", y: " + String.format("%+01.2f", y) + ", z: " + String.format("%+01.2f", z));
                boolean isShaked = detectShake(x, y, z);
                if(isShaked){
                    AndroidUtils.showToast(self, "shaked!");
                }
            }
        };
    }

    public boolean detectShake(float x, float y, float z){
        boolean isShaked = false;
        // シェイク時間のチェック
        long now = System.currentTimeMillis();
        if(mLastTime == 0){
            mLastTime = now;
        }
        // SHAKE_TIMEOUT までに次の加速を検知しなかったら mShakeCount をリセット
        if(now - mLastAccel > SHAKE_TIMEOUT){
            mShakeCount = 0;
        }
        // 速度を算出する
        long diff = now - mLastTime;
        float speed = Math.abs(x + y + z - mLastX - mLastY - mLastZ) / diff * 10000;
        LogUtils.d("speed: " + speed);
        if(speed > SPEED_THRESHOLD){
            // mShakeCount の加算、SHAKE_COUNT を超えているかのチェック
            // 最後のシェイク時間から SHAKE_DURATION 経過しているかチェック
            if(++mShakeCount >= SHAKE_COUNT && now - mLastShake > SHAKE_DURATION){
                mLastShake = now;
                mShakeCount = 0;
                isShaked = true;
            }
            // SPEED_THRESHOLD を超える速度を検出した時刻をセット
            mLastAccel = now;
        }
        mLastTime = now;
        mLastX = x;
        mLastY = y;
        mLastZ = z;
        return isShaked;
    }

}
それぞれの閾値が適切かどうかはわからないです。
歩いていて手に持った端末が振られたことを検知していたから Xperia A にはちょうどいいかもしれません。
<ポイント>
1.機種によって加速度センサーの閾値が違う。
2.機種によって加速度センサーの搭載位置が違う。
→同じくらいの強さで振っても機種によって取得できる値がまったく違う。
3.端末持つ位置でも当然変わってくる。
加速度センサーを利用した自社Androidアプリ『鮭 – Shake』をリリース!! | R-Labs
これを調整するの難しそうです。
収集した実測値の標準偏差を算出して調整幅のMAX値とMIN値を決定しています。
加速度センサーを利用した自社Androidアプリ『鮭 – Shake』をリリース!! | R-Labs
これが一番良い対処法かもしれません。
このへんは調整だからコスパと重要度と精度を天秤にかけてあげなければいけなさそうです。

2013/12/15

[Android]DayDream機能でスクリーンセーバーを作る

Android 4.2 から DayDream といういわゆるスクリーンセーバー機能が追加されました。
DayDream | Android 4.2 APIs | Android Developers
Daydream: Interactive Screen Savers | Android Developers Blog
DayDream が利用可能な状況は充電中、ドック接続中(車など)のみ(※)です。
(※ユーザーの設定により両方もしくはいずれか)
ユーザーの設定でスクリーンセーバーが有効になっていれば動作可能で、
画面がスリープする代わりに起動設定されているスクリーンセーバーが動作します。
詳しい設定方法は以下のサイトがわかりやすいです。
Android 4.2の新機能、Daydream(スクリーンセーバー)の使い方と設定方法。 - Android(アンドロイド)おすすめアプリ・カスタムニュース|AndroidLover.Net

DayDream の実装には DreamService という Service を継承したクラスを使用します。
DreamService は Activity のように扱うことができます。
その他にも onAttachedToWindow(), onDreamingStarted(), onDreamingStopped(), onDetachedFromWindow() という DreamService 特有のライフサイクルがあります。
詳しくは DreamService | Android Developers を見てください。

簡単なサンプルを作ってみました。wada811/AndroidLibrary@wada811

設定付き DayDream の作り方

<service
    android:name="at.wada811.android.library.demos.daydream.DayDreamDemo"
    android:exported="false"
    android:label="@string/DayDreamDemo" >
    <intent-filter>
        <action android:name="android.service.dreams.DreamService" />
        <category android:name="android.intent.category.DEFAULT" />
    </intent-filter>
    <meta-data
        android:name="android.service.dream"
        android:resource="@xml/daydream" />
</service>
<activity
    android:name="at.wada811.android.library.demos.daydream.DayDreamSettingsActivity"
    android:label="@string/app_name" >
</activity>
<dream xmlns:android="http://schemas.android.com/apk/res/android"
    android:settingsActivity="at.wada811.android.library.demos/.daydream.DayDreamSettingsActivity" />
public class DayDreamDemo extends DreamService {

    @Override
    public void onAttachedToWindow(){
        super.onAttachedToWindow();
        LogUtils.d();
        setInteractive(PreferenceUtils.getBoolean(getApplicationContext(), getString(R.string.setInteractive), true));
        setFullscreen(PreferenceUtils.getBoolean(getApplicationContext(), getString(R.string.setFullscreen), true));
        setScreenBright(PreferenceUtils.getBoolean(getApplicationContext(), getString(R.string.setScreenBright), true));
        setContentView(R.layout.daydream);
    }

    @Override
    public void onDreamingStarted(){
        super.onDreamingStarted();
        LogUtils.d();
    }

    @Override
    public void onDreamingStopped(){
        super.onDreamingStopped();
        LogUtils.d();
    }

    @Override
    public void onDetachedFromWindow(){
        super.onDetachedFromWindow();
        LogUtils.d();
    }
}
public class DayDreamSettingsActivity extends FragmentActivity {

    final DayDreamSettingsActivity self = this;

    @Override
    protected void onCreate(Bundle savedInstanceState){
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_daydream_settings);

        Switch switchInteractive = (Switch)findViewById(R.id.setInteractive);
        switchInteractive.setChecked(PreferenceUtils.getBoolean(self, getString(R.string.setInteractive), true));
        switchInteractive.setOnCheckedChangeListener(new OnCheckedChangeListener(){
            @Override
            public void onCheckedChanged(CompoundButton buttonView, boolean isChecked){
                PreferenceUtils.putBoolean(self, getString(R.string.setInteractive), isChecked);
            }
        });
        Switch switchFullscreen = (Switch)findViewById(R.id.setFullscreen);
        switchFullscreen.setChecked(PreferenceUtils.getBoolean(self, getString(R.string.setFullscreen), true));
        switchFullscreen.setOnCheckedChangeListener(new OnCheckedChangeListener(){
            @Override
            public void onCheckedChanged(CompoundButton buttonView, boolean isChecked){
                PreferenceUtils.putBoolean(self, getString(R.string.setFullscreen), isChecked);
            }
        });
        Switch switchScreenBright = (Switch)findViewById(R.id.setScreenBright);
        switchScreenBright.setChecked(PreferenceUtils.getBoolean(self, getString(R.string.setScreenBright), true));
        switchScreenBright.setOnCheckedChangeListener(new OnCheckedChangeListener(){
            @Override
            public void onCheckedChanged(CompoundButton buttonView, boolean isChecked){
                PreferenceUtils.putBoolean(self, getString(R.string.setScreenBright), isChecked);
            }
        });
    }
}
以上!実装はすごく簡単です。
DreamService#setScreenBright(boolean) | Android Developers の効果がいまいちわかりませんでした。
DreamService は WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED と
WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD が設定されているので
KEYGUARD_LAYER にいるっぽいです。
参考
表示レイヤーについて - baroqueworksdevの日記
Y.A.M の 雑記帳: Android FLAG_DISMISS_KEYGUARD と FLAG_SHOW_WHEN_LOCKED を使う
Toast や DreamService から起動した Activity などは KEYGUARD_LAYER より下に表示されるので
見ることができません。
また、DreamService はあくまで「 Activity のように」、なので Fragment を扱うことができません。
面倒なので普通の Activity を使いたい場合は以下のように起動させてあげると良いです。

DayDream から Activity を起動する

<service
    android:name="at.wada811.android.library.demos.daydream.DayDreamLauncher"
    android:exported="false"
    android:label="@string/DayDreamLauncher" >
    <intent-filter>
        <action android:name="android.service.dreams.DreamService" />
        <category android:name="android.intent.category.DEFAULT" />
    </intent-filter>
</service>
<activity
    android:name="at.wada811.android.library.demos.daydream.DayDreamActivity"
    android:label="@string/app_name" >
</activity>
public class DayDreamLauncher extends DreamService {

    @Override
    public void onAttachedToWindow(){
        super.onAttachedToWindow();
        Intent intent = new Intent(this, DayDreamActivity.class);
        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        startActivity(intent);
    }
}
public class DayDreamActivity extends FragmentActivity {

    final DayDreamActivity self = this;

    @Override
    protected void onCreate(Bundle savedInstanceState){
        getWindow().addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED | WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON | WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD);
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_daydream);
        LogUtils.d();

        Button button = (Button)findViewById(R.id.button);
        button.setOnClickListener(new OnClickListener(){
            @Override
            public void onClick(View v){
                AndroidUtils.showToast(self, R.string.touch);
            }
        });
    }
}
DreamService と同じようにフラグを設定してあげれば
Activity も DreamService と同じレイヤーで表示することが可能です。
参考
JB 4.2 Daydreamの制約を乗り越えようとしてみる。 #androidadvent2012 | Mobile Dev Blog (by @korodroid)
ぺろぺろ会でのDaydreamまとめ #JB42 - ReDo

利用状況がかなり限られていますが競合が少なそうな分野なのでインストールされやすそうですね。
Android4.2 以上の端末の普及とユーザーへのスクリーンセーバーが使えるという認知が広まればの話ですが…。

2013/12/14

[Android]端末起動時にサービスを起動する

常駐サービスを実装したい場合は端末起動時に自動的にサービスを起動しておきたいので
端末起動時のイベントを通知する Broadcast Intent を受信できるようにして、
そこからサービスを起動したいと思います。

参考
システムの起動時にサービスを実行する « Tech Booster
App Install Location | Android Developers
AndroidのBOOT_COMPLETEDの受信とテスト | 9ensanのLifeHack

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    android:installLocation="internalOnly"
    …>
android.intent.action.BOOT_COMPLETED は 外部SDが端末にマウントされる前にブロードキャストされるので
外部SDにインストールされている場合、Broadcast Intent を受け取ることができません。
また、アプリが外部SDにインストールされている場合、外部SDがアンマウントされると
Service が kill され、マウントしても再開されません。
Intent#ACTION_EXTERNAL_APPLICATIONS_AVAILABLE | Android Developers
Broadcast Intent を受け取って自分で再開する必要があります。

<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<receiver
    android:name=".BootReceiver"
    android:permission="android.permission.RECEIVE_BOOT_COMPLETED" >
    <intent-filter>
        <action android:name="android.intent.action.BOOT_COMPLETED" />
        <category android:name="android.intent.category.DEFAULT" />
    </intent-filter>
</receiver>
Manifest とreceiver にそれぞれ Permission が必要なようです。
public class BootReceiver extends BroadcastReceiver {

    @Override
    public void onReceive(Context context, Intent intent){
        if(Intent.ACTION_BOOT_COMPLETED.equals(intent.getAction())){
            context.startService(new Intent(context, BootService.class));
        }
    }

}
上記のように BroadcastReceiver で Service を開始します。

以下のコマンドを実行することで BOOT_COMPLETED のテストが可能です。
adb shell am broadcast -a android.intent.action.BOOT_COMPLETED

Android 4.0 以上からはアプリを外部SDに移すことができなくなったようで、
android:installLocation="preferExternal" を指定していても動きましたが
Xperia A が良い感じにしてくれているだけかもしれませんし、
Android 4.0 未満では動かなくなると思うので
android:installLocation="internalOnly" を指定しておいたほうが良いと思います。
最近の端末は内部容量が 24G とかあってもはやアプリのサイズを気にする必要がほとんど無いんですね。

2013/12/07

[Android]android.graphics.Matrix の set, pre, post メソッドの違い

画像を色々変換しようと思って Matrix クラスを使ってみたら、
Rotate, Scale, Skew, Translate のメソッドにそれぞれ set, pre, post メソッドがあって
どういうことだろうと思ったら以下のサイトを見て解決した。
android.graphics.Matrixの動作 - あおいろ日記
Matrix は行列だし、行列は Matrix だった。
行列といえば掛ける順番で結果が変わるのだから
pre, post 系メソッドは前から掛けるか、後ろから掛けるかの違いで、
set 系メソッドは行列の初期化ということだった。

これだけだとアレなので行う変換の順番によって結果が異なるという例を上げておこうと思う。

回転してから移動


動くかどうかは試してないけどコードで書くならこんな感じ?
Matrix matrix = new Matrix();
matrix.postRotate(90);
matrix.postTranslate(100, 0);

移動してから回転


動くかどうかは試してないけどコードで書くならこんな感じ?
Matrix matrix = new Matrix();
matrix.postRotate(90);
matrix.preTranslate(100, 0);
下のような感じでもいいのかもしれないけど、
回転してから移動と処理順を合わせて条件によって Translate の pre, post 切り替える方が賢いと思う。
Matrix matrix = new Matrix();
matrix.postTranslate(100, 0);
matrix.postRotate(90);

2013/11/29

ExecutorServiceでマルチスレッドで実行可能なタスクキューを使う

マルチスレッドシリーズその2。
その1はこちら→[Java]マルチスレッドでの排他処理を行うsynchronizedを理解する
前回と同じく理解するためにサンプルを Android で作ってみました。

参考
ExecutorService の使い方 - Java 入門(図や概念がわかりやすい!)
Java - ExecutorService の復習 - Qiita [キータ](動作の違いがわかりやすい!)
死刑執行中 ExecutorService 終了中 - 倭マン's BLOG
[Java]ExecutorServiceの正しい終了(shutdown)の仕方 | 大発見 | 大発見

サンプルコードは以下にあります。
ExecutorActivity.java | wada811/AndroidLibrary@wada811
AndroidLibrary ってなってるけどやっぱりコレは Android 関係ないです。

実行するタスク

名前と実行時間(sleep時間)を貰ってログを出力するだけのクラス。以後、共通で使います。
public class ExecutorRunnable implements Runnable {
    private String mName;
    private int mSeconds;

    public ExecutorRunnable(String name, int seconds) {
        mName = name;
        mSeconds = seconds;
    }

    @Override
    public void run(){
        LogUtils.d(mName + ": ThreadId: " + Thread.currentThread().getId());
        LogUtils.d(mName + ": start");
        try{
            TimeUnit.SECONDS.sleep(mSeconds);
        }catch(InterruptedException e){
            e.printStackTrace();
        }
        LogUtils.d(mName + ": end");
    }
}

Executors.newSingleThreadExecutor();

public void newSingleThreadExecutorTest(){
    LogUtils.d();
    ExecutorService executorService = Executors.newSingleThreadExecutor();
    executorService.submit(new ExecutorRunnable("A", 1));
    executorService.submit(new ExecutorRunnable("B", 1));
    executorService.submit(new ExecutorRunnable("C", 1));
    executorService.submit(new ExecutorRunnable("D", 1));
}
SingleThread なのでタスクをいくつ追加しても一つのスレッドで実行します。追加した順に実行されます。
実行中だったら待たされます。実行ログからもそのことがわかります。
11-27 23:50:49.697: [ExecutorActivity#newSingleThreadExecutorTest:108]
11-27 23:50:49.707: [ExecutorActivity$ExecutorRunnable#run:299]A: ThreadId: 27824
11-27 23:50:49.707: [ExecutorActivity$ExecutorRunnable#run:300]A: start
11-27 23:50:50.708: [ExecutorActivity$ExecutorRunnable#run:306]A: end
11-27 23:50:50.708: [ExecutorActivity$ExecutorRunnable#run:299]B: ThreadId: 27824
11-27 23:50:50.718: [ExecutorActivity$ExecutorRunnable#run:300]B: start
11-27 23:50:51.719: [ExecutorActivity$ExecutorRunnable#run:306]B: end
11-27 23:50:51.719: [ExecutorActivity$ExecutorRunnable#run:299]C: ThreadId: 27824
11-27 23:50:51.719: [ExecutorActivity$ExecutorRunnable#run:300]C: start
11-27 23:50:52.720: [ExecutorActivity$ExecutorRunnable#run:306]C: end
11-27 23:50:52.720: [ExecutorActivity$ExecutorRunnable#run:299]D: ThreadId: 27824
11-27 23:50:52.730: [ExecutorActivity$ExecutorRunnable#run:300]D: start
11-27 23:50:53.731: [ExecutorActivity$ExecutorRunnable#run:306]D: end

Executors.newFixedThreadPool(int);

public void newFixedThreadPoolTest(){
    LogUtils.d();
    ExecutorService executorService = Executors.newFixedThreadPool(2);
    executorService.submit(new ExecutorRunnable("A", 1));
    executorService.submit(new ExecutorRunnable("B", 1));
    executorService.submit(new ExecutorRunnable("C", 1));
    executorService.submit(new ExecutorRunnable("D", 1));
}
引数のスレッド数で実行します。今回は2スレッドで実行します。追加した順に実行されます。
実行ログからも A, B と実行され、 A が終わったら C、B が終わったら D と実行されていることがわかります。
11-27 23:56:04.974: [ExecutorActivity#newFixedThreadPoolTest:124]
11-27 23:56:04.984: [ExecutorActivity$ExecutorRunnable#run:299]A: ThreadId: 27825
11-27 23:56:04.994: [ExecutorActivity$ExecutorRunnable#run:300]A: start
11-27 23:56:05.024: [ExecutorActivity$ExecutorRunnable#run:299]B: ThreadId: 27826
11-27 23:56:05.034: [ExecutorActivity$ExecutorRunnable#run:300]B: start
11-27 23:56:05.995: [ExecutorActivity$ExecutorRunnable#run:306]A: end
11-27 23:56:05.995: [ExecutorActivity$ExecutorRunnable#run:299]C: ThreadId: 27825
11-27 23:56:05.995: [ExecutorActivity$ExecutorRunnable#run:300]C: start
11-27 23:56:06.035: [ExecutorActivity$ExecutorRunnable#run:306]B: end
11-27 23:56:06.035: [ExecutorActivity$ExecutorRunnable#run:299]D: ThreadId: 27826
11-27 23:56:06.045: [ExecutorActivity$ExecutorRunnable#run:300]D: start
11-27 23:56:07.006: [ExecutorActivity$ExecutorRunnable#run:306]C: end
11-27 23:56:07.046: [ExecutorActivity$ExecutorRunnable#run:306]D: end

Executors.newCachedThreadPool();

public void newCachedThreadPoolTest(){
    LogUtils.d();
    ExecutorService executorService = Executors.newCachedThreadPool();
    executorService.submit(new ExecutorRunnable("A", 1));
    executorService.submit(new ExecutorRunnable("B", 1));
    executorService.submit(new ExecutorRunnable("C", 1));
    executorService.submit(new ExecutorRunnable("D", 1));
}
必要な分だけ(※)スレッドを生成して実行します。
(※ 内部の処理を見ると Integer.MAX_VALUE とあるので限界は int の最大値?)
タスクが何個 submit されようと必要に応じてスレッドを生成して同時に実行していることがわかります。
11-28 00:00:09.593: [ExecutorActivity#newCachedThreadPoolTest:140]
11-28 00:00:09.603: [ExecutorActivity$ExecutorRunnable#run:299]A: ThreadId: 27829
11-28 00:00:09.623: [ExecutorActivity$ExecutorRunnable#run:300]A: start
11-28 00:00:09.623: [ExecutorActivity$ExecutorRunnable#run:299]B: ThreadId: 27830
11-28 00:00:09.633: [ExecutorActivity$ExecutorRunnable#run:300]B: start
11-28 00:00:09.643: [ExecutorActivity$ExecutorRunnable#run:299]C: ThreadId: 27831
11-28 00:00:09.643: [ExecutorActivity$ExecutorRunnable#run:300]C: start
11-28 00:00:09.653: [ExecutorActivity$ExecutorRunnable#run:299]D: ThreadId: 27832
11-28 00:00:09.653: [ExecutorActivity$ExecutorRunnable#run:300]D: start
11-28 00:00:10.614: [ExecutorActivity$ExecutorRunnable#run:306]A: end
11-28 00:00:10.634: [ExecutorActivity$ExecutorRunnable#run:306]B: end
11-28 00:00:10.634: [ExecutorActivity$ExecutorRunnable#run:306]C: end
11-28 00:00:10.654: [ExecutorActivity$ExecutorRunnable#run:306]D: end

Executors.newScheduledThreadPool(int);

public void newScheduledThreadPoolTest(){
    LogUtils.d();
    ScheduledExecutorService executorService = Executors.newScheduledThreadPool(2);
    executorService.schedule(new ExecutorRunnable("A", 1), 1, TimeUnit.SECONDS);
    executorService.schedule(new ExecutorRunnable("B", 1), 1, TimeUnit.SECONDS);
    executorService.schedule(new ExecutorRunnable("C", 1), 1, TimeUnit.SECONDS);
    executorService.schedule(new ExecutorRunnable("D", 1), 1, TimeUnit.SECONDS);
}
引数のスレッド数で実行します。今回は2スレッドで実行します。
実行タイミングを設定できますが同じタイミングを設定した場合は実行順が前後することがあります。
というか実行タイミング依存なので実行順は追加順に依存しないということだと思います。
設定のスレッド数に達したら待たされていることがわかります。
11-28 00:04:23.701: [ExecutorActivity#newScheduledThreadPoolTest:156]
11-28 00:04:24.712: [ExecutorActivity$ExecutorRunnable#run:299]A: ThreadId: 27833
11-28 00:04:24.722: [ExecutorActivity$ExecutorRunnable#run:299]B: ThreadId: 27834
11-28 00:04:24.722: [ExecutorActivity$ExecutorRunnable#run:300]A: start
11-28 00:04:24.722: [ExecutorActivity$ExecutorRunnable#run:300]B: start
11-28 00:04:25.733: [ExecutorActivity$ExecutorRunnable#run:306]B: end
11-28 00:04:25.733: [ExecutorActivity$ExecutorRunnable#run:306]A: end
11-28 00:04:25.743: [ExecutorActivity$ExecutorRunnable#run:299]D: ThreadId: 27834
11-28 00:04:25.753: [ExecutorActivity$ExecutorRunnable#run:299]C: ThreadId: 27833
11-28 00:04:25.753: [ExecutorActivity$ExecutorRunnable#run:300]C: start
11-28 00:04:25.753: [ExecutorActivity$ExecutorRunnable#run:300]D: start
11-28 00:04:26.754: [ExecutorActivity$ExecutorRunnable#run:306]D: end
11-28 00:04:26.754: [ExecutorActivity$ExecutorRunnable#run:306]C: end

Executors.newSingleThreadScheduledExecutor(); で ScheduledExecutorService#schedule(Runnable, long, TimeUnit) の実行タイミングが被らないパターン

public void newSingleThreadScheduledExecutorTest(){
    LogUtils.d();
    ScheduledExecutorService executorService = Executors.newSingleThreadScheduledExecutor();
    executorService.schedule(new ExecutorRunnable("A", 1), 8, TimeUnit.SECONDS);
    executorService.schedule(new ExecutorRunnable("B", 1), 4, TimeUnit.SECONDS);
    executorService.schedule(new ExecutorRunnable("C", 1), 0, TimeUnit.SECONDS);
    executorService.schedule(new ExecutorRunnable("D", 1), 12, TimeUnit.SECONDS);
}
SingleThread なのでタスクをいくつ追加しても一つのスレッドで実行します。
設定した実行タイミングの順で実行されていることがわかるようにしました。
11-28 00:10:40.248: [ExecutorActivity#newSingleThreadScheduledExecutorTest:172]
11-28 00:10:40.278: [ExecutorActivity$ExecutorRunnable#run:299]C: ThreadId: 27847
11-28 00:10:40.278: [ExecutorActivity$ExecutorRunnable#run:300]C: start
11-28 00:10:41.289: [ExecutorActivity$ExecutorRunnable#run:306]C: end
11-28 00:10:44.262: [ExecutorActivity$ExecutorRunnable#run:299]B: ThreadId: 27847
11-28 00:10:44.262: [ExecutorActivity$ExecutorRunnable#run:300]B: start
11-28 00:10:45.273: [ExecutorActivity$ExecutorRunnable#run:306]B: end
11-28 00:10:48.256: [ExecutorActivity$ExecutorRunnable#run:299]A: ThreadId: 27847
11-28 00:10:48.266: [ExecutorActivity$ExecutorRunnable#run:300]A: start
11-28 00:10:49.267: [ExecutorActivity$ExecutorRunnable#run:306]A: end
11-28 00:10:52.260: [ExecutorActivity$ExecutorRunnable#run:299]D: ThreadId: 27847
11-28 00:10:52.270: [ExecutorActivity$ExecutorRunnable#run:300]D: start
11-28 00:10:53.271: [ExecutorActivity$ExecutorRunnable#run:306]D: end

Executors.newSingleThreadScheduledExecutor(); で ScheduledExecutorService#schedule(Runnable, long, TimeUnit) の実行タイミングが被るパターン

public void newSingleThreadScheduledExecutorDuringExecutionTest(){
    LogUtils.d();
    ScheduledExecutorService executorService = Executors.newSingleThreadScheduledExecutor();
    executorService.schedule(new ExecutorRunnable("A", 1), 1, TimeUnit.SECONDS);
    executorService.schedule(new ExecutorRunnable("B", 1), 1, TimeUnit.SECONDS);
    executorService.schedule(new ExecutorRunnable("C", 1), 1, TimeUnit.SECONDS);
    executorService.schedule(new ExecutorRunnable("D", 1), 1, TimeUnit.SECONDS);
}
SingleThread なのでタスクをいくつ追加しても一つのスレッドで実行します。
実行タイミングがかぶったら待たされます。
11-28 00:14:52.637: [ExecutorActivity#newSingleThreadScheduledExecutorDuringExecutionTest:188]
11-28 00:14:53.648: [ExecutorActivity$ExecutorRunnable#run:299]A: ThreadId: 27848
11-28 00:14:53.648: [ExecutorActivity$ExecutorRunnable#run:300]A: start
11-28 00:14:54.659: [ExecutorActivity$ExecutorRunnable#run:306]A: end
11-28 00:14:54.669: [ExecutorActivity$ExecutorRunnable#run:299]B: ThreadId: 27848
11-28 00:14:54.669: [ExecutorActivity$ExecutorRunnable#run:300]B: start
11-28 00:14:55.680: [ExecutorActivity$ExecutorRunnable#run:306]B: end
11-28 00:14:55.690: [ExecutorActivity$ExecutorRunnable#run:299]C: ThreadId: 27848
11-28 00:14:55.690: [ExecutorActivity$ExecutorRunnable#run:300]C: start
11-28 00:14:56.701: [ExecutorActivity$ExecutorRunnable#run:306]C: end
11-28 00:14:56.701: [ExecutorActivity$ExecutorRunnable#run:299]D: ThreadId: 27848
11-28 00:14:56.711: [ExecutorActivity$ExecutorRunnable#run:300]D: start
11-28 00:14:57.712: [ExecutorActivity$ExecutorRunnable#run:306]D: end

Executors.newSingleThreadScheduledExecutor(); で ScheduledExecutorService#scheduleAtFixedRate(Runnable, long, long, TimeUnit) の実行タイミングを過ぎないパターン

public void newSingleThreadScheduledExecutorAtFixedRateTest(){
    LogUtils.d();
    ScheduledExecutorService executorService = Executors.newSingleThreadScheduledExecutor();
    executorService.scheduleAtFixedRate(new ExecutorRunnable("A", 1), 1, 2, TimeUnit.SECONDS);
    shutdown(executorService); // 8秒後に終了します
}
一つのスレッドで実行します。実行タイミングを設定できます。繰り返し実行でき、実行間隔を設定できます。
実行間隔は実行開始から次の実行開始までの時間です。
実行間隔 が 2 秒で、実行 1 秒なので終了してから 1 秒で実行されます。
11-28 00:19:44.952: [ExecutorActivity#newSingleThreadScheduledExecutorAtFixedRateTest:205]
11-28 00:19:45.963: [ExecutorActivity$ExecutorRunnable#run:299]A: ThreadId: 27850
11-28 00:19:45.963: [ExecutorActivity$ExecutorRunnable#run:300]A: start
11-28 00:19:46.974: [ExecutorActivity$ExecutorRunnable#run:306]A: end
11-28 00:19:47.965: [ExecutorActivity$ExecutorRunnable#run:299]A: ThreadId: 27850
11-28 00:19:47.965: [ExecutorActivity$ExecutorRunnable#run:300]A: start
11-28 00:19:48.966: [ExecutorActivity$ExecutorRunnable#run:306]A: end
11-28 00:19:49.967: [ExecutorActivity$ExecutorRunnable#run:299]A: ThreadId: 27850
11-28 00:19:49.967: [ExecutorActivity$ExecutorRunnable#run:300]A: start
11-28 00:19:50.968: [ExecutorActivity$ExecutorRunnable#run:306]A: end
11-28 00:19:51.969: [ExecutorActivity$ExecutorRunnable#run:299]A: ThreadId: 27850
11-28 00:19:51.969: [ExecutorActivity$ExecutorRunnable#run:300]A: start
11-28 00:19:52.970: [ExecutorActivity$1#run:273]shutdown
11-28 00:19:52.980: [ExecutorActivity$ExecutorRunnable#run:306]A: end

Executors.newSingleThreadScheduledExecutor(); で ScheduledExecutorService#scheduleAtFixedRate(Runnable, long, long, TimeUnit) の実行タイミングを過ぎるパターン

public void newSingleThreadScheduledExecutorAtFixedRateDuringExecutionTest(){
    LogUtils.d();
    ScheduledExecutorService executorService = Executors.newSingleThreadScheduledExecutor();
    executorService.scheduleAtFixedRate(new ExecutorRunnable("A", 3), 1, 2, TimeUnit.SECONDS);
    shutdown(executorService); // 8秒後に終了します
}
一つのスレッドで実行します。実行タイミングを設定できます。繰り返し実行でき、実行間隔を設定できます。
実行間隔は実行開始から次の実行開始までの時間です。
実行に 3 秒かかるのに実行間隔を 2 秒にした場合、次の実行開始タイミングは過ぎているのですぐに実行します。
11-28 00:23:59.431: [ExecutorActivity#newSingleThreadScheduledExecutorAtFixedRateDuringExecutionTest:221]
11-28 00:24:00.442: [ExecutorActivity$ExecutorRunnable#run:299]A: ThreadId: 27851
11-28 00:24:00.442: [ExecutorActivity$ExecutorRunnable#run:300]A: start
11-28 00:24:03.444: [ExecutorActivity$ExecutorRunnable#run:306]A: end
11-28 00:24:03.454: [ExecutorActivity$ExecutorRunnable#run:299]A: ThreadId: 27851
11-28 00:24:03.454: [ExecutorActivity$ExecutorRunnable#run:300]A: start
11-28 00:24:06.467: [ExecutorActivity$ExecutorRunnable#run:306]A: end
11-28 00:24:06.477: [ExecutorActivity$ExecutorRunnable#run:299]A: ThreadId: 27851
11-28 00:24:06.477: [ExecutorActivity$ExecutorRunnable#run:300]A: start
11-28 00:24:07.438: [ExecutorActivity$1#run:273]shutdown
11-28 00:24:08.449: [ExecutorActivity$1#run:276]shutdownNow
11-28 00:24:08.499: [ExecutorActivity$ExecutorRunnable#run:306]A: end

Executors.newSingleThreadScheduledExecutor(); で ScheduledExecutorService#scheduleWithFixedDelay(Runnable, long, long, TimeUnit) の実行タイミングを被らないパターン

public void newSingleThreadScheduledExecutorWithFixedDelayTest(){
    LogUtils.d();
    ScheduledExecutorService executorService = Executors.newSingleThreadScheduledExecutor();
    executorService.scheduleWithFixedDelay(new ExecutorRunnable("A", 1), 1, 2, TimeUnit.SECONDS);
    shutdown(executorService); // 8秒後に終了します
}
一つのスレッドで実行します。実行タイミングを設定できます。繰り返し実行でき、実行間隔を設定できます。
実行間隔は実行終了から次の実行開始までの時間です。
1 秒のタスクを実行終了してから 2 秒後に次の実行が開始されていることがわかります。
11-28 00:31:25.436: [ExecutorActivity#newSingleThreadScheduledExecutorWithFixedDelayTest:236]
11-28 00:31:26.447: [ExecutorActivity$ExecutorRunnable#run:299]A: ThreadId: 27852
11-28 00:31:26.447: [ExecutorActivity$ExecutorRunnable#run:300]A: start
11-28 00:31:27.458: [ExecutorActivity$ExecutorRunnable#run:306]A: end
11-28 00:31:29.470: [ExecutorActivity$ExecutorRunnable#run:299]A: ThreadId: 27852
11-28 00:31:29.470: [ExecutorActivity$ExecutorRunnable#run:300]A: start
11-28 00:31:30.481: [ExecutorActivity$ExecutorRunnable#run:306]A: end
11-28 00:31:32.483: [ExecutorActivity$ExecutorRunnable#run:299]A: ThreadId: 27852
11-28 00:31:32.483: [ExecutorActivity$ExecutorRunnable#run:300]A: start
11-28 00:31:33.443: [ExecutorActivity$1#run:273]shutdown
11-28 00:31:33.494: [ExecutorActivity$ExecutorRunnable#run:306]A: end

Executors.newSingleThreadScheduledExecutor(); で ScheduledExecutorService#scheduleWithFixedDelay(Runnable, long, long, TimeUnit) の実行タイミングが被るパターン

public void newSingleThreadScheduledExecutorWithFixedDelayDuringExecutionTest(){
    LogUtils.d();
    ScheduledExecutorService executorService = Executors.newSingleThreadScheduledExecutor();
    executorService.scheduleWithFixedDelay(new ExecutorRunnable("A", 3), 1, 2, TimeUnit.SECONDS);
    shutdown(executorService);
}
一つのスレッドで実行します。実行タイミングを設定できます。繰り返し実行でき、実行間隔を設定できます。
実行間隔は実行終了から次の実行開始までの時間なので実行タイミングが被ることがないです。
3 秒のタスクを実行終了してから 2 秒後に次の実行が開始されていることがわかります。
11-28 00:34:05.822: [ExecutorActivity#newSingleThreadScheduledExecutorWithFixedDelayDuringExecutionTest:252]
11-28 00:34:06.833: [ExecutorActivity$ExecutorRunnable#run:299]A: ThreadId: 27853
11-28 00:34:06.833: [ExecutorActivity$ExecutorRunnable#run:300]A: start
11-28 00:34:09.846: [ExecutorActivity$ExecutorRunnable#run:306]A: end
11-28 00:34:11.848: [ExecutorActivity$ExecutorRunnable#run:299]A: ThreadId: 27853
11-28 00:34:11.858: [ExecutorActivity$ExecutorRunnable#run:300]A: start
11-28 00:34:13.840: [ExecutorActivity$1#run:273]shutdown
11-28 00:34:14.851: [ExecutorActivity$1#run:276]shutdownNow
11-28 00:34:14.901: [ExecutorActivity$ExecutorRunnable#run:306]A: end

おまけ

上の ScheduledExecutorService#scheduleAtFixedRate と
ScheduledExecutorService#scheduleWithFixedDelay ではタスクを一つしか与えていないが、
これをそれぞれもう一つ initialDelay を1秒ずらして追加すると
どちらも実行終了を待たされて次の実行タイミングが来てすぐに実行するようになってしまいます。

Executors.newScheduledThreadPool(int); ではどうなる?

上の ScheduledExecutorService#scheduleAtFixedRate と
ScheduledExecutorService#scheduleWithFixedDelay で ExecutorService の生成を
Executors.newScheduledThreadPool(int); で行った場合は、
繰り返される同一のタスクは一つのスレッドで固定で、タスクの追加ごとにスレッドが分かれるようになります。
実行タイミングが過ぎたり、被ったりすると実行タイミングが来てすぐに実行するようになってしまいます。
ちゃんと等間隔で実行したかったら一つの ExecutorService に一つのタスクが良いかと思います。

ExecutorService の終了の仕方

try{
    LogUtils.d("shutdown");
    executorService.shutdown();
    if(!executorService.awaitTermination(1, TimeUnit.SECONDS)){
        LogUtils.d("shutdownNow");
        executorService.shutdownNow();
    }
}catch(InterruptedException e){
    e.printStackTrace();
    LogUtils.d("shutdownNow: " + e.getMessage());
    executorService.shutdownNow();
}
ExecutorService#shutdown で終了命令を出して新規タスクの追加の禁止して、
実行中のタスクが終了せずに ExecutorService#awaitTermination でタイムアウト時間を過ぎたら
ExecutorService#shutdownNow で実行中のタスクをキャンセルするといった流れです。
shutdownNow が呼ばれると ExecutorRunnable の sleep で InterruptedException が投げられますが
ExecutorService#shutdownNow が直接的にタスクの実行を強制終了するわけではないようです。

まとめ

長くなりましたが ExecutorService の使い方をまとめました。
ExecutorService はかなり強力なマルチスレッドの実行キューの便利クラスなのでぜひ使いこなしたいですね!

2013/11/28

[HomeBrew]gitのバージョンアップをするためにやったこと

Git の diff を美しく表示するために必要なたった 1 つの設定 #gitという記事を見て、
やってみたくなったので設定してみたら diff-highlight が見つからないと言われました。

git のバージョンも古かったので git 環境のアップデートを行おうと思いました。
git は HomeBrew で入れていたので、まずは HomeBrew のアップデートを行います。

brew doctor を実行すると
You may wish to `brew unlink` these brews:

    openssl
のように言われたのですがいらないのがいくつかあったので
brew remove openssl などのように色々削除しました。

もう一度 brew doctor を実行すると
You should `brew install` the missing dependencies:

    brew install readline
のように言われたので brew install readline を実行しました。

更に再び brew doctor を実行すると
Error: Homebrew doesn't know what compiler versions ship with your version of
Xcode. Please file an issue with the output of `brew --config`:
  https://github.com/mxcl/homebrew/issues

Thanks!
のように言われていたので Xcode と Command Line Tools をアップデートしました。

ダメ押しで brew doctor を実行しましたが状況は変わらず絶望しつつも
brew update を実行すると成功したので
brew upgrade で git を含め全てバージョンアップしました。
git version 1.8.4.3

ところで目的の diff-highlight はというとパスの通ってないところにいたので
以下のコマンドでシンボリックリンクを作成して実行できるようにしました。
ln -s /usr/local/opt/git/share/git-core/contrib/diff-highlight/diff-highlight /usr/local/bin
~/.gitconfig に以下を追加しました。
[pager]
 log = diff-highlight | less
 show = diff-highlight | less
 diff = diff-highlight | less
文字列単位の差分がある場合は差分にハイライトされたように背景がつきます。

2013/11/24

[Android]Property AnimationでViewの背景色を変更する

View の背景色をアニメーションしながら変更したい場合があって
調べてみたら Property Animation | Android Developers というのがあった。
そしてモロそのままの質問が Stack Overflow にあった。
Animate change of view background color in Android - Stack Overflow

背景色が黒から透明になって消えていくアニメーションをするサンプル。
さり気なく API Level 11 から。2.x 系をサポートするなら NineOldAndroids を使おう。
final Integer black = getResources().getColor(R.color.black);
final Integer transparent = getResources().getColor(R.color.transparent);
ValueAnimator colorAnimation = ValueAnimator.ofObject(new ArgbEvaluator(), black, transparent);
colorAnimation.addUpdateListener(new AnimatorUpdateListener(){
    @Override
    public void onAnimationUpdate(ValueAnimator animator){
        mView.setBackgroundColor((Integer)animator.getAnimatedValue());
    }
});
colorAnimation.setDuration(1000);
colorAnimation.setInterpolator(new LinearInterpolator());
colorAnimation.start();

指定するアニメーション補完クラスは以下がわかりやすい。
[Android] アニメーション補間クラスをグラフ化してみました - adakoda
式さえわかれば移植できそうだなー。これ↓っぽい?
Equations.as - tweener - A class for creating tweens in actionscript 2 and 3 - because there's infinity between 0 and 1. - Google Project Hosting
あとはこの手のアニメーション系の Web 版があったからそこから持ってきて実装というのも良さそう。

2013/11/23

[Android]機種依存でMediaRecorderで動画撮影に失敗する

来たぜ!Android の機種依存の闇に!

Android の機種依存の特徴

  • まともに組んでも何故か動かない
  • 動かない原因がわからない
  • 機種依存の実機がないと何が起きてるのかわからない
Xperia 系端末は適当に組んでも良い感じに動いてくれるけど
他の機種で動かないっていうのは、まぁあることだ。
まとも組めばいいのだけど Android API で対処できる範囲でまともに組んでも動かないやつがいて、
そういうのが本当の機種依存なわけだ。
そういうやつはたいていエラーログにもろくな情報がない。

MediaRecorder は機種依存でなくてもろくなエラーログを出力しなくて、
start failed とか stop failed とだけ言ってくるので失敗してるのはわかってるよ!と言い返したくなる。
MediaRecorder なんかは Java 層がただのラッパーだから仕方ないのかもしれないが直せなくて辛い。
Android API を眺めて設定項目を変えて色々試してみるしかなくて機種依存の実機がないと修正は困難になる。

愚痴はココらへんにして、Samsung Galaxy S2 SC-02C と HUAWEI M886 で起きたバグを確認したい。

Samsung Galaxy S2 SC-02C 動画撮影が開始できない機種依存バグ

まずはこいつを見てくれ。見事に MediaRecorder#start() で RuntimeException で落ちてるだろう?
しかもメッセージは start failed. と来た。Caused by もないとはいよいよもって死ぬがよい。
{
  "Exception": {
    "name": "java.lang.RuntimeException",
    "message": "start failed.",
    "ExceptionStacktrace": [
      "at [MediaRecorder#start:-2]",
      "at [aa#b:-1]",
      "at [VideoCameraActivity#f:-1]",
      "at [e#onClick:-1]",
      "at [View#performClick:3620]",
      "at [View$PerformClick#run:14292]",
      "at [Handler#handleCallback:605]",
      "at [Handler#dispatchMessage:92]",
      "at [Looper#loop:137]",
      "at [ActivityThread#main:4507]",
      "at [Method#invokeNative:-2]",
      "at [Method#invoke:511]",
      "at [ZygoteInit$MethodAndArgsCaller#run:980]",
      "at [ZygoteInit#main:747]",
      "at [NativeStart#main:-2]"
    ]
  }
}
この JSON は独自クラッシュレポートによって手に入れたものだ。こいつがあると色々便利だ。
[Android]独自クラッシュレポート送信機能を実装する レポート作成編 | DevAchieve
カメラパラメータをこいつで送信してわかったことがある。
DisplaySize(800x480)、PreviewSize(800x480) に対して VideoSize(720x480) で
CamcorderProfile は "videoFrameWidth: ": 1920, "videoFrameHeight: ": 1080 という設定になっていた。
こいつが怪しい。
試しに Xperia A の値を確認すると、
DispalySize(1184x720)、PreviewSize(1280x720) に対して VideoSize(1920x1080) で
CamcorderProfile は "videoFrameWidth: ": 1920, "videoFrameHeight: ": 1080 という設定になっていた。
VideoSize と CamcorderProfile の videoFrame(Width|Height) が一致していない。

とここまで書いて気がついたのだが、 VideoSize は MediaRecorder#setVideoSize で設定している。
MediaRecorder#setVideoSize(int width, int height) | Android Developers
071 プレビューが見えるように撮影ができるように修正 refs #6 #11 · c2b99d2 · gabu/gabubon2
ちょうど↑こんなかんじで PreviewSize を指定していたのだけど
VideoSize(720x480) って PreviewSize(800x480) の近似サイズなような気がする。
Galaxy S2 のサポートしているビデオサイズのリストは以下のとおり。
"video-size-values": "720x480,176x144,320x240,352x288,640x480,1280x720,1920x1080"

この線が濃厚なので調べてみると Stack Overflow にそれっぽい回答があった。
video - How to use getSupportedVideoSizes() on Android? - Stack Overflow
CamcorderProfile camcorderProfile = CamcorderProfile.get(cameraId, CamcorderProfile.QUALITY_HIGH);
mMediaRecorder.setVideoSize(camcorderProfile.videoFrameWidth, camcorderProfile.videoFrameHeight);
上記のように CamcorderProfile の videoFrame(Width|Height) を使えば確実みたい。
このへんの指定の情報が少なすぎて泣ける。
過去にきっと泣かされてきたエンジニアたち↓
ビデオアプリが難なワケ: hikariのブログ
071 動画撮影が行えない · Issue #6 · gabu/gabubon2
希望を信じた Androider を、わたしは泣かせたくない。ということで多分解決。
機種依存バグじゃなくて Android API の使い方を間違えた僕が悪い。

HUAWEI M886 動画撮影が開始できない機種依存バグ

さてもう一つ。こっちはかなり高い確率で本当に機種依存バグ。だって中華端末だもん。
以下はレポートの設定値の一部抜粋。
{
    "preview-size": "1280x720",
    "preview-size-values": "1280x720,800x480,768x432,720x480,640x480,576x432,480x320,384x288,352x288,320x240,240x160,176x144",
    "record-size": "",
    "videoFrameWidth: ": 1280,
    "videoFrameHeight: ": 720
}
DisplaySize(854x480), PreviewSize(1280x720) で VideoSize がなくて
なぜか "record-size" で、しかも空という状況。
Android の Camera.Parameters を確認すると以下の定義は見つかるが、
"record-size" という設定値のキーはない。
private static final String KEY_VIDEO_SIZE = "video-size";
レポートくれたユーザーには思わず俺が直せることを祈ってくれって返した!
さすがに設定キーが違うと設定メソッド使えないから上の修正で奇跡的に直ることを祈ってくれ!

このように動画撮影には色々ノウハウが必要だから気をつけるんだぞ!
DevCamera はそのへん頑張ってるから応援よろしくお願いします。

2013/11/18

[Android]加速度センサーのノイズ除去とSensorFragment

直線加速度センサーを使うことがあったので SensorFragment を作ってみました。
参考
Sensors Overview | Android Developers
SensorEvent | Android Developers
ノイズ除去にはローパスフィルタというものを使いました。
加速度センサの値のノイズ除去 - Javaな日々
加速度センサ と ローパスフィルタ (波形) | アンドロイドな日々
加速度センサ と ローパスフィルタ (周波数特性) | アンドロイドな日々
public class SensorFragment extends Fragment implements SensorEventListener {

    public static final String  TAG              = SensorFragment.class.getSimpleName();
    private static final String KEY_SENSOR_TYPES = "KEY_SENSOR_TYPES";
    private SensorCallback      mCallback;
    private SensorManager       mSensorManager;
    private float               x, y, z = 0;

    public static interface SensorCallbackProvider {
        public SensorCallback getSensorCallback();
    }

    public static interface SensorCallback {

        public void onLinearAccelerationSensorChanged(float x, float y, float z);

    }

    public static SensorFragment newInstance(ArrayList<Integer> sensorTypes){
        SensorFragment fragment = new SensorFragment();
        Bundle args = new Bundle();
        args.putIntegerArrayList(KEY_SENSOR_TYPES, sensorTypes);
        fragment.setArguments(args);
        return fragment;
    }

    @Override
    public void onAttach(Activity activity){
        super.onAttach(activity);

        if(activity instanceof SensorCallbackProvider == false){
            throw new ClassCastException(activity.getLocalClassName() + " must implements " + SensorCallbackProvider.class.getSimpleName());
        }
        SensorCallbackProvider picker = (SensorCallbackProvider)activity;
        mCallback = picker.getSensorCallback();
        mSensorManager = (SensorManager)activity.getSystemService(Context.SENSOR_SERVICE);
        registerListener();
    }

    @Override
    public void onDetach(){
        super.onDetach();
        unregisterListener();
    }

    private void registerListener(){
        if(mSensorManager != null){
            ArrayList<Integer> sensorTypes = getArguments().getIntegerArrayList(KEY_SENSOR_TYPES);
            for(int sensorType : sensorTypes){
                Sensor sensor = mSensorManager.getDefaultSensor(sensorType);
                mSensorManager.registerListener(this, sensor, SensorManager.SENSOR_DELAY_NORMAL);
            }
        }
    }

    public void registerListener(Integer sensorType){
        ArrayList<Integer> sensorTypes = getArguments().getIntegerArrayList(KEY_SENSOR_TYPES);
        sensorTypes.add(sensorType);
        getArguments().putIntegerArrayList(KEY_SENSOR_TYPES, sensorTypes);
        if(mSensorManager != null){
            Sensor sensor = mSensorManager.getDefaultSensor(sensorType);
            mSensorManager.registerListener(this, sensor, SensorManager.SENSOR_DELAY_NORMAL);
        }
    }

    private void unregisterListener(){
        if(mSensorManager != null){
            ArrayList<Integer> sensorTypes = getArguments().getIntegerArrayList(KEY_SENSOR_TYPES);
            for(int sensorType : sensorTypes){
                Sensor sensor = mSensorManager.getDefaultSensor(sensorType);
                mSensorManager.unregisterListener(this, sensor);
            }
        }
    }

    public void unregisterListener(Integer sensorType){
        ArrayList<Integer> sensorTypes = getArguments().getIntegerArrayList(KEY_SENSOR_TYPES);
        sensorTypes.remove(sensorType);
        getArguments().putIntegerArrayList(KEY_SENSOR_TYPES, sensorTypes);
        if(mSensorManager != null){
            Sensor sensor = mSensorManager.getDefaultSensor(sensorType);
            mSensorManager.unregisterListener(this, sensor);
        }
    }

    @Override
    public void onSensorChanged(SensorEvent event){
        if(event.sensor.getType() == Sensor.TYPE_LINEAR_ACCELERATION){
            x = (float)(x * 0.9 + event.values[0] * 0.1);
            y = (float)(y * 0.9 + event.values[1] * 0.1);
            z = (float)(z * 0.9 + event.values[2] * 0.1);
            mCallback.onLinearAccelerationSensorChanged(x, y, z);
        }
    }

    @Override
    public void onAccuracyChanged(Sensor sensor, int accuracy){
        // do nothing
    }

}
まだ他のセンサー系を使っていないのでコレで大丈夫なのか気になります。

2013/11/17

[Android]R.idの追加方法

通常レイアウトで R.id を追加するのだけど
動的に生成した View に id を設定したいときは
R.id も動的に生成しなければいけません。
方法は以下の2つです。

リソースから生成する

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <item name="INTERNAL_EMPTY_ID" type="id"/>
    <item name="INTERNAL_PROGRESS_CONTAINER_ID" type="id"/>
    <item name="INTERNAL_LIST_CONTAINER_ID" type="id"/>
</resources>

プログラムから生成する

int viewId = View.generateViewId();
ただし、 API Level 17 (Android4.2) からです。

コレで、以下の ExpandableListFragment の内部の id を固定値から動的に生成した id に変更しました。
ExpandableListFragment - OrangeSignal ブログ
動的に生成しないとライブラリプロジェクトとかで id 衝突してバグる可能性ありますからね。

2013/11/15

[Java]マルチスレッドでの排他処理を行うsynchronizedを理解する

今までマルチスレッドプログラミングというものをしてこなかったので
排他処理とか synchronized とかなにそれオイシイの状態だったので
理解するために簡単なサンプルを Android で作ってみました。

参考
Javaスレッドメモ(Hishidama's Java thread Memo)

サンプルコードは以下にあります。
SynchronizedActivity.java | wada811/AndroidLibrary@wada811
AndroidLibrary ってなってるけどコレは Android 関係ないです。

メソッド修飾の synchronized

クラスメソッド

/**
 * クラスメソッドを synchronized にした場合のテスト実行メソッド
 */
public void syncClassMethodExecute(){
    PreferenceUtils.putString(this, SyncClassMethodExecuter.KEY, null);
    new Thread(new SyncClassMethodExecutorRunnable("A")).start();
    new Thread(new SyncClassMethodExecutorRunnable("B")).start();
    new Thread(new SyncClassMethodExecutorRunnable("C")).start();
}

public class SyncClassMethodExecutorRunnable implements Runnable {
    String threadName;

    public SyncClassMethodExecutorRunnable(String threadName) {
        this.threadName = threadName;
    }

    @Override
    public void run(){
        SyncClassMethodExecuter.execute(self, threadName);
    }
}

/**
 * クラスメソッドを synchronized にした場合のテストクラス
 */
public static class SyncClassMethodExecuter {

    public static final String KEY = SyncClassMethodExecuter.class.getSimpleName();

    public static final synchronized void execute(Context context, String threadName){
        String lastThreadName = PreferenceUtils.getString(context, KEY, null);
        if(lastThreadName != null){
            LogUtils.d(threadName + ": do nothing. " + lastThreadName + " is executed.");
        }else{
            try{
                TimeUnit.SECONDS.sleep(1);
            }catch(InterruptedException e){
                e.printStackTrace();
            }
            LogUtils.d(threadName + ": execute");
            PreferenceUtils.putString(context, KEY, threadName);
            try{
                TimeUnit.SECONDS.sleep(1);
            }catch(InterruptedException e){
                e.printStackTrace();
            }
        }
    }
}
synchronized にしてるから1秒待って A が実行されたら1秒後に B, C と実行されていて
ブロックされているのが実行ログからわかります。
11-14 22:57:39.801: [SynchronizedActivity#syncClassMethodExecute:115]
11-14 22:57:40.872: [SynchronizedActivity$SyncClassMethodExecutor#execute:152]A: execute
11-14 22:57:41.933: [SynchronizedActivity$SyncClassMethodExecutor#execute:145]B: do nothing. A is executed.
11-14 22:57:41.943: [SynchronizedActivity$SyncClassMethodExecutor#execute:145]C: do nothing. A is executed.
synchronized をとれば A, B, C 全部が execute を通ることも確認できます。

インスタンスメソッド

/**
 * インスタンスメソッドを synchronized にした場合のテスト実行メソッド
 */
public void syncThisInstanceMethodExecute(){
    LogUtils.d();
    SyncThisInstanceMethodExecutor executor = new SyncThisInstanceMethodExecutor();
    new Thread(new SyncThisInstanceMethodExecutorRunnable(executor, "A")).start();
    new Thread(new SyncThisInstanceMethodExecutorRunnable(executor, "B")).start();
    new Thread(new SyncThisInstanceMethodExecutorRunnable(executor, "C")).start();
}

public class SyncThisInstanceMethodExecutorRunnable implements Runnable {
    private SyncThisInstanceMethodExecutor mExecutor;
    private String                         mThreadNmae;

    public SyncThisInstanceMethodExecutorRunnable(SyncThisInstanceMethodExecutor executor, String threadNmae) {
        mExecutor = executor;
        mThreadNmae = threadNmae;
    }

    @Override
    public void run(){
        mExecutor.execute(mThreadNmae);
    }

}

/**
 * インスタンスメソッドを synchronized にした場合のテストクラス
 */
public class SyncThisInstanceMethodExecutor {
    private int mCount = 0;

    public synchronized void execute(String threadName){
        if(mCount == N){
            LogUtils.d(threadName + ": do nothing");
        }else{
            for(int i = 0; i < N; i++){
                LogUtils.d(threadName + ": " + ++mCount);
            }
            try{
                TimeUnit.SECONDS.sleep(1);
            }catch(InterruptedException e){
                e.printStackTrace();
            }
        }
    }
}
インスタンスメソッドの synchronized はメソッドボディを synchronized(this){ 〜 } で囲ったのと
同じ動きをするらしいので SyncThisInstanceMethodExecutor という名前にしました。
参考: Javaスレッドメモ(Hishidama's Java thread Memo)
コレもバッチリ A の実行が終わるのを待たされているのが実行ログから分かります。
[SynchronizedActivity#syncThisInstanceMethodExecute:219]
[SynchronizedActivity$SyncThisInstanceMethodExecutor#execute:253]A: 1
[SynchronizedActivity$SyncThisInstanceMethodExecutor#execute:253]A: 2
[SynchronizedActivity$SyncThisInstanceMethodExecutor#execute:253]A: 3
[SynchronizedActivity$SyncThisInstanceMethodExecutor#execute:253]A: 4
[SynchronizedActivity$SyncThisInstanceMethodExecutor#execute:253]A: 5
[SynchronizedActivity$SyncThisInstanceMethodExecutor#execute:253]A: 6
[SynchronizedActivity$SyncThisInstanceMethodExecutor#execute:253]A: 7
[SynchronizedActivity$SyncThisInstanceMethodExecutor#execute:253]A: 8
[SynchronizedActivity$SyncThisInstanceMethodExecutor#execute:253]A: 9
[SynchronizedActivity$SyncThisInstanceMethodExecutor#execute:253]A: 10
[SynchronizedActivity$SyncThisInstanceMethodExecutor#execute:250]B: do nothing
[SynchronizedActivity$SyncThisInstanceMethodExecutor#execute:250]C: do nothing

ロックオブジェクトを synchronized

/**
 * インスタンスメソッドでロックオブジェクトを synchronized した場合のテスト実行メソッド
 */
public void syncLockInstanceMethodExecute(){
    LogUtils.d();
    SyncLockInstanceMethodExecutor executor = new SyncLockInstanceMethodExecutor();
    new Thread(new SyncLockInstanceMethodExecutorRunnable(executor, "A")).start();
    new Thread(new SyncLockInstanceMethodExecutorRunnable(executor, "B")).start();
    new Thread(new SyncLockInstanceMethodExecutorRunnable(executor, "C")).start();
}

public class SyncLockInstanceMethodExecutorRunnable implements Runnable {
    private SyncLockInstanceMethodExecutor mExecutor;
    private String                         mThreadNmae;

    public SyncLockInstanceMethodExecutorRunnable(SyncLockInstanceMethodExecutor executor, String threadNmae) {
        mExecutor = executor;
        mThreadNmae = threadNmae;
    }

    @Override
    public void run(){
        mExecutor.execute(mThreadNmae);
    }

}

/**
 * インスタンスメソッドでロックオブジェクトを synchronized した場合のテストクラス
 */
public class SyncLockInstanceMethodExecutor {
    private final Object lock   = new Object();
    private int          mCount = 0;

    public void execute(String threadName){
        synchronized(lock){
            if(mCount == N){
                LogUtils.d(threadName + ": do nothing");
            }else{
                for(int i = 0; i < N; i++){
                    LogUtils.d(threadName + ": " + ++mCount);
                }
                try{
                    TimeUnit.SECONDS.sleep(1);
                }catch(InterruptedException e){
                    e.printStackTrace();
                }
            }
        }
    }
}
SyncLockInstanceMethodExecutor は SyncThisInstanceMethodExecutor とほとんど同じだけど
メソッド修飾の synchronized じゃなくて synchronized 文になってオブジェクトをロックしています。
SyncThis と同じなので実行ログは省略します。
StackOverflow でこっちの方法を推奨している回答がありました。
synchronization - How to synchronize or lock upon variables in Java? - Stack Overflow
blog.pasberth.com: Javaのsynchronizedってなにをブロックしているの?のコメントでもあるように
synchronized(this) は同一インスタンスでしか排他処理ができないので
SyncLockInstanceMethodExecutor を複数インスタンス生成されたら排他制御できなくなります。
なので以下のようにグローバルなオブジェクトを synchronized すると別インスタンスでも排他制御できます。
public static final Object LOCK = new Object();

public void syncGrobalLockExecute(){
    LogUtils.d();
    new Thread(new SyncGrobalLockExecutorRunnable(new SyncGrobalLockExecutor(), "A")).start();
    new Thread(new SyncGrobalLockExecutorRunnable(new SyncGrobalLockExecutor(), "B")).start();
    new Thread(new SyncGrobalLockExecutorRunnable(new SyncGrobalLockExecutor(), "C")).start();
}

public class SyncGrobalLockExecutorRunnable implements Runnable {
    private SyncGrobalLockExecutor mExecutor;
    private String                 mThreadNmae;

    public SyncGrobalLockExecutorRunnable(SyncGrobalLockExecutor executor, String threadNmae) {
        mExecutor = executor;
        mThreadNmae = threadNmae;
    }

    @Override
    public void run(){
        mExecutor.execute1(mThreadNmae);
        mExecutor.execute2(mThreadNmae);
        mExecutor.execute3(mThreadNmae);
    }

}

public class SyncGrobalLockExecutor {
    private int mCount = 0;

    public void execute1(String threadName){
        synchronized(LOCK){
            for(int i = 0; i < N; i++){
                LogUtils.d(threadName + "[1]: " + ++mCount);
            }
        }
    }

    public void execute2(String threadName){
        synchronized(LOCK){
            for(int i = 0; i < N; i++){
                LogUtils.d(threadName + "[2]: " + ++mCount);
            }
        }
    }

    public void execute3(String threadName){
        synchronized(LOCK){
            for(int i = 0; i < N; i++){
                LogUtils.d(threadName + "[3]: " + ++mCount);
            }
        }
    }
}
同じ LOCK に対する排他制御なので execute1, execute2, execute3 同士も排他制御されます。
インスタンスごとに呼ぶメソッドを変えてあげればわかりやすいです。
SyncGrobalLockExecutor はサンプルに入ってないので適当に書き換えて試してみて下さい。

synchronized を使いこなせればプログラミングの幅が広がりそうですね。
長くなったのでこのへんで。

2013/11/14

[Android]FragmentからActivityへのイベントの通知方法

Fragment から Activity へのイベントの通知方法のベストな方法を紹介します。
@vvakameさんの CallbackPicker のことなんですが、
文書化されていないようなので記事を書いておきます。
CallbackPicker よりも CallbackProvider の方が
なんとなくしっくり来たので勝手に名前を変えました。

とりあえず以下の方法を理解していることが前提です。
Y.A.M の 雑記帳: Fragment から Activity にコールバックする方法
リンク先の内容を簡単に説明すると、
onAttach で activity がリスナーを実装しているかチェックしてコールバックメソッドを呼ぼう!ってことです。

今回の方法はその方法を1段深くしたような形になっています。
public class MainActivity extends FragmentActivity implements MainFragmnetCallbackProvider {

    @Override
    public void onCreate(Bundle savedInstanceState) {  
        super.onCreate(savedInstanceState);

        MainFragment fragment = MainFragment.newInstance();
        getFragmentManager().beginTransaction().add(android.R.id.content, fragment, MainFragment.TAG).commit();
    }

    @Override
    public MainFragmnetCallback getMainFragmnetCallback(){
        return new MainFragmnetCallback(){
            @Override
            public void onAttach(MainFragmnet mainFragmnet){

            }
        }
    }
}
public class MainFragment extends Fragment {

    public static final String   TAG = MainFragment.class.getSimpleName();
    private MainFragmentCallback mCallback;

    public static interface MainFragmentCallbackProvider {

        public MainFragmentCallback getMainCallback();

    }

    public static interface MainFragmentCallback {

        public void onAttach(MainFragment mainFragment);

    }

    public static MainFragment newInstance(){
        MainFragment fragment = new MainFragment();
        Bundle args = new Bundle();
        fragment.setArguments(args);
        return fragment;
    }

    @Override
    public void onAttach(Activity activity){
        super.onAttach(activity);

        if(activity instanceof MainFragmentCallbackProvider == false){
            throw new ClassCastException(activity.getLocalClassName() + " must implements " + MainFragmentCallbackProvider.class.getSimpleName());
        }
        MainFragmentCallbackProvider provider = (MainFragmentCallbackProvider)activity;
        mCallback = provider.getMainCallback();
        mCallback.onAttach(this);
    }

}
MainFragment に2つの interface を用意して Activity で MainFragmnetCallbackProvider を実装します。
MainFragmnetCallbackProvider は MainFragmentCallback を返す getMainCallback() のみを提供します。
getMainCallback() で MainFragmentCallback を 匿名インナークラスとして書いてあげれば
MainFragment に関するイベントはその匿名インナークラス内に集約されます(←重要!※)。
あとは MainFragment で MainFragmentCallback のメソッド宣言に好きなコールバックを書いてあげるだけ!

※ MainFragmnetCallbackProvider を使うことでコールバックメソッドのスコープが小さくなるから便利ですよ!
MainFragment のちょっとしたリスナーだけだったらいいかもしれないけど
MainActivity で 他にも Fragment を扱ったりするとコールバックメソッドが入り乱れて見難くなります。
7, 8個くらい Fragment を Activity にくっつけたときはコレじゃなかったら可読性低くて大変だったかも。

interface の名前的に自由にコールバックメソッドを追加しやすかったのも良いと思いました。
Fragment 同士の連携も Activity に一旦コールバックして
Activity から Fragment のメソッド実行すれば良いので
お互いの実装に関わりを持たずにコールバックを投げておけば良いというのも開発しやすいポイントです。

この MainFragment は Fragment クラスの拡張を作る際のテンプレにしておきたいので記事を書きました。