LoginSignup
27
27

More than 5 years have passed since last update.

ParseSDK のトラップを集めたページ

Last updated at Posted at 2014-05-16

先日、potatotips #7 にて、Parse と上手に付き合うための tips を発表させていただきました。

この記事では、ParseSDK を使う上で気をつけていないとハマるポイントを、もう少し掘り下げつつ、発表には無かったものも含めてお送りしようと思います。

1. 非同期処理

1-1. **InBackground はメインスレッドをブロックする

名前からして、メインスレッドとは違うスレッドで非同期に処理をしてくれて、結果をコールバックにかえしてくれるように見えますが、メインスレッドをブロックするところがあるので Stop the World します。

内部的にはリクエストをキューに積んで逐次処理でさばいているようですが…

1-2. **InBackground のコールバックは Context の生死にかかわらず呼ばれる

よくあるカンタンな実装として、以下の様なものがあると思います。


public class SomeActivity extends Activity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        ParseQuery<ParseObject> query = ParseQuery.getQuery("SomeData");
        query.findInBackground(new FindCallback<ParseObject>() {
            @Override
            public void done(List<ParseObject> result, ParseException e) {

            }
        });
    }
}

Activity が死んだら、コールバックは呼ばれてほしくないですが、Activity の死活管理をしていないので、コールバックが返ってきます。
内部的には、Bolts フレームワークと同じものが使われています。

1-3. 同期呼び出し API は別のスレッドでリクエストを投げている

リクエストを同期的に実行する API も、実装としてはリクエストの実行は別のスレッドでやっています。
つまり、同期呼び出し API といいつつ、単に別のスレッドの結果を待つだけのことしかしていません。

同期呼び出し API を実行しているスレッドに割り込み等が発生すると、待っているところがInterruptedExceptionを吐き出します。
SDK では、このInterrutedExceptionをキャッチして、RuntimeExceptionに翻訳してスローするようになっています。

たとえば、AbstractThreadedSyncAdapterを使ってデータを同期するようなことを考えると、同期のキャンセルが発生した時に、AbstractThreadedSyncAdapterのスレッドが途中で終わらせられるので、上記のような状況が発生します。

1-4. リトライ処理でクラッシュする

わりと古い Apache HttpClient を使用しているようです。リクエストのリトライで、SingleClientConnManagerがうまく使えずIllegalStateExceptionとなる事があり、クラッシュの原因となります。

1-5. エラーコード表にないコードのエラーが帰ってくることがある

Parse も知り得ない謎のコードもあるようです。

2. オブジェクトの操作

2-1. レスポンスデータの取得の都度ロック取得があり、かつデータの存在確認をしている。

例えば、キー名を指定してデータを取得する際には、以下の様なコードを書きます。


ParseObject object = // ...

String name = object.getString("name");

ParseObject#get**(String)のメソッド名からして、オンメモリのデータをすぐに返してくれそうなイメージが有りますが、並列性のため、アクセスの度にロックを取得します。

ロックの取得の上で、メモリにデータが有るかどうかも確認しますが、この確認にもロックの取得が行われます。

ロックのコストが嵩んでいるため、そこそこの時間を要することになります。

2-2. データ構造以上に様々な情報を持つのでメモリを割りとよく食う

ParseObjectは内部的に、単なるデータを保持するMap構造以外にも、どのような操作を実行したかを記録するキューがあったり、必要なTaskを保持するキューがあったりと、デコンパイルすると本当にいろいろなデータを保持していることがわかります。

ParseObjectを拡張してデータ構造とモデルの兼用なクラスを以下のように作ると、サーバから取得したデータやその他のデータ構造の上に個々のデータのフィールドも持つことになるので、メモリをバクバク食います。

public void MyData extends ParseObject implements Parcelable {
    private String mObjectId;
    private String mName;
    private Date mCreatedAt;
    private Date mUpdatedAt;
    // ...
}

仮に、ParseObjectを拡張せず、自分でデータ構造の型(ex. SomeData)を定義した場合、List<ParseObject>からList<SomeData>にマップする処理を走らせると、結構な回数の GC が走っているのが見て取れると思います。

27
27
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
27
27