かずきのBlog@hatena

日本マイクロソフトに勤めています。XAML + C#の組み合わせをメインに、たまにASP.NETやJavaなどの.NET系以外のことも書いています。掲載内容は個人の見解であり、所属する企業を代表するものではありません。

Loaderを使ったデータの読み込み

最近のAndroidでのデータの読み込みはLoaderというものを使うらしいです。ちょっとやってみました。

オレオレ仕様にカスタムできるLoader

カーソル前提のLoaderもあるんですが、自前のデータ読み込み処理を書けるAsyncTaskLoaderというのがあるのでそれを使うのが一番柔軟っぽいです。

AsyncTaskLoaderは、継承してloadInBackgroundメソッドを実装するだけの簡単クラスです。例えばPersonという以下のようなクラスがあるとします。

// めんどいのでActivityの内部クラスにした
public static class Person {
    private String name;
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    @Override
    public String toString() {
        return this.name;
    }
}

適当にこのクラスを生成する実装は以下のようになります。画面にeditTextという名前のEditTextを置いていて、そいつから取得したテキストをベースに先ほどのPersonクラスを組み立てては、Listに突っ込んでいます。時間のかかる処理っぽく少しSleepも入れています。

final Random r = new Random();
final String input = this.editText.getText().toString();
AsyncTaskLoader<List<Person>> loader = new AsyncTaskLoader<List<Person>>(this) {
    @Override
    public List<Person> loadInBackground() {
        Log.d("MyActivity", "loadInBackground");
        int count = r.nextInt(100);
        List<Person> result = new ArrayList<Person>();
        for (int i = 0; i < count; i++) {
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            Person p = new Person();
            p.setName(input + " " + i);
            result.add(p);
        }
        return result;
    }
};

Loaderを使うためのコールバックインターフェース

Javaってコールバック地獄でめんどくさいですよね…。Loaderをバックグラウンドで実行するために、以下のインターフェースを実装しないといけません。これは先ほど示したAsyncTaskLoaderクラスとは別ものです。LoaderManager.LoaderCallbacksというインターフェースになります。Tは、AsyncTaskLoaderのTと合わせて使います。

LoaderCallbacksには、以下の3つのメソッドがあります。

  • onCreateLoader: Loaderを作るメソッド
  • onLoadFinished: Loaderの処理が終わった後に呼ばれるメソッド
  • onLoaderReset: リセットしたときに呼ばれるメソッド

ActivityにLoaderCallbacksインターフェースを実装するサンプルをよく見るのでここでもそうしました。

まず、onCreateLoaderメソッドです。こいつはさっきのAsyncTaskLoaderを作成しています。 そして、Loaderの処理をキックするforceLoadメソッドを呼んでいます(大事)

@Override
public Loader<List<Person>> onCreateLoader(int id, Bundle args) {
    Log.d("MyActivity", "onCreateLoader");
    Random r = new Random();
    final String input = this.editText.getText().toString();
    AsyncTaskLoader<List<Person>> loader = new AsyncTaskLoader<List<Person>>(this) {
        @Override
        public List<Person> loadInBackground() {
            Log.d("MyActivity", "loadInBackground");
            int count = r.nextInt(100);
            List<Person> result = new ArrayList<Person>();
            for (int i = 0; i < count; i++) {
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                Person p = new Person();
                p.setName(input + " " + i);
                result.add(p);
            }
            return result;
        }
    };
    loader.forceLoad();
    return loader;
}

そして、onLoadFinishedメソッドで読み込みが終わったときの処理を書きます。ここではArrayAdapterにデータをセットしています。

@Override
public void onLoadFinished(Loader<List<Person>> loader, List<Person> data) {
    Log.d("MyActivity", "onLoadFinished");
    this.peopleAdapter.clear();
    this.peopleAdapter.addAll(data);
}
@Override
public void onLoaderReset(Loader<List<Person>> loader) {
}

ついでに、何もしないonLoaderResetも書いておきました。

コード全体

レイアウトファイル。EditTextとListView置いただけの画面

<?xml version="1.0" encoding="utf-8"?>

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical" android:layout_width="match_parent"
    android:layout_height="match_parent">

    <EditText
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:id="@+id/editText" />

    <ListView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:id="@+id/listView" />
</LinearLayout>

Activityに全部処理かいてます///

package com.example.kazuki.myapplication;

import android.app.Activity;
import android.app.LoaderManager;
import android.content.AsyncTaskLoader;
import android.content.Loader;
import android.os.Bundle;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.widget.ArrayAdapter;
import android.widget.EditText;
import android.widget.ListView;

import java.util.ArrayList;
import java.util.List;
import java.util.Random;


public class MyActivity extends Activity implements LoaderManager.LoaderCallbacks<List<MyActivity.Person>>  {

    private EditText editText;
    private ListView listView;
    private ArrayAdapter<Person> peopleAdapter;


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_my);
        this.editText = (EditText) this.findViewById(R.id.editText);
        this.listView = (ListView) this.findViewById(R.id.listView);
        this.peopleAdapter = new ArrayAdapter<Person>(this, android.R.layout.simple_list_item_1);
        this.listView.setAdapter(this.peopleAdapter);
    }


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

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        // Handle action bar item clicks here. The action bar will
        // automatically handle clicks on the Home/Up button, so long
        // as you specify a parent activity in AndroidManifest.xml.
        int id = item.getItemId();
        if (id == R.id.action_settings) {
            Log.d("MyActivity", "onOptionsItemSelected");
            this.getLoaderManager().restartLoader(0, null, this);
            return true;
        }
        return super.onOptionsItemSelected(item);
    }


    @Override
    public Loader<List<Person>> onCreateLoader(int id, Bundle args) {
        Log.d("MyActivity", "onCreateLoader");
        final Random r = new Random();
        final String input = this.editText.getText().toString();
        AsyncTaskLoader<List<Person>> loader = new AsyncTaskLoader<List<Person>>(this) {
            @Override
            public List<Person> loadInBackground() {
                Log.d("MyActivity", "loadInBackground");
                int count = r.nextInt(100);
                List<Person> result = new ArrayList<Person>();
                for (int i = 0; i < count; i++) {
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    Person p = new Person();
                    p.setName(input + " " + i);
                    result.add(p);
                }
                return result;
            }
        };
        loader.forceLoad();
        return loader;
    }

    @Override
    public void onLoadFinished(Loader<List<Person>> loader, List<Person> data) {
        Log.d("MyActivity", "onLoadFinished");
        this.peopleAdapter.clear();
        this.peopleAdapter.addAll(data);
    }

    @Override
    public void onLoaderReset(Loader<List<Person>> loader) {

    }


    public static class Person {
        private String name;

        public String getName() {
            return name;
        }

        public void setName(String name) {
            this.name = name;
        }

        @Override
        public String toString() {
            return this.name;
        }
    }
}