Serviceからレイアウトをinflateできない罠

環境

AndroidStudio 2.2.3
compileSdkVersion 24
buildToolsVersion "23.0.3"

現象

Serviceにこんな感じのコード書いたら描画されなかった。(View自体は生成されてる)

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        super.onStartCommand(intent, flags, startId);
        getWindowManager().addView(LayoutInflater.from(this).inflate(R.layout.sample, null), params);
        return START_STICKY;
    }

原因

よくわからんけどリソースに書かれてた

xmlns:app="http://schemas.android.com/apk/res-auto"

を消したら描画されるようになった

だめなリソース

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:orientation="vertical" android:layout_width="match_parent"
    android:layout_height="match_parent">

    <ImageView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        app:srcCompat="@mipmap/ic_launcher"
        android:id="@+id/floatingButton" />
</LinearLayout>

大丈夫なリソース

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical" android:layout_width="match_parent"
    android:layout_height="match_parent">

    <ImageView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:src="@mipmap/ic_launcher"
        android:id="@+id/floatingButton" />
</LinearLayout>

iPhoneでテルミンを作りたかった

要件

  • 音が出ること(単音)
  • 手をかざして上下に動かすと音程が変化すること

AudioUnitで音出した

近さの取得

手をかざして上下に動かして値を操作したかったので、近さの値が欲しかった。

近接センサは2値なのでダメ

この辺ざっと見たけどデバイスの近接センサはboolean値でしか取れないっぽい

カメラ + 顔認識

とりあえず、近づけたり遠ざけたりすると値が変わる何かが取れればよかったので、カメラ+顔認識で試してみた
どうやら標準で顔認識できるっぽかったのでこの辺参考に顔の幅をとってみた

カメラはこちらを参考にほぼコピペ

顔認識雑感
  • カメラからのフレームをCIDetectorに渡すと顔認識してくれるんだけど、遠慮なしにバンバンぶち込むとメモリ足りないよエラーになって死んじゃうので、雑なフラグ作ってdetectorの処理が終わるまでは次のフレームを受け取らないようにする必要があった。
  • 自分の顔は顔だと認識してくれなかった。
  • 自分のどころか、いろんな顔をカメラにかざしてみたけど、一番反応が良かったのはこの画像だった

Android r22 + ant で java.lang.VerifyError からの java.nio.BufferOverflowException

java.lang.VerifyErrorが出ました

気がついたらJenkins管理下のAndroidプロジェクトのビルドが全滅してました。
antでビルドしてるところで

java.lang.VerifyError

なるものが吐かれていました。
javadocを覗いてみると

クラスファイルが適切な形式でも、ある種の内部矛盾またはセキュリティ上の問題があることを「ベリファイア (verifier)」が検出した場合にスローされます

という割とよくわからない解説がなされており、
android開発してるとjava.lang.VerifyError起きるよね - 明日の鍵
とか
r22 tools cause java.lang.VerifyError when building from ant script
とかを参考にしつついろいろやるけど解決せず。

そういや最近AndroidSDKをr22にアップデートしたなと思って
http://developer.android.com/tools/sdk/tools-notes.html
ここからr22の変更点を見てみると

Changed the structure of the SDK by adding a new build tool SDK Component, which is based on the existing platform-tools component. This change decouples the build tools versions from the IDE versions, allowing updates to the tools without requiring an IDE update.

とありました。
なんだかよくわかりませんが、なんかbuild toolってのが増えたらしい?
というわけでSDKManagerを開いてみると、たしかにAndroid SDK Build-toolsっていうのが増えていたのでインストールしました。

VerifyErrorはきえました

java.nio.BufferOverflowExceptionになりました。
依然ビルドが通りません。んなばかな!

ダメもとでissuesを調べてみると
java.nio.BufferOverflowException When Building with 19 Build Tools
にあっちゃいました。

Please use SDK Manager to get different versions of the build tools.

だそうです。
ちょっとなに言ってるのかよくわかりませんでしたが、
とりあえずSDKManagerからBuild-toolsのr19を削除してr18を残すようにしたら
めでたくビルドが通りました。

まとめ

  1. AndroidSDK r22はBuild-toolsも入れなきゃだめ
  2. Build-tools r19はなんかバグってるらしいから、今はまだr18で行くのがいいらしい
  3. AndroidSDKを無闇にアップデートしてはいけない

AndroidのHttpURLConnectionのデフォルトのUserAgent

HttpURLConnectionで実装していた部分をHttpClientに変えてみたら、UserAgentが変わってしまいました。
そこでHttpClientのリクエストにHttpURLConnectionのデフォルトのUserAgentをセットしてやろう思ったのですが、それがなかなか見つからなかったのでメモっときます。

HttpURLConnectionでUserAgentをセットせずに通信すると、

Dalvik/1.4.0 (Linux; U; Android 2.3.6; Nexus S Build/GRK39F)

な文字列が入ってリクエストが送られます。

WebViewのデフォルトのUserAgentは、WebSettings#getUserAgentString()で取得できて

Mozilla/5.0 (Linux; U; Android 2.3.6; zh-tw; Nexus S Build/GRK39F) 

みたいなのがもらえるんですが、似てるけどちょっと違う。
ちなみにWebSettingsでは

    private synchronized String getCurrentUserAgent() {
        Locale locale;
        synchronized(sLockForLocaleSettings) {
            locale = sLocale;
        }
        StringBuffer buffer = new StringBuffer();
        // Add version
        final String version = Build.VERSION.RELEASE;
        if (version.length() > 0) {
            buffer.append(version);
        } else {
            // default to "1.0"
            buffer.append("1.0");
        }  
        buffer.append("; ");
        final String language = locale.getLanguage();
        if (language != null) {
            buffer.append(language.toLowerCase());
            final String country = locale.getCountry();
            if (country != null) {
                buffer.append("-");
                buffer.append(country.toLowerCase());
            }
        } else {
            // default to "en"
            buffer.append("en");
        }
        // add the model for the release build
        if ("REL".equals(Build.VERSION.CODENAME)) {
            final String model = Build.MODEL;
            if (model.length() > 0) {
                buffer.append("; ");
                buffer.append(model);
            }
        }
        final String id = Build.ID;
        if (id.length() > 0) {
            buffer.append(" Build/");
            buffer.append(id);
        }
        final String base = mContext.getResources().getText(
                com.android.internal.R.string.web_user_agent).toString();
        return String.format(base, buffer);
    }

な感じで頑張って文字列を作ってるので、HttpURLConnectionのUserAgentもどっかで作ってるんだろうと思ってソースコードを追っていたのですが、どうもAndroidの方では作ってないようです。

そこでSystemの情報をあさっていたところ、

System.getProperty("http.agent");

の値が

Dalvik/1.4.0 (Linux; U; Android 2.3.6; Nexus S Build/GRK39F)

と出ました。
何かそれっぽいですね。

URLクラスのURLStreamHandlerがこの辺の値を取りに行ってるのでしょうか。
時間があればもうちょっと追ってみたいところですが、とりあえずそれっぽいの取れたんでよしとします。

Android JUnit + guava で java.lang.NoClassDefFoundError

Android JUnitのテストプロジェクトからguava-libraryを使ってるプロジェクトのguavaのAPIを使ってる部分をテストしようとしたら

java.lang.NoClassDefFoundError 

と出ました。

Android JUnitではなく、通常のJUnitで実行すればちゃんとクラスを見つけてくれます。

いろいろ調べた挙句、このサイトがヒントになりました。
http://dtmilano.blogspot.jp/2009/12/android-testing-external-libraries.html

ターゲットプロジェクトのビルドパスの設定から、guavaのjarのエクスポートにチェックを入れて、eclipseでクリーンして実行でツモでした。

Android4.0でdo-whileが実行されない場合がある

Android4.0端末でのみ変数の値がおかしくなるバグを追いかけていて気がつきました。
端的に言うと、

Android4.0端末で、ネストされたループ内のdo-while文が実行されない場合がある

「実行されない場合がある」なんてボンヤリした表現になってますが、原因はちょっとよくわかりません。
一応条件っぽいのを挙げておきます。

  • Android4.0端末である
  • for文 -> for文 -> do-while文 の入れ子になっている
  • 二番目のfor文内でメソッド呼び出しをしていない
  • do-while文内でメソッド呼び出しをしていない

次のコードで、実機、シミュレータ共に確認できました。

public class LoopTestActivity extends Activity {
   @Override
   public void onCreate(final Bundle savedInstanceState) {
      super.onCreate(savedInstanceState);
      final LinearLayout layout = new LinearLayout(this);
      setContentView(layout);

      final Button button = new Button(this);
      button.setText("ボタンを押したらループが走るよ");
      button.setOnClickListener(new OnClickListener(){
         @Override
         public void onClick(final View view) {
            loop();
         }
      });
      layout.addView(button);
   }

   private void loop(){
      for(int j = 0; j < 100; j++){   // 下のfor文をアホみたいに100回繰り返す。
        int counter = 0;   // 次のfor文の外でsysoutを呼びたいので、ここで宣言しておく。

        for(int i = 0; i < 1; i++){   // ここでの繰り返しは1回でも再現する。
          counter = 0;
          do {
            counter++;   // ここが実行されない
          } while (counter < 5);
        }

        if(counter != 0) System.out.println("counter=" + counter);   // counter=5 のはず。
        if(counter == 0){
          System.err.println("counter=" + counter);
          break;   // エラーが出たらすぐわかるように break する。
        }
      }
   }
}

ボタンをぽちぽちしながらlogcatを見てると、結構な頻度で counter=0 が出力されます。
一番外側のfor文(この例では100回回してるやつ)は、私の環境では大体8回くらいからでもcounter=0の出力を得ました。
内側のfor文は1回回すだけでも再現します。

do-whileかその親のスコープ内でなんらかのメソッドを呼んでいると、正常に処理が進みます。
上記コードのsysoutがスコープ外にあるのはそのためです。
次のように、何もしないメソッドを呼んでも処理が走るようになりました。

   private void doNothing(){
   }
for(int i = 0; i < 1; i++){
   counter = 0;
//   doNothing();   // ここでもいい
   do {
      doNothing();   // メソッドを呼び出せば処理される
      counter++;
   } while (counter < 5);
}

また、do-while文をfor文やwhile文に変えても再現しなくなります。
再現するまんまのapkをデバッグモードで実行しても再現しなくなりました。
なんだかよくわかりませんが、JITコンパイラの最適化方式が4.0で変わったんでしょうかね。

そうそう出くわす状況でもなさそうですが、参考にしていただければ幸いです。