RxAndroidで勘違いしていたこと

2015/04/22

Android RxAndroid RxJava RxAndroid

Observable.subscribe()したら必ずsubscribe()の戻り値であるSubscriptionのunsubscribe()を呼ばなければいけない

そう思っていた時期が僕にもありました。 Subscriptionのleakが起きるのはonCompletedが呼ばれないObservableをsubscribe()したままにしてしまう場合です。以下のコード例はonCompletedが呼ばれてunsubscribeされるので明示的に呼び出す必要はありません。 ただし、Activityのライフサイクルより処理が長くなる場合は不要な処理となるのでCompositeSubscription等を使ってonPause()で明示的にunsubscribe()してあげましょう。

Observable.just(...).subscribe(...);
Observable.from(...).subscribe(...);
Observable.create(new Observable.OnSubscribe<Object>() {
    @Override
    public void call(Subscriber<? super Object> subscriber) {
        subscriber.onNext(new Object());
        subscriber.onCompleted();
    }
}).subscribe(...);
Observable.create(new Observable.OnSubscribe<Object>() {
    @Override
    public void call(Subscriber<? super Object> subscriber) {
        subscriber.onError(new IllegalStateException());
    }
}).subscribe(...);

参考

Top 7 Tips for RxJava on Android

RxAndroidでよくやる間違い(実行スレッド編その1)

2015/03/18

Android RxAndroid

observeOn(…)とsubscribeOn(…)正しく使えてますか

以下の2つのコードはobserveOn(…)とsubscribOn(…)の位置が異なります。 表示の問題上、ブロック内での実装は省略しています。 どのポイントがどのスレッドで動くか確認します。

コードその1

Observable.create(new Observable.OnSubscribe<Object>() {
    @Override
    public void call(Subscriber<? super Object> subscriber) {
        // ポイント1
    }
})
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.map(new Func1<Object, Object>() {
    @Override
    public Object call(Object o) {
        // ポイント2
        return o;
    }
})
.subscribe(new Observer<Object>() {
    @Override
    public void onCompleted() {
        // ポイント3
    }
    @Override
    public void onError(Throwable e) {
    }
    @Override
    public void onNext(Object o) {
    }
});

コードその2

Observable.create(new Observable.OnSubscribe<Object>() {
    @Override
    public void call(Subscriber<? super Object> subscriber) {
        // ポイント1
    }
})
.map(new Func1<Object, Object>() {
    @Override
    public Object call(Object o) {
        // ポイント2
        return o;
    }
})
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Observer<Object>() {
    @Override
    public void onCompleted() {
        // ポイント3
    }
    @Override
    public void onError(Throwable e) {
    }
    @Override
    public void onNext(Object o) {
    }
});

それぞれのポイントへ以下のようにログを仕込みます。

Log.d("Thread", String.format("%s threadId:%d","OnSubscribe", Thread.currentThread().getId()));
Log.d("Thread", String.format("%s threadId:%d","map", Thread.currentThread().getId()));
Log.d("Thread", String.format("%s threadId:%d","onCompleted", Thread.currentThread().getId()));

コードその1の場合はmapがUIスレッドで実行されているのに対し

D/Thread﹕ OnSubscribe threadId:199
D/Thread﹕ map threadId:1
D/Thread﹕ onCompleted threadId:1

コードその2の場合はmapがonSubscribe(…)のスレッドで実行されています。

D/Thread﹕ OnSubscribe threadId:201
D/Thread﹕ map threadId:201
D/Thread﹕ onCompleted threadId:1

mapで重い処理を走らせてANRを発生させないためにも、BuildConfig.DEBUGがtrueの時はエラーを投げるよう工夫しましょう。(戒め

JacksonでJsonから特定の要素をデシリアライズしたくない場合のメモ

2015/03/17

Android Jackson

以下のようなJsonとデシリアライズ先クラスがありlistの要素だけデシリアライズしたくない場合にtitleフィールドをStringに変更するのは間違いです。

Can not deserialize instance of java.lang.String

Data.java

public class Data {
    String id;
    List<Title> title;
    public static class Title {
        String title;
    }
}

sample.json

{
    "id": "12345",
    "list": [
        {
            "title": "ふしぎの海のナディア"
        },
        {
            "title": "ガンバの冒険"
        }
    ]
}

Data.javaクラスのtitleフィールドをObjectにすると後でデシリアライズできる。

Data.java

public class Data {
    String id;
    Object title;
}

FrameLayoutでCanvasを弄る

2013/07/02

Android

Viewが持っているCanvasのdrawBitmapを使ってスタンプもどきの実装をしていて詰んだのでメモ。 ViewにBitmapを貼ったり線を引きたい場合はonDrawをOverrideして貼り付けてやればいい。


    @Override
    protected void onDraw(Canvas canvas) {
        canvas.drawColor(Color.BLUE);
        canvas.drawBitmap(mStampBitmap, onTouchCurrent.x, onTouchCurrent.y, null);
    }

FrameLayoutも同様にonDrawでいけるかなと思ってやってみたが最初の1度だけでonTouchListener内でinvalidate()を呼んでもだめだった。 他にdraw(Canvas canvas)という関数もあったがこれもだめであった。 dispatchDraw(Canvas canvas)はinvalidate()後に実行されるようなのでこちらで描画処理をすることにした。

    @Override
    protected void dispatchDraw(Canvas canvas) {
        // move 10pixel
        canvas.translate(10,10);
        super.dispatchDraw(canvas);
    }

ndk-buildでドハマリ

2013/05/27

Android NDK

すごくしょうもないことで時間を消費してしまった。 こんなエラー。

05-27 23:11:16.316  17783-17783/com.epy0n0ff.gles4image.sample E/AndroidRuntime: FATAL EXCEPTION: main
        java.lang.ExceptionInInitializerError
        at java.lang.Class.newInstanceImpl(Native Method)
        at java.lang.Class.newInstance(Class.java:1319)
        at android.app.Instrumentation.newActivity(Instrumentation.java:1026)
        at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:1882)
        at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:1992)
        at android.app.ActivityThread.access$600(ActivityThread.java:127)
        at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1158)
        at android.os.Handler.dispatchMessage(Handler.java:99)
        at android.os.Looper.loop(Looper.java:137)
        at android.app.ActivityThread.main(ActivityThread.java:4511)
        at java.lang.reflect.Method.invokeNative(Native Method)
        at java.lang.reflect.Method.invoke(Method.java:511)
        at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:976)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:743)
        at dalvik.system.NativeStart.main(Native Method)
        Caused by: java.lang.UnsatisfiedLinkError: Couldn't load libgles4image: findLibrary returned null
        at java.lang.Runtime.loadLibrary(Runtime.java:365)
        at java.lang.System.loadLibrary(System.java:535)
        at com.epy0n0ff.gles4image.sample.MainActivity.<clinit>(MainActivity.java:17)

libgles4imageがね~ぞというエラーだけどndk-buildは成功してlibsにアーキテクチャ別のlibgles4imageが入っているので 間違いないはず…。生成物のapkを念の為にzipinfoで調べてみた。

$ zipinfo OpenGLES4ImageSample.apk
-rw----     2.0 fat      552 bl defN 27-May-13 23:11 res/layout/main.xml
-rw----     2.0 fat     1648 bl defN 27-May-13 23:11 AndroidManifest.xml
-rw----     1.0 fat     1292 b- stor 27-May-13 01:31 resources.arsc
-rw----     1.0 fat     9193 b- stor 26-May-13 16:52 res/drawable-hdpi/ic_launcher.png
-rw----     1.0 fat     2658 b- stor 26-May-13 16:52 res/drawable-ldpi/ic_launcher.png
-rw----     1.0 fat     5057 b- stor 26-May-13 16:52 res/drawable-mdpi/ic_launcher.png
-rw----     1.0 fat    14068 b- stor 26-May-13 16:52 res/drawable-xhdpi/ic_launcher.png
-rw----     2.0 fat   319884 bl defN 27-May-13 23:11 classes.dex
-rw----     2.0 fat    13432 bl defN 27-May-13 22:46 lib/armeabi/libgles4image.so
-rw----     2.0 fat    13436 bl defN 27-May-13 22:46 lib/armeabi-v7a/libgles4image.so
-rw----     2.0 fat     5312 bl defN 27-May-13 22:46 lib/mips/libgles4image.so
-rw----     2.0 fat     5208 bl defN 27-May-13 22:46 lib/x86/libgles4image.so
-rw----     2.0 fat     1070 bl defN 27-May-13 23:11 META-INF/MANIFEST.MF
-rw----     2.0 fat     1123 bl defN 27-May-13 23:11 META-INF/CERT.SF
-rw----     2.0 fat      776 bl defN 27-May-13 23:11 META-INF/CERT.RSA
16 files, 395225 bytes uncompressed, 181134 bytes compressed:  54.2%

確かにlibgles4image.soが存在している。俺に間違いはない。 はずだった…。 Android.mkファイルを見てみよう。

$ cat Android.mk
LOCAL_PATH:= $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE    := gles4image
...

そう、loadLibraryの引数はLOCAL_MODULEで与えたstringを指定してやらないとだめなんだ…。

static {
    System.loadLibrary("libgles4image");
}

な記述を

static {
    System.loadLibrary("gles4image");
}

に修正してやるとExceptionは発生しなくなった。 こういうつまらないミスを減らすためにもbuild scriptでサクッとチェックいれる体制を 作っていかねばならんなーと痛感した一日であった。

Android Studio触ってみる

2013/05/16

Android Android Studio

標準でgitとgithub入ってるみたい。

VCSからインポートでプロジェクト作るとうまくいくんだけど New Projectからいくとこの状態で止まってしまう。

中身はIntelliJそのものなんで安定したらこっちで開発進めて行きたい。

EGit + Eclipse + git flow

2013/05/14

Android Eclipse git git flow

既存リポジトリにdevelopブランチを作成.

git branch develop
git push origin develop

EGitからURI指定でインポート後にgit flowの初期化を行う.

git checkout -b develop origin/develop
git config --global --add marge.ff false
git flow init

さあ,開発の時間だ.

git flow feature start camera_filter