2014/09/14

aapt を実行するも No such file or directory と出てコンパイルができなくなった

Ubuntu 14.04 LTS (64bit) を新規インストールしたマシンで Android プロジェクトをビルドしようとしたら、以下のようなエラーが出てビルド出来ませんでした。
R.java が作成されていないようです。
他のマシンではコンパイルが通っているプロジェクトをそのまま持ってきている形なので、プロジェクトには問題がないはず。

Error executing aapt: Cannot run program "/home/yusuke/local/android-studio/sdk/build-tools/19.1.0/aapt": error=2, No such file or directory: error=2, No such file or directory SQLiteViewer line 1 Android ADT Problem

もちろん、表示されているディレクトリに aapt ファイルは存在しているし、実行権限もあります。
困ったなぁと思って、もう一度よく見てみると、その下に Hint なるものが表示されていました。

Hint: On 64-bit systems, make sure the 32-bit libraries are installed: "sudo apt-get install ia32-libs" or on some systems, "sudo apt-get install lib32z1" SQLiteViewer  line 1 Android ADT Problem

なるほど。
ia32-libs か lib32z1 をインストールすればいいのね。14.04 LTS だと lib32z1 をインストールすれば良い様子。
また、lib32z1 をインストールしただけでは、 libstdc++.so.6 が無いって怒られるので、そちらもインストール。

$ sudo apt-get install lib32z1
$ sudo apt-get install lib32stdc++6

これで、無事ビルドが通るようになりました。
2014/08/17

onActivityResult 内で DialogFragment.show() を実行すると IllegalStateException が発生する

タイトルの通り、FragmentActivity.onActivityResult() 内で DialogFragment.show() を実行すると、以下の様に IllegalStateException が発生して落ちてしまいます。

Caused by: java.lang.IllegalStateException: Can not perform this action after onSaveInstanceState

onActivityResult() が呼ばれる時には Activity は復活しているはずなので、IllegalStateException はおかしいと思うのですが…

調べてみると、Support Library v4 の既知のバグらしい。

確かに、android.support.v4.app.DialogFragment を android.app.DialogFragment に変更すると問題なく動作しました。

2.x 系のシェアは 15% 以下になってきたようなので、新規のアプリでは 2.x 系をサポートしないのが良いかもしれません。
とはいえ、既存のアプリは対応せざるを得ず、まだまだ Support Library を使う必要がありそうですが…

回避方法

回避方法を調べてみたのですが、大きく分けると次の 3通りに分類できそうです。

  1. Support Library を使わない
    新規で作成するのであれば、これが正攻法ですね。
    既存のアプリでも、2.x 系のサポートを終わらせて、移行するのも良いかもしれません。
    ただ、残念ながらそうもいかない場合も多いと思います。
  2. DialogFragment.show() を Override して使う
    DialogFragment.show() の中は以下のようになっています。

    android.support.v4.app.DialogFragment (Support Library rev. 20)
        public void show(FragmentManager manager, String tag) {
            mDismissed = false;
            mShownByMe = true;
            FragmentTransaction ft = manager.beginTransaction();
            ft.add(this, tag);
            ft.commit();
        }
    

    そこで、DialogFragment の子クラスで以下のように show() を Override します

    public class TestDialog extends DialogFragment {
        // 省略
    
        @Override
        public void show(FragmentManager manager, String tag) {
            // mDismissed = false;
            // mShownByMe = true;
            try {
                Field mDismissedField = DialogFragment.class.getDeclaredField("mDismissed");
                mDismissedField.setAccessible(true);
                mDismissedField.set(this, true);
                Field mShownByMeField = DialogFragment.class.getDeclaredField("mShownByMe");
                mShownByMeField.setAccessible(true);
                mShownByMeField.set(this, true);
            } catch (NoSuchFieldException |
                    IllegalAccessException |
                    IllegalArgumentException e) {
                throw new RuntimeException(e);
            }
            FragmentTransaction ft = manager.beginTransaction();
            ft.add(this, tag);
            ft.commitAllowingStateLoss();
        }
    
        // 省略
    }
    

    commit() を commitAllowingStateLoss() に書き換えたいだけなのですが、リフレクションを使わないといけないため、とても見にくくなってしまいました。
    mDissmissed と mShownByMe を無視して書いてある例もありましたが、あまりお勧めはできません。

    この方法、Dialog の状態を保存しておいて、表示の時に復活させようとすると失敗します。
    また、Support Library を更新するたびに、DialogFragment.show() の内容を確認して、修正があったら小クラスの方にも修正を加えなければなりません。
    結構面倒くさいです。
  3. onActivityResult() 内では Dialog を表示せず、onResumeFragments() 内で表示する
    現時点で最も現実的な解はこれかもしれません。
    onActivityResult() 内で Dialog を表示することを諦め、フラグを設定するだけにします。
    そして、onResumeFragments() 内でフラグをチェックして Dialog を表示するようにします。

    public class MainActivity extends FragmentActivity {
        private static final String TEST_DIALOG_TAG = "dialog";
        private static final int NO_DIALOG = -1;
        private int mShowDialogNo = NO_DIALOG;
    
        // 省略
    
        @Override
        protected void onActivityResult(int requestCode, int resultCode, Intent data){
            mShowDialogNo = requestCode;
        }
    
        @Override
        protected void onResumeFragments() {
            super.onResumeFragments();
    
            switch (mShowDialogNo) {
            case 1:
                DialogFragment dialog = (DialogFragment) getSupportFragmentManager().findFragmentByTag(TEST_DIALOG_TAG);
                if (dialog == null) {
                    dialog = new TestDialog();
                }
                dialog.show(getSupportFragmentManager(), TEST_DIALOG_TAG);
                break;
            default:
                break;
            }
            mShowDialog = NO_DIALOG;
        }
    }
    

    記述量は増えてしまいますが、仕方がありません。

補足

一部情報では Support Library rev. 18 で修正されたとあったのですが、rev. 20 で確認したところ、修正されている形跡はありませんでした。
残念ながら、2014年8月16日現在でもこの問題は存在するようです。
2014/08/12

Android の Log をリリース時にだけ表示しないようにする方法

Android に限らず、プログラムを書く上で Log を仕込むのは重要な作業です。
しかし、デバッグ用に出力した Log はリリースビルドでは出力しないようにしたいものです。
Java にはプリプロセッサがありませんので、Android アプリの開発時、私は以下の様な方法(もしくは、同様のライブラリを作成すること)でデバッグ時にログを出力しないようにしてきました。

if (BuildConfig.DEBUG) {
   LOG.d(TAG, "This is a log message");
}

しかし、先日購入した 50 Android Hacks 開発現場ですぐに役立つヒントとコード に、もっとエレガントな方法が載っていました。

それは ProGuard を利用して、後から Log を削除する方法です。
確かに、Android の開発環境ではリリース時にしか ProGuard がかからないようになっていますから、うってつけの方法と言えます何故、今まで気付かなかったのだろうか…。

以下、Android Studio と Eclipse での具体的な方法です本の中の tips を勝手に公開してしまいました。著者の方々申し訳ありません。
Android Studio に関してなど+αがあるのでご容赦いただきたいです。
皆様、他にも良い tips が載っていますので、是非本を買ってください。なお、著者の方々と私には一切の利害関係はございません。
。

Android Studio

proguard-rules.pro に以下の記述を追加

proguard-rules.pro
-assumenosideeffects public class android.util.Log {
    public static *** v(...);
    public static *** d(...);
    public static *** i(...);
    public static *** w(...);
    public static *** e(...);
    public static *** wtf(...);
}

以下のように ProGuard を有効にします。
proguard-android-optimize.txt を使っているところに注意。デフォルトでは proguard-android.txt なので書き換える必要があります。

app/build.gradle
    buildTypes {
        release {
            runProguard true
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        }
    }


Eclipse

proguard-project.txt に以下の記述を追加。

proguard-project.txt
-assumenosideeffects public class android.util.Log {
    public static *** v(...);
    public static *** d(...);
    public static *** i(...);
    public static *** w(...);
    public static *** e(...);
    public static *** wtf(...);
}

以下のように ProGuard を有効にします。
proguard-android-optimize.txt を使っているところに注意。デフォルトでは proguard-android.txt なので書き換える必要があります。

project.properties
proguard.config=${sdk.dir}/tools/proguard/proguard-android-optimize.txt:proguard-project.txt



2014/07/22

compileSdkVersion を 'android-L' にすると INSTALL_FAILED_OLDER_SDK が発生する

Android Studio を 0.8.1 にして New Project を作成すると、app/build.gradle は以下のようになっています。

app/build.gradle
apply plugin: 'com.android.application'

android {
    compileSdkVersion 'android-L'
    buildToolsVersion "20.0.0"

    defaultConfig {
        applicationId "com.kokufu.test"
        minSdkVersion 8
        targetSdkVersion 'L'
        versionCode 1
        versionName "1.0"
    }
    buildTypes {
        release {
            runProguard false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
}
// 以下略

これ、そのままコンパイルすると、以下のようなエラーが発生してしまいます。
pkg: /data/local/tmp/com.kokufu.test
Failure [INSTALL_FAILED_OLDER_SDK]

もちろん、Android L (API 20, L preview) はインストールしてあります。

で、検索してみたところ、やっぱりバグみたいです。

というわけで、compileSdkVersion を 20 にしてやるとうまくコンパイル出来るようになりました。

app/build.gradle
apply plugin: 'com.android.application'

android {
    compileSdkVersion 20
    buildToolsVersion "20.0.0"

    defaultConfig {
        applicationId "com.kokufu.test"
        minSdkVersion 8
        targetSdkVersion 20
        versionCode 1
        versionName "1.0"
    }
    buildTypes {
        release {
            runProguard false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
}
// 以下略
2014/07/11

Nexus10 用に Android 用有線LANアダプタを買ってみました

有線LANしか無い環境に Nexus 10 を持っていかなければならなくなったので、OTG対応有線LANアダプタを買ってみました。

注意
ただ、最初にお断りしておかねばなりません。
一応、動作はしたのですが、かなり微妙な動きになっているので、決してお勧め出来るものではありません。
「ちゃんと動作した」という報告を期待してこのページに来られた方には申し訳ないのですが、その点御了承の上、お読みください。

購入したのは以下。
Nexus 7 (2012) には対応しているけど、Nexus 7 (2013) には対応していないとのこと。この時点で、結構ドキドキ。



で、届いた商品がこちら。
型番は RCG-MULA01 のようです。
Model No は VK-88772A。Amazon の写真と微妙に違うなぁ。

で、Nexus 10 (Android 4.4.4) につないでみたところ、うんともすんとも言わず…
ダメかなーと思いながら、Nexus 10 を再起動してみると・・・動いたもちろん、WiFiは切っています。!!


よしよし、と思って使っていたのですが、次に使おうと思ってつなぐと動かず…。今度は再起動しても動かず。
しかし、USB を挿抜すると動きました。

現在、どのような状況下でうまく動くのかがハッキリわかっていません。
どうも、有効なIPアドレスが取れるという意味ですLANケーブルを刺した状態でUSBをつなぐのが最低条件のようです。
一度うまくいくと、USBを挿抜しても大丈夫なことが多いです。

今後、何かわかったら情報を更新したいと思います。
何か情報をお持ちの方がいらっしゃいましたら、是非教えて下さい。

ちなみに、Nexus 7 (2012) では今のところ問題なく動作しています。