2013年7月21日日曜日

Android2.3.3でAsyncTaskLoaderを使おう

Android2.3.3でアプリを開発しています。

バックグラウンドで通信して、取ってきたデータをキャッシュしてユーザに提示するような、
よくあるアプリケーションを制作しています。
実際にはアップデートを担当しています。

現在、アプリのバックグラウンド処理にはAsyncTaskを利用しています。
AsyncTaskについて調べているとAndroid3.0からはAsyncTaskは非推奨になっていて、
代わりにAsyncTaskLoaderなるものが登場していることを知った。

http://www.srv-shinra.com/wordpress/?p=308

を見てもわかるようにそういうことだそうです。
APIレベルを上げてやればいいのですが、Android.2.3.3のユーザも結構いるため、
そう簡単に切り捨てることはできません。

でも、AsyncTaskはcancel処理あたりに問題があるという記事をチラホラみかけます。
例えば、

GCMのIntentServiceでAsyncTaskを実行したらsending message to a Handler on a dead threadの警告が出て悩まされる


に取り上げられていることが発生するみたいです。
回避するためにはThreadを使ったりするみたいですが、それも面倒です。

だから、Android2.3.3でもAsyncTaskLoaderを使いたいという結論に至りました。
でも、ネット上で実装している人の情報をあまり見つけることができません。
調べ方が悪いのか。。。

でも、SupportLibraryにLoaderManagerとかあるのになんでActivityにgetLoaderManagerメソッドがないの??

と思っていろいろ調べていたら、FragmentActivityに実装されていました。
なるほど、もうActivityで作るのは時代遅れなのですね。。

でも、ActivityにないLoaderManagerさえ自前で自作してしまえば
ActivityでもAsyncTaskLoaderが使えるようです。

Androidソースコードを見ると、LoaderManagerはAbstractなので実際に実装されているクラスは、
LoaderManagerImplというクラスがありました。

それを参考にしてLoaderManagerだけ実装してやれば実現できそうです。
HashMapで管理するLoaderManagerを実装して簡単に試してみたらできました。

でも、もうActivityはもう時代遅れなのか?!。。。

Androidソースを除いてたらSparseArrayなるクラスがあることを知った。

「AndroidのSparseArrayは本当に速いのか測定してみた」
http://thinking-megane.blogspot.jp/2012/06/androidsparsearray.html

ここを見ると以降SparseArrayを使おうと思いました。

結局、今回の調べごとで勉強したことは、
・FragmentActivityをつけば問題は解決されることと
・SparseArrayはHashMapより速い
でした。

収穫収穫!!

以下は実装途中ですが、こんな感じでLoaderManagerを実装すればActivity(Android2.3.3)でもAsyncTaskLoaderが使えるということです。
--------
package com.example.asyncloadertest;

import java.io.FileDescriptor;
import java.io.IOException;
import java.io.PrintWriter;

import org.apache.http.HttpResponse;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.util.EntityUtils;

import android.content.Context;
import android.support.v4.content.AsyncTaskLoader;

public class TestLoader extends AsyncTaskLoader<String> {

    String result;

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

    @Override
    public String loadInBackground() {
DefaultHttpClient client = new DefaultHttpClient();
HttpGet get = new HttpGet("http://www.yahoo.co.jp/");
try {
    HttpResponse execute = client.execute(get);
    String res = EntityUtils.toString(execute.getEntity());
    return res;
} catch (ClientProtocolException e) {
    e.printStackTrace();
} catch (IOException e) {
    e.printStackTrace();
}
return null;
    }

    @Override
    public void deliverResult(String data) {
if (isReset()) {
    if (this.result != null) {
this.result = null;
    }
    return;
}

this.result = data;

if (isStarted()) {
    super.deliverResult(data);
}
    }

    @Override
    protected void onStartLoading() {
if (this.result != null) {
    deliverResult(this.result);
}
if (takeContentChanged() || this.result == null) {
    // これをやっておくとonCreateLoaderで開始処理をしなくてよくなる
    forceLoad();
}
    }

    @Override
    protected void onStopLoading() {
super.onStopLoading();
cancelLoad();
    }

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

    @Override
    public void dump(String prefix, FileDescriptor fd, PrintWriter writer,
    String[] args) {
super.dump(prefix, fd, writer, args);
writer.print(prefix);
writer.print("result=");
writer.println(this.result);
    }

}
--------
package com.example.asyncloadertest;

import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.HashMap;

import android.os.Bundle;
import android.app.Activity;
import android.support.v4.app.LoaderManager;
import android.support.v4.app.LoaderManager.LoaderCallbacks;
import android.support.v4.content.Loader;
import android.util.Log;
import android.view.Menu;
import android.widget.TextView;

public class AsyncTascLoaderTestActivity extends Activity implements LoaderCallbacks<String> {

    // Yahooをロードする
    public static final int TASK_001 = 0x00001;
    
    LoaderManager loaderManager = new LoaderManager() {
        HashMap<Integer, Loader<String>> loaders = new HashMap<Integer, Loader<String>>();
        
        @Override
        public <String> Loader<String> restartLoader(int arg0, Bundle arg1,
        LoaderCallbacks<String> arg2) {
    return null;
        }
        
        /**
         * ローダの初期化
         */
        @Override
        public <String> Loader<String> initLoader(int arg0, Bundle arg1,
        LoaderCallbacks<String> arg2) {
            Log.d("Called","initLoader");
    Loader<String> loader = arg2.onCreateLoader(arg0, arg1);
    // do initialize...
    loaders.put(arg0, (Loader<String>) loader);
    return loader;
        }
        
        @Override
        public <String> Loader<String> getLoader(int arg0) {
            Log.d("Called","getLoader");
    return (Loader<String>) loaders.get(arg0);
        }
        
        @Override
        public void dump(String arg0, FileDescriptor arg1, PrintWriter arg2,
        String[] arg3) {
            // nothing
        }
        
        /**
         * 破棄
         * これより前にロードを止めるべき
         */
        @Override
        public void destroyLoader(int arg0) {
            Log.d("Called","destroyLoader");
            loaders.remove(arg0);
        }
    };
    // TextView
    TextView view;
    
    @Override
    protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_async_tasc_loader_test);
// ローダー初期化
loaderManager.initLoader(TASK_001, null, this);
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.async_tasc_loader_test, menu);
return true;
    }

    /**
     * ローダー作成
     */
    @Override
    public Loader<String> onCreateLoader(int arg0, Bundle arg1) {
        Log.d("Called","onCreateLoader");
// Create
TestLoader loader = new TestLoader(this);
return loader;
    }

    /**
     * バックグラウンド処理が終わったら
     */
    @Override
    public void onLoadFinished(Loader<String> arg0, String arg1) {
        Log.d("Called","onLoadFinished");
view = (TextView) findViewById(R.id.view);
if (arg0 != null && arg0.getId() == TASK_001) {
    view.setText(arg1);
}
    }

    /**
     * 処理がリセットされたら
     */
    @Override
    public void onLoaderReset(Loader<String> arg0) {
if (view != null)
    view.setText("Loader is reset");
    }


}
--------

0 件のコメント:

コメントを投稿