‘java’ タグのついている投稿

AnimationDrawableが最後のフレームしか表示されない場合

AndroidのAnimationDrawableを再生するには

        ImageView imageView = (ImageView) findViewById(R.id.imageView1);
        imageView.setBackgroundResource(R.drawable.animation1);
        AnimationDrawable animation = (AnimationDrawable) imageView.getBackground();
        animation.setOneShot(true); // 一度だけ再生
        animation.start();

こんなコードで実行しますが、もう一度再生しようとstart()を実行すると、最後の1フレームしか再生されません。

        ImageView imageView = (ImageView) findViewById(R.id.imageView1);
        imageView.setBackgroundResource(R.drawable.animation1);
        AnimationDrawable animation = (AnimationDrawable) imageView.getBackground();
        animation.stop(); // 念のため一度ストップ
        animation.selectDrawable(0); // 0フレーム目に戻す
        animation.setOneShot(true);
        animation.start();

とすれば最初から再生できます。

DrawableContainer | Android Developers

selectDrawableってドキュメントに何の説明も書いてないんですね。


Volleyが勝手にリダイレクトするのを禁止する方法

サーバから302が返ってきたときに、Volleyが自動的にリダイレクトするのですが、開発用のAPIなどでBasic認証ヘッダを追加している場合など、リダイレクト先にも認証ヘッダを付けたままリクエストしてしまいます。

これが原因でAWS S3からデータが取れないということがありました。

とりあえず勝手にリダイレクトしないようになれば良いので、HttpURLConnectionのsetInstanceFollowRedirectsをfalseにすることで対応しました。

public class MyHurlStack extends HurlStack {

    @Override
    protected HttpURLConnection createConnection(URL url) throws IOException {
        HttpURLConnection connection = super.createConnection(url);

        // 自動リダイレクトを禁止する
        connection.setInstanceFollowRedirects(false);
        connection.setRequestProperty("Accept-Encoding", "");

        return connection;
    }

    @Override
    public HttpResponse performRequest(Request<?> request,
            Map<String, String> additionalHeaders) throws IOException,
            AuthFailureError {
        URL url = new URL(request.getUrl());
        if (url.getHost().equals("開発環境のホスト名")) {
            // Basic認証ヘッダの追加
            additionalHeaders
                    .put("Authorization", "Basic ********************");
        }

        return super.performRequest(request, additionalHeaders);
    }

}

setInstanceFollowRedirectsは

HTTP リダイレクト (応答コード 3xx の要求) を、この HttpURLConnection インスタンスで自動に従うかどうかを設定します。

とのこと。

HttpURLConnection (Java Platform SE 6)


ADT22でのMultiple dex files defineの解決方法

ADT22にして既存のプロジェクトを実行してみようと思ったら「Multiple dex files define」というエラーがコンソールに出て、実行できない状態になりました。

[2013-05-24 17:07:13 - Dex Loader] Unable to execute dex: Multiple dex files define Loauth/signpost/commonshttp/CommonsHttpOAuthConsumer;
[2013-05-24 17:07:13 - ***] Conversion to Dalvik format failed: Unable to execute dex: Multiple dex files define Loauth/signpost/commonshttp/CommonsHttpOAuthConsumer;

これは同じクラス名を持つ複数のjarがAPKに含まれている場合に出るエラーです。
ADT22でプロジェクトにAndroid Private Librariesというフォルダができていて、apkにexportするファイルが被るから、という理由のようです。

解決方法は、Eclipseのプロジェクトを右クリックして、「Build Path」→「Configure Build Path」→「Order and Export」タブで、「Android Private Libraries」にチェックが入っていることを確認して、またlibs以下のファイルにチェックが「入っていないこと」を確認します。
このエラーが出たときは、signpostのライブラリにチェックが入っていたので、外しました。


[dx] Could not reserve enough space for object heap

PHPからAndroidのapkをantでビルドする環境を作成中、ヒープ不足でVMが起動しない問題に遭遇しました。

-dex:
      [dex] input: /path1/to/bin/classes
      [dex] input: /path2/to/bin/classes.jar
      [dex] input: /path/to/android-sdk-linux/tools/support/annotations.jar
      [dex] input: /path2/to/libs/android-support-v4.jar
      [dex] Pre-Dexing /path2/to/bin/classes.jar -> classes-df6cdfb7c6be3c83700a640e18a54033.jar
       [dx] Error occurred during initialization of VM
       [dx] Could not reserve enough space for object heap
       [dx] Error: Could not create the Java Virtual Machine.
       [dx] Error: A fatal exception has occurred. Program will exit.

解決策は android-sdk-linux/platform-tools/dx の
defaultMx=”-Xmx1024M”
を小さくすればOKです。

今回は
defaultMx=”-Xmx128M”
としました。

dxコマンドを直接実行する場合は、コマンドに
-JXmx128M
とオプションを付ければ大丈夫なようですが、antの場合はできないっぽいです。

あと、ANT_OPTSはantの中で実行されるjavaコマンドについては引き継がないようです。


3D物理エンジンbulletをAndroidで使ってみた

Game Physics Simulation

以下のエントリーが詳しいです。今回はこちらで公開されているサンプルを実行してみました。

OpenGLによる3D描画とBulletによる物理演算の縫合メモ – 麗ちゃんとママの実験ノート

自分でビルドしてみようと思ってハマったポイントをいくつか書いておきます。ちなみに元のサンプルは何も問題ないです。余計なことをして個人的にハマっただけです。

bulletのバージョン

bulletのライブラリのバージョンが新しくなっているのでAndroid.mkは更新する必要があります。

bullet – Bullet is a professional free 3D Game Multiphysics Library – Google Project Hosting

僕はbullet-2.81-rev2613.zipをダウンロードしました。
jniフォルダにsrcの中身を展開します。Makefile.amを開いてリストを確認します。今回はBulletMultiThreadedを除いた、拡張子cppのファイルのリストを全部コピーして、Android.mkに貼り付けました。

ヘッダファイルは作りなおした方が良さそうです

作りなおすとヘッダファイルの内容が少し増えます。

もしくはサンプル通りの名前空間でクラスファイルを作りましょう。

後述するUnsatisfiedLinkErrorの関係で、正確なメソッド名がわからなくて困っていました。
javahコマンドで作成できます。

C:\Users\test\Documents\workspace\BulletTest>javah -classpath bin/classes -o jni/info_play_smart_android_bullettest_Cube.h info.play_smart.android.bullettest.Cube

クラスパスが間違っていると以下のようなエラーになります。

C:\Users\test\Documents\workspace\BulletTest>javah -classpath bin -o jni/info_play_smart_android_bullettest_Cube.h info.play_smart.android.bullettest.Cube
エラー:info.play_smart.android.bullettest.Cube にアクセスできません。
info.play_smart.android.bullettest.Cube のクラスファイルが見つかりません
javadoc: エラー - クラス info.play_smart.android.bullettest.Cube が見つかりません。
Error: コマンド行でクラスが指定されませんでした。-help で確認してください。

ただ、引数の名前と数が変わってしまうので、Cube.cppファイルから引数の部分だけコピーしてヘッダファイルを更新しました。

名前空間とアンダースコア

名前空間にアンダースコアが含まれるクラスを作っていたので、以下のようなエラーが発生しました。

01-05 12:56:43.351: W/dalvikvm(20100): threadid=11: thread exiting with uncaught exception (group=0x40c5d300)
01-05 12:56:43.390: E/AndroidRuntime(20100): FATAL EXCEPTION: AsyncTask #1
01-05 12:56:43.390: E/AndroidRuntime(20100): java.lang.RuntimeException: An error occured while executing doInBackground()
01-05 12:56:43.390: E/AndroidRuntime(20100):     at android.os.AsyncTask$3.done(AsyncTask.java:299)
01-05 12:56:43.390: E/AndroidRuntime(20100):     at java.util.concurrent.FutureTask$Sync.innerSetException(FutureTask.java:273)
01-05 12:56:43.390: E/AndroidRuntime(20100):     at java.util.concurrent.FutureTask.setException(FutureTask.java:124)
01-05 12:56:43.390: E/AndroidRuntime(20100):     at java.util.concurrent.FutureTask$Sync.innerRun(FutureTask.java:307)
01-05 12:56:43.390: E/AndroidRuntime(20100):     at java.util.concurrent.FutureTask.run(FutureTask.java:137)
01-05 12:56:43.390: E/AndroidRuntime(20100):     at android.os.AsyncTask$SerialExecutor$1.run(AsyncTask.java:230)
01-05 12:56:43.390: E/AndroidRuntime(20100):     at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1076)
01-05 12:56:43.390: E/AndroidRuntime(20100):     at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:569)
01-05 12:56:43.390: E/AndroidRuntime(20100):     at java.lang.Thread.run(Thread.java:856)
01-05 12:56:43.390: E/AndroidRuntime(20100): Caused by: java.lang.UnsatisfiedLinkError: Native method not found: info.play_smart.android.bullettest.Cube.simulate:(Linfo/play_smart/android/bullettest/Cube;)V
01-05 12:56:43.390: E/AndroidRuntime(20100):     at info.play_smart.android.bullettest.Cube.simulate(Native Method)
01-05 12:56:43.390: E/AndroidRuntime(20100):     at info.play_smart.android.bullettest.Cube.simulate(Cube.java:75)
01-05 12:56:43.390: E/AndroidRuntime(20100):     at info.play_smart.android.bullettest.Cube$AsyncTestTask.doInBackground(Cube.java:69)
01-05 12:56:43.390: E/AndroidRuntime(20100):     at info.play_smart.android.bullettest.Cube$AsyncTestTask.doInBackground(Cube.java:1)
01-05 12:56:43.390: E/AndroidRuntime(20100):     at android.os.AsyncTask$2.call(AsyncTask.java:287)
01-05 12:56:43.390: E/AndroidRuntime(20100):     at java.util.concurrent.FutureTask$Sync.innerRun(FutureTask.java:305)
01-05 12:56:43.390: E/AndroidRuntime(20100):     ... 5 more

JNIの呼び出しは

JNIEXPORT void JNICALL Java_名前空間_クラス名_関数名

で定義された関数を呼び出します。

.(ドット)は_(アンダースコア)に変換します。

今回、名前空間にinfo.play_smartを使ったのでinfo_play_smartとしていたのですが、それが間違いでした。

すでにあるアンダースコアは「_1」に変換しておく必要があります。

この辺は上記ヘッダファイルを作ったときに初めて知りました。(ヘッダファイル中に記述されるので)

名前空間を元のサンプルに合わせておけばもっと楽にできたので残念です。

一応これで動くようになりました。

LuaかSquirrelとバインドして動くようにできたら便利だろうなぁ。誰か作らないかなぁ。


AndroidのORM、greenDAOを使ってみた

Androidで使えるORMとしてはormliteが有名ですが、より高速だという触れ込みのgreenDAOを使ってみました。バージョンは1.2です。
greenDAO – Android ORM for SQLite | greenDAO is a light & fast ORM solution that maps objects to SQLite databases.

greenDAOはSQLiteをDAO(Data Access Object)で扱うことができます。
ジェネレーターを使ってエンティティとDAOをあらかじめ自動生成することで、データベース定義のコードを書く手間から解放されます。

このスライドも参考になります。後半はパフォーマンスに関する話題です。

まずはExampleを触ってみます。このExampleは自分で一度試してみたほうが良さそうです。今後はDaoExampleGeneratorを編集して自分のプロジェクトに適用することになるので。

EclipseのFileメニューからImport->Existing Projects into Workspaceで
DaoExample
DaoExampleGenerator
を取り込みます。

de.greenrobot.daogenerator.gentest.ExampleDaoGeneratorを見ると、バージョン番号、書き出し先のパッケージ名、スキーマ定義、そして書き出し先の相対パスを指定するだけの内容になっていることがわかります。

実際に使うときはこのファイルを書き換えてスキーマ定義をしていきます。
新規に同じ内容のプロジェクトを作ってしまってもいいですが、毎回このDaoExampleGeneratorをインポートして名前変更するのが楽かと思います。

WS000031

ジェネレーターはJava Applicationとして実行します。

image

ExampleDaoGeneratorを実行するとコンソールにログが出ます。

greenDAO Generator
Copyright 2011-2012 Markus Junginger, greenrobot.de. Licensed under GPL V3.
This program comes with ABSOLUTELY NO WARRANTY
Processing schema version 3...
Written C:\Users\test\Documents\workspace\DaoExample\src-gen\de\greenrobot\daoexample\NoteDao.java
Written C:\Users\test\Documents\workspace\DaoExample\src-gen\de\greenrobot\daoexample\Note.java
Written C:\Users\test\Documents\workspace\DaoExample\src-gen\de\greenrobot\daoexample\CustomerDao.java
Written C:\Users\test\Documents\workspace\DaoExample\src-gen\de\greenrobot\daoexample\Customer.java
Written C:\Users\test\Documents\workspace\DaoExample\src-gen\de\greenrobot\daoexample\OrderDao.java
Written C:\Users\test\Documents\workspace\DaoExample\src-gen\de\greenrobot\daoexample\Order.java
Written C:\Users\test\Documents\workspace\DaoExample\src-gen\de\greenrobot\daoexample\DaoMaster.java
Written C:\Users\test\Documents\workspace\DaoExample\src-gen\de\greenrobot\daoexample\DaoSession.java
Processed 3 entities in 334ms

これで上記ファイルが生成されました。

ちなみにExampleでは不要ですが、対象プロジェクトのsrc-genフォルダはあらかじめ作っておく必要があるようです。

また、プロジェクトのプロパティから、Java Build Path→Sourceタブを開いて、Add Folderを実行、src-genを追加する必要があります。

libsフォルダも作成して、greenDAO.jarをコピー、Java Build Path→Librariesタブ→Add JARsでgreenDAO.jarを参照します。

全件読み込みはloadAllメソッドを使えばリストオブジェクトで取得できます。

        List<Note> list = noteDao.loadAll();

Cursorを取得する場合はSQLiteDatabaseに対してqueryを実行することもできます。

        cursor = db.query(noteDao.getTablename(), noteDao.getAllColumns(), null, null, null, null, orderBy);

書き込みはオブジェクトのインスタンスを作成してinsert、という仕組みです。

        Note note = new Note(null, noteText, comment, new Date());
        noteDao.insert(note);

insert系メソッドも、同時に複数追加できるinsertInTxや、insertもしくはupdateで、さらに複数実行のinsertOrReplaceInTxなどもあります。

必要なら生成されたDAOを拡張してメソッド追加すれば色々便利そうです。

この辺はJavaDoc参照です。

http://greendao-orm.com/javadoc/greendao/

複雑な検索はクエリービルダーを使います。

Queries | greenDAO – Android ORM for SQLite

List joes = userDao.queryBuilder()
.where(Properties.FirstName.eq("Joe"))
.orderAsc(Properties.LastName)
.list();

サブクエリも自分で書けるみたいなので、greenDAOの利用が制約にならないというのはいいですね。

あと、バージョン番号は指定できますが、マイグレーション処理については自分で書く必要があります。DaoMaster.OpenHelper(SQLiteOpenHelperの拡張)を拡張してonUpgradeを書くことになります。初期データを入れたい場合も同じようにonCreateをオーバーライドします。そもそもOpenHelperはabstractなので必ずサブクラスを作る必要があります。

OpenHelperとDevOpenHelperがありますが、DevOpenHelperはonUpgrade時にデータベースを一度ドロップする点が違います。リリースビルドではOpenHelperのサブクラスを使います。

public class MyOpenHelper extends OpenHelper {

    private static final String DB_NAME = "clocks-db";

    private SQLiteDatabase db = null;
    private DaoMaster daoMaster = null;
    private DaoSession daoSession = null;

    public MyOpenHelper(Context context, CursorFactory factory) {
        super(context, DB_NAME, factory);

        // onCreate呼び出しのためにデータベース作成
        this.getDatabase();
    }

    @Override
    public void onCreate(SQLiteDatabase db) {
        super.onCreate(db);

        // ローカル変数としてセッションを作成する
        DaoMaster daoMaster = new DaoMaster(db);
        DaoSession daoSession = daoMaster.newSession();
        // 初期データ作成
        ClockSettingDao clockSettingDao = daoSession.getClockSettingDao();
        ClockSetting data = new ClockSetting(null, "", 0, 0);
        clockSettingDao.insert(data);
    }

    @Override
    public void onUpgrade(SQLiteDatabase arg0, int arg1, int arg2) {
    }

    public SQLiteDatabase getDatabase() {
        if (this.db == null) {
            this.db = this.getWritableDatabase();
        }

        return this.db;
    }

    public DaoMaster getDaoMaster() {
        if (this.daoMaster == null) {
            this.daoMaster = new DaoMaster(this.getDatabase());
        }

        return this.daoMaster;
    }

    public DaoSession getDaoSession() {
        if (this.daoSession == null) {
            this.daoSession = this.getDaoMaster().newSession();
        }

        return this.daoSession;
    }

}

セッションはアプリケーションスコープで持つことを推奨されています。

http://greendao-orm.com/documentation/how-to-get-started/#comment-45

public class MyApplication extends Application {

    private MyOpenHelper dbHelper;

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

        this.dbHelper = new MyOpenHelper(this, null);
    }

    public DaoSession getDaoSession() {
        return this.dbHelper.getDaoSession();
    }

}

ちなみにcloseは不要です。

SQLiteを使う場合の注意点 – 一歩ずつコツコツと

AndroidManifest.xmlのapplicationにMyApplicationへのクラスパスを指定します。

    <application
        android:name="test.MyApplication"
        android:allowBackup="true"
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name"
        android:theme="@style/TestTheme" >

呼び出しはこんな形で。

        MyApplication app = (MyApplication) getApplication();
        DaoSession daoSession = app.getDaoSession();
        ClockSettingDao clockSettingDao = daoSession.getClockSettingDao();

        ClockSetting data = clockSettingDao.queryBuilder()
                .orderAsc(Properties.Priority).limit(1).list().get(0);

使ってみた感想ですが、あまり癖がないので使いやすいと思います。せっかくジェネレートしてくれるなら、自分の処理を追加するための拡張クラスも作っておいてくれるとさらに便利なのですが。

最後に、一点気になる点は、生成されたファイルがCR+LF改行だったことです。Windows環境だからかな…


Eclipse JunoでMercurialEclipseがエラーを出す問題

EclipseでMercurialEclipseを使っているのですが、Junoにアップデートしてから事あるごとにポップアップでエラーが表示されて困っていました。

ヒストリーを表示したときや
An error has occurred. See error log for more details.
com.vectrace.MercurialEclipse.history.GraphLogTableViewer cannot be cast to org.eclipse.ui.part.PageBookView$SelectionProvider
PerspectiveをJavaに切り替えた時などに発生していました。

同じ現象がこちらで現在進行中ですが
https://bitbucket.org/mercurialeclipse/main/issue/320/exception-on-history-when-using-eclipse

MercurialEclipse 2.0.1.v201210021536
というバージョンに問題があるようです。

ところがこのバージョン、公式のリポジトリにはなく、調べてみたら、以下のリポジトリを有効にしていると入るようです。
http://cbes.javaforge.com/update

Window→Preferences→Install/Update→Available Software Sitesでこのリポジトリを除外して、公式
でアナウンスされている
http://mercurialeclipse.eclipselabs.org.codespot.com/hg.wiki/update_site/stable
を登録すれば解決でした。

MercurialEclipse    2.0.0.201208160020
がインストールされました。

もし、すでにv201210021536などの新しいバージョンが入ってしまっている場合はHelp→About Eclipse→Installation Details→Installation Historyから、古い日付を選択してMercurialEclipseをRevertしてください。


Android版OpenCVでファイルからのキャプチャ

Android版のOpenCVで、ローカルのSDカード内にある動画ファイルを開いて編集したかったのですが、VideoCapture.javaにはネイティブメソッドの定義があるものの、Java側にコンストラクタが用意されていなかったので、こんな感じでMyVideoCapture.javaを用意して試してみました。

import org.opencv.highgui.VideoCapture;

public class MyVideoCapture extends VideoCapture {

    protected final long nativeObj;

    public MyVideoCapture(String file) {
        nativeObj = n_VideoCapture(file);

        return;
    }

    private static native long n_VideoCapture(java.lang.String filename);

}

結果としては、UnsatisfiedLinkErrorが発生しました。

09-16 23:34:51.932: W/dalvikvm(23911): No implementation found for native Lcom/example/android/opencv_test/utils/MyVideoCapture;.n_VideoCapture (Ljava/lang/String;)J
09-16 23:35:04.151: D/AndroidRuntime(23911): Shutting down VM
09-16 23:35:04.151: W/dalvikvm(23911): threadid=1: thread exiting with uncaught exception (group=0x40a251f8)
09-16 23:35:04.182: E/AndroidRuntime(23911): FATAL EXCEPTION: main
09-16 23:35:04.182: E/AndroidRuntime(23911): java.lang.UnsatisfiedLinkError: n_VideoCapture
09-16 23:35:04.182: E/AndroidRuntime(23911): 	at com.example.android.opencv_test.utils.MyVideoCapture.n_VideoCapture(Native Method)
09-16 23:35:04.182: E/AndroidRuntime(23911): 	at com.example.android.opencv_test.utils.MyVideoCapture.<init>(MyVideoCapture.java:10)
09-16 23:35:04.182: E/AndroidRuntime(23911): 	at com.example.android.opencv_test.MainActivity.onClick(MainActivity.java:92)
09-16 23:35:04.182: E/AndroidRuntime(23911): 	at android.view.View.performClick(View.java:3511)
09-16 23:35:04.182: E/AndroidRuntime(23911): 	at android.view.View$PerformClick.run(View.java:14105)
09-16 23:35:04.182: E/AndroidRuntime(23911): 	at android.os.Handler.handleCallback(Handler.java:605)
09-16 23:35:04.182: E/AndroidRuntime(23911): 	at android.os.Handler.dispatchMessage(Handler.java:92)
09-16 23:35:04.182: E/AndroidRuntime(23911): 	at android.os.Looper.loop(Looper.java:137)
09-16 23:35:04.182: E/AndroidRuntime(23911): 	at android.app.ActivityThread.main(ActivityThread.java:4424)
09-16 23:35:04.182: E/AndroidRuntime(23911): 	at java.lang.reflect.Method.invokeNative(Native Method)
09-16 23:35:04.182: E/AndroidRuntime(23911): 	at java.lang.reflect.Method.invoke(Method.java:511)
09-16 23:35:04.182: E/AndroidRuntime(23911): 	at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:784)
09-16 23:35:04.182: E/AndroidRuntime(23911): 	at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:551)
09-16 23:35:04.182: E/AndroidRuntime(23911): 	at dalvik.system.NativeStart.main(Native Method)

原因はファイルを開いてデコードするのに必要なnative側の機能を、Androidが持っていないため無効になっている、らしいです。

VideoCapture(String) interface – Google グループ

まぁ、キャプチャだけの用途ならMediaMetadataRetriever.getFrameAtTimeが使えるので、回避はできそうですが、先行きが不安です。

追記:

OpenCV 2.4.3ではAndroid 3.0以上に対してこの機能が有効になるそうです。OpenCV 2.4.3は2012年9月上旬にはリリースされるとのこと。

How to read a avi file from Android Opencv? – Google グループ

でもなぁ、最低でもAndroid 2.3はサポートしないとなぁ…


AsyncTaskLoaderのあるActivityに戻ってきたときに再度loadInBackgroundが呼ばれる問題

最近はもっぱらAndroidのSupport LibraryでFragmentActivityな日々ですが、AsyncTaskLoaderが良いというウワサなのでAsyncTaskを置き換える程度の気持ちで使ったらガッツリハマりました。

今回の問題はAsyncTaskLoaderを実装したActivityから他のActivityに移動し、戻るボタンで戻ってきたときに勝手にloadInBackgroundが実行されてしまう問題です。

結論としては、何も考えずに以下サイトのdeliverResult以下全部自分のAsyncTaskLoaderにコピーしておけばいいです。

android – onLoadFinished not called after coming back from a HOME button press – Stack Overflow

一応転載しておきます。

public abstract class AsyncLoader<D> extends AsyncTaskLoader<D> {

    private D data;

    public AsyncLoader(Context context) {
        super(context);
    }

    @Override
    public void deliverResult(D data) {
        if (isReset()) {
            // An async query came in while the loader is stopped
            return;
        }

        this.data = data;

        super.deliverResult(data);
    }


    @Override
    protected void onStartLoading() {
        if (data != null) {
            deliverResult(data);
        }

        if (takeContentChanged() || data == null) {
            forceLoad();
        }
    }

    @Override
    protected void onStopLoading() {
         // Attempt to cancel the current load task if possible.
        cancelLoad();
    }

    @Override
    protected void onReset() {
        super.onReset();

        // Ensure the loader is stopped
        onStopLoading();

        data = null;
    }
}

この例では抽象クラスなので同じようなことをする場合はAsyncLoaderをextendsすればいいですね。

こちらも大体同じ構成のようです。

時代は AsyncTask より AsyncTaskLoader – ぐま あーかいぶ

呼び出しに時間のかかるサーバサイドのAPIの処理に利用しようとして、ActivityのonCreateで

getSupportLoaderManager().initLoader(0, null, mCallbacks);

として、呼び出しのたびに

        Bundle bundle = new Bundle(1);
        bundle.putString(KEY_PARAM_1, "something");
        getSupportLoaderManager().restartLoader(0, bundle, mCallbacks);

として呼び出していました。

戻るボタンで戻ってくると、これらを通らずにLoaderがonStartLoadingを実行するので、ここでforceLoadを実行していると、loadInBackgroundが実行されてしまいます。

まぁ、結局問題は条件をつけずに

    @Override
    protected void onStartLoading() {
        forceLoad();
    }

にしてしまっていたことなので、気をつけましょうという話です。

Loaderは概念もうちょっと理解しないと、危ないですね。


AlertDialogのViewとの隙間を埋める

AndroidのAlertDialogを継承したクラスで、タイトルなし、setViewでダイアログいっぱいにViewを広げようとしたら、上下に隙間が開いてしまいました。

image

解決方法は簡単で、setView(View view)ではなく、setView(View view, int viewSpacingLeft, int viewSpacingTop, int viewSpacingRight, int viewSpacingBottom)を使うだけです。

image

コードのサンプルは以下のような感じです。

public class MyDialog extends AlertDialog {

    public MyDialog(Context context) {
        super(context);
    }

    @Override
    public void show() {

        LayoutInflater inflater = (LayoutInflater) this.getContext()
                .getSystemService(Context.LAYOUT_INFLATER_SERVICE);

        View layout = inflater.inflate(R.layout.my_dialog, null);

        GridView gridView = (GridView) layout.findViewById(R.id.gridView1);

        // this.setView(layout); // <- 上下に余白ができる
        this.setView(layout, 0, 0, 0, 0); // <- ぴったり埋まる

        super.show();

    }

}

ちなみに、AlertControllerの中ではこのように処理されています。

            customPanel = (FrameLayout) mWindow.findViewById(R.id.customPanel);
            FrameLayout custom = (FrameLayout) mWindow.findViewById(R.id.custom);
            custom.addView(mView, new LayoutParams(MATCH_PARENT, MATCH_PARENT));
            if (mViewSpacingSpecified) {
                custom.setPadding(mViewSpacingLeft, mViewSpacingTop, mViewSpacingRight,
                        mViewSpacingBottom);
            }
            if (mListView != null) {
                ((LinearLayout.LayoutParams) customPanel.getLayoutParams()).weight = 0;
            }

R.id.customはalert_dialog.xmlで定義されています。

    <FrameLayout android:id="@+id/customPanel"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_weight="1">
        <FrameLayout android:id="@+android:id/custom"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:paddingTop="5dip"
            android:paddingBottom="5dip" />
    </FrameLayout>

上下5dip開いてますね。