2012/10/27

ClipboardManager で NoClassDefFoundError

Java のパッケージは便利なのですが、同じ名前でパッケージが違うと時々混乱します。
Android の ClipboardManager もそんなクラスの一つです。

テキストを Clipboard にコピーする方法として、以下の様なコードがよく提示されています。
あえて import 文を見せているところが重要。
import android.content.ClipboardManager;

// 省略

private void copyToClipboard(String text) {
    ClipboardManager clipboardManager =
            (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE);
    clipboardManager.setText(text);
}


これ、最近のADTでは、"Class requires API level 11 (current min is 7)" というように怒られます。


このエラー、「API11 未満ではこのクラスは提供されていないよ。」という意味ですから、この時点で何かおかしいとは思うわけですが、とりあえず、@TargetApi(11) をつけて問題を回避してみます。
import android.content.ClipboardManager;

// 省略

@TargetApi(11)
private void copyToClipboard(String text) {
    ClipboardManager clipboardManager =
            (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE);
    clipboardManager.setText(text);
}

これを、API11 未満の端末で動作させると、NoClassDefFoundError が発生します。
E/AndroidRuntime(221): java.lang.NoClassDefFoundError: android.content.ClipboardManager

先ほど Eclipse で警告が出ていたように、Class が存在していないようです。
2012/10/26

adb を使わないで fastbootモードにする @ Nexus7

先日、穀風: Nexus7 を工場出荷状態に初期化してみた で fastbootモードにする手順として、以下のように書きました。
fastboot Nexus7 をUSBで接続して、Cygwin から以下のコマンドを実行。
$ adb.exe reboot bootloader

しかし、改めて Building for devices | Android Open Source を読んでいたら、以下のような記述を発見。
During a cold boot, the following key combinations can be used to boot into fastboot mode


(adb を使わなくても、)電源ON してからロゴが表示されている間に音量ダウンキーを押せばオッケーとのことです。

実際やってみると、電源キーから手を離してしまうとダメでした。
「電源を押したまま、ロゴが表示されたタイミングで音量ダウンキーを押す」とうまくいきます。
ネット検索してみると、多くの方が、「音量ダウンキーを押したまま、電源キー長押し」と書いています。
確かに、そちらの方が確実ですね。
2012/10/23

Nexus7 用に Android をビルドしてみた (3)

穀風: Nexus7 用に Android をビルドしてみた (2) でソースコードの取得が完了したので、次は Building the System | Android Open Sourceを参考に、実際のビルドに移ります。
ターゲットはBuilding for devices | Android Open Sourceに載っていました。
$ source build/envsetup.sh
$ lunch full_grouper-userdebug
$ make -j4

気長に待つこと数時間。
コンパイルが終了しました。

あとは実機に焼くだけです。
Nexus7 を接続し、まずは fastbootモード立ち上げます。
$ adb reboot bootloader

私の Nexus7 は既にアンロック済みなので、このまま続行しますが、まだの場合は以下のコマンドでブートローダーをアンロックします。
$ fastboot oem unlock

出力先のディレクトリに移動し、書き込みコマンドを実行します。
$ cd out/target/product/grouper
$ fastboot -w flashall

書き込みが終了すると、自動的にリブートがかかり、あのお馴染みの起動画面が立ち上がります。
エミュレータを良く使ってる人は見飽きているはずのこれです。


数秒待つと、Android のホーム画面に。
当然ですが、Gmail も Google Play もないプレーンな Androidです。

なんか、役に立たない子になっちゃったなぁ(笑)
まぁ、ちょっと遊んだら工場出荷時に戻してやろうと思います。


2012/10/22

Nexus7 用に Android をビルドしてみた (2)

穀風: Nexus7 用に Android をビルドしてみた (1) で環境構築が済んだので、次は Downloading the Source Tree | Android Open Sourceを参考にソースコードを取ってきます。

ダウンロード先は任意に決められますが、今回は ~/android_src というディレクトリにソースコードをダウンロードすることにしました。

また、Building for devices | Android Open Sourceによると、Nexus7 用のブランチは 「android-4.1.1_r4 or master」 と書かれています。
ただ、先日のアップデートで Nexus7 は Android 4.1.2 になってしまいました。
後ほど書きますが、ビルドに必要なバイナリも 4.1.2 用しか提供されていません。
とりあえず、今回は android-4.1.2_r1 を取ってくることにしました。(Nexus7 のデバイスネームは grouper って言うらしい)
2013/2/5 追記
現在の最新ブランチ(タグ)は android-4.2.1_r1.2 です。
Nexus 系はどんどん新しいバージョンが出るので、どのブランチを取って来るべきか悩みます。
基本的には提供されているバイナリ(下記)を見て決定すればよいでしょう。
指定できるブランチ(タグ)名は https://android.googlesource.com/platform/manifest にブラウザなどでアクセスするとわかります
$ mkdir ~/android_src
$ cd ~/android_src
$ repo init -u https://android.googlesource.com/platform/manifest -b android-4.1.2_r1
$ repo sync

結構長い時間がかかりますが、ソースコードの取得が終了するまで気長に待ちます。

その間に、Nexus7 の動作に必要なバイナリを取得しておきます。
Building for devices | Android Open Source によると、ICS 以降、オープンソースに出来ないハードウェアドライバのバイナリが必要であるとのこと。
それらを、Google's Nexus driver page からダウンロードしておく必要があるそうです。

ちなみに、2012/12/22 現在のスクリーンショットは以下です。
既に 4.1.2 のものしか提供されていませんので、これらをダウンロードしてきます。

ダウンロードしたファイルをソースツリーのルートに置きます。
今回は ~/android_src です。
tar ball を展開すると、シェルスクリプトが作成されますので、それらを実行します。
$ ls *.tgz
broadcom-grouper-jzo54k-26240daf.tgz    nvidia-grouper-jzo54k-56de148f.tgz
elan-grouper-jzo54k-c889b8f4.tgz        nxp-grouper-jzo54k-4bfb1fb6.tgz
invensense-grouper-jzo54k-aae1cd0c.tgz  widevine-grouper-jzo54k-aca0e725.tgz
$ for i in *.tgz
> do
> tar zxf $i
> done
$ for i in *.sh
> do 
> ./${i}
> done

この時、各スクリプトを実行すると、ライセンスに同意するよう求められます。

同意するには、Y や Yes ではなく、I ACCEPT と書かなければいけません。

全てうまく行くと、vendor というディレクトリに必要なファイルが展開されます。

過去のビルドがあって、バイナリを新規に追加した場合は、以下のコマンドを実行して残骸を消しておく必要があるようです。
$ make clobber

うまく行ったら、もとのファイルは削除してしまってかまいません。
rm *.tgz *.sh

穀風: Nexus7 用に Android をビルドしてみた (3)

Nexus7 用に Android をビルドしてみた (1)

@ Ubuntu 12.04 LTS

Nexus7 を手に入れたので、自分でビルドしていろいろいじってみようかと思います。
とりあえずは、AOSPから取ってきたソースコードをビルドして焼いてみるところまで。

以下を参考に環境を構築します。
Initializing a Build Environment | Android Open Source

基本的には書いてある通りなのですが、現在は sun JDK 6 の入手先が変わっているので要注意。
以下のサイトを参考にさせていただきました。
Installing Java6 JDK on Ubuntu 12.04 | digital nomad
$ mkdir ~/src
$ cd ~/src
$ git clone https://github.com/flexiondotorg/oab-java6.git
$ cd ~/src/oab-java6
$ sudo ./oab-java.sh
$ sudo apt-get install sun-java6-plugin sun-java6-jre sun-java6-bin sun-java6-jdk

update-java-alternatives を使用して実行環境を java-6-sun にしておきます。
参考: まさおのブログ (表): Ubuntu 複数インストールされている java を切り替え
$ update-java-alternatives -l
java-1.6.0-openjdk-amd64 1061 /usr/lib/jvm/java-1.6.0-openjdk-amd64
java-1.7.0-openjdk-amd64 1051 /usr/lib/jvm/java-1.7.0-openjdk-amd64
java-6-sun 63 /usr/lib/jvm/java-6-sun
$ sudo update-java-alternatives -s java-6-sun

必要なパッケージインストール
$ sudo apt-get install git-core gnupg flex bison gperf build-essential \
  zip curl libc6-dev libncurses5-dev:i386 x11proto-core-dev \
  libx11-dev:i386 libreadline6-dev:i386 libgl1-mesa-glx:i386 \
  libgl1-mesa-dev g++-multilib mingw32 openjdk-6-jdk tofrodos \
  python-markdown libxml2-utils xsltproc zlib1g-dev:i386
$ sudo ln -s /usr/lib/i386-linux-gnu/mesa/libGL.so.1 /usr/lib/i386-linux-gnu/libGL.so

/etc/udev/rules.d/51-android.rules に以下の行を加える(ファイルがない場合は作成する)
# adb protocol on grouper (Nexus 7)
SUBSYSTEM=="usb", ATTR{idVendor}=="18d1", ATTR{idProduct}=="4e42", MODE="0666"
# fastboot protocol on grouper (Nexus 7)
SUBSYSTEM=="usb", ATTR{idVendor}=="18d1", ATTR{idProduct}=="4e40", MODE="0666"

CCACHE と repo もインストールしておく
$ sudo apt-get install ccache
$ mkdir ~/bin
$ curl https://dl-ssl.google.com/dl/googlesource/git-repo/repo > ~/bin/repo
$ chmod a+x ~/bin/repo

~/.bashrc に以下を加える
export USE_CCACHE=1
PATH=~/bin:$PATH

変更を反映させて環境構築完了。
$ source ~/.bashrc

穀風: Nexus7 用に Android をビルドしてみた (2)
2012/10/19

CheckedTextView と SimpleAdapter の組み合わせで IllegalStateException

以下のようなコードを Android 2.2 以前の端末で実行すると IllegalStateException が発生します。
List<Map<String, String>> data = new ArrayList<Map<String, String>>();
for (int i = 0; i < 10; i++) {
    Map<String, String> map = new HashMap<String, String>();
    map.put("item", String.valueOf(i));
    data.add(map);
}
SimpleAdapter adapter = new SimpleAdapter(
                this,
                data,
                android.R.layout.simple_list_item_single_choice,
                new String[] {"item"},
                new int[] {android.R.id.text1});
mListView.setAdapter(adapter);

LogCat の出力は以下のような感じ
E/AndroidRuntime(2226): java.lang.IllegalStateException: android.widget.CheckedTextView should be bound to a Boolean, not a class java.lang.String

これ、やっかいなのは Android 2.3 以降では普通に実行できるのです。
なので、きちんと古い端末で動作確認しないと問題を発見できません。

この問題の原因は SimpleAdapter の実装にあります。
2.3 以降の SimpleAdapter は TextView を継承している View に対して暗黙的にテキスト (データに対しtoString()したもの) を割り当ててくれるのですが、古いものにはそのような機能はありません。

そのため、2.2 以前を対象にする場合は ViewBinder を明示的に定義してやる必要があります。
具体的には、以下のように setAdapter より手前で setViewBinder を呼んでやります。
adapter.setViewBinder(new ViewBinder() {
    @Override
    public boolean setViewValue(View view, Object data, String textRepresentation) {
        CheckedTextView v = (CheckedTextView) view;
        v.setText((CharSequence) data);
        return true;
    }
});
mListView.setAdapter(adapter);

2012/10/11

Exception が発生した時の LogCat の読み方 (3)

穀風: Exception が発生した時の LogCat の読み方 (2) に続いて、もう少し現実的なパターンを書いておきます。

サンプルは前回までと同じものです。


Button3 を押すと、以下のような Exception が発生します。

これまでのルールだと一行目を見て、RuntimeException が発生したということになります。
まぁそれで間違いではないのですが、RuntimeException というのは例外処理を必須としない一般的な Exception です。
どんなエラーが起こったかの情報にはなりません。

実は、今回のエラーは別スレッドで起こっているので、このようなログが出力されるのです。
2012/10/10

Nexus7 を工場出荷状態に初期化してみた

せっかく Nexus7 を買ったので、カスタムロムを作って遊ぼうと思っているのですが、ミスった時に元に戻せないと危険なので、工場出荷時に戻せるか試してみました。

今回は Cygwin on Windows 7 Professional (64bit) で実行しましたが、当然 Linux でも出来ると思います。

以下は前提条件です。
  • Android SDK がインストールされていること
  • %SDK Location%/platform-tools にパスが通っていること
  • 開発者向けオプション - USBデバッグがONになっていること

Nexus7 のファクトリーイメージを以下のサイトからダウンロードしておきます。
Factory Images for Nexus Devices - Android — Google Developers

2012/10/10 時点でのダウンロード先

それでは、早速初期化してみます。
まずは、Nexus7 をUSBで接続して、Cygwin から以下のコマンドを実行。
$ adb.exe reboot bootloader
もしくは、音量ダウンキー+電源ボタンで起動(2012/10/26 追記 穀風: adb を使わないで fastbootモードにする @ Nexus7

すると、Nexus7 が再起動し、fastboot モードで立ち上がります。
fastboot モードで立ち上がったところ
ドロイド君がパカっとお腹を開けられてしまっています。
2012/10/07

Exception が発生した時の LogCat の読み方 (2)

穀風: Exception が発生した時の LogCat の読み方 (1) では、最もシンプルな LogCat の読み方を書きましたが、実際はそんなに単純な Exception が出ることは稀です。
今回はもう少し現実的な Exception を見てみたいと思います。

サンプルは前回と同様のものです。


Button2 を押すと、以下のような Exception が発生します。


上から見ていくと、IndexOutOfBoundsException が発生したことがわかります。
どんな Exception なのかは調べてみて下さい。

さらに、次の行を見ていくと、ArrayList.java の 257行目が発生源のようです。
そんなコードを書いた覚えはありません…
2012/10/06

Exception が発生した時の LogCat の読み方 (1)

先日、知人が Exception (例外)が発生した時のデバッグが大変だと言うので、話を聞いてみたところ、LogCat を使っていないということがわかりました。
いや、正確には LogCat 自体は知っていて、怪しそうなところに片っ端からログを出力してデバッグしているとのこと。
いわゆる printfデバッグですね。

デバッグ手法として間違ってはいないけど、まずはスタックトレースの見方を知らないと大変だよねってことで、結構真面目に説明してあげました。
せっかくなので、ここにも書いておこうかと思います。


まずは、サンプルコードを作ります。
今回は、ボタンを押したら Exception が発生するサンプルを作成してみました。
以下にアップロードしてあります。
ExceptionThrower.zip


あえて、どんな Exception が発生するかわからないようにしてみたので、実行する前にコードを見て、どんな Exception が発生するか想像してみるのも良いでしょう。