It’s now or never

IT系の技術ブログです。気になったこと、勉強したことを備忘録的にまとめて行きます。

【Android】GCMを使ってみる

GCM(Google Cloud Messaging for Android)は、

開発者がサーバからAndroidデバイス上のAndroidアプリケーションに対して、
データを送信できるようにする仕組みです。

Developers Consoleの準備

Developers Consoleにて、アプリケーションを登録してAPI実行に必要なKey情報を取得します。

Google Developers Console

1.任意のアプリケーションを作成する

アプリケーションを作成すると、APIコンソールのDashboardProject Numberが表示されます。
この数字は、後ほど作成するGCM受信アプリにて、登録ID(registration ID)取得の際に必要になるので覚えておきます。

f:id:inon29:20140407215605j:plain

2.GCMの設定を有効にする

APIs & authを選択し、表示されるリストの中からGoogle Cloud Messaging for Androidを探してONにします。

f:id:inon29:20140407215837p:plain

3. API Keyを発行する

API Keyを作成するには、左メニューのAPIs & auth > credentialボタンを押下し、
Public API access領域にあるCREATE NEW KEYボタンを押下します 。

f:id:inon29:20140407220058p:plain

以下のように作成するキーを選択するダイアログが表示されるます。
今回は、Pushの確認テストとしてサーバーからリクエストを送るため、Server keyを選択します。

f:id:inon29:20140409212815p:plain

Server keyボタンを押下すると、アクセスを許可するIPアドレスを設定するためのダイアログが表示されるので、
アクセスを許可するグローバルIPを入力します。

f:id:inon29:20140407220350p:plain

処理が正常に完了すると下記のようにAPI keyが発行されます。
このKeyを使用して、サーバー側からのGCMのメッセージを送信します。(後述)

f:id:inon29:20140409213514p:plain

Androidアプリの準備

GCMを受信するためのAndroidアプリケーションを実装します。

1.GSM用のライブラリをeclipseにimportする

Android SDK Managerを起動し、Extras配下のGoogle Play servicesを選択し、
importします。

f:id:inon29:20140410201849p:plain

importが完了すると<ANDROID_HOME>/extras/google/google_play_services/libproject/配下に、google-play-services_libというディレクトリがDLされているので、
これをAndroid Project From Exting Code形式でeclipseにimportします。

次に、GCMを受信する為のプロジェクトを新規で作成し、
Projectを選択 > 右クリックでPropertiesを選択 > Androidから、
先ほど、eclipseにimportしたライブラリプロジェクトを追加します。

これで、Android プロジェクトでGCMクラスを使用するための準備は完了です。

2. GCM受信の準備

GCMの通知を受け取るためのReceiverクラスを実装します。

GCMの受信処理中に端末がスリープしないように、ReceiverクラスはWakefulBroadcastReceiverを継承しています。

WakefulBroadcastReceiverは、処理を実行している間は、デバイスがスリープしないことが保証できる特殊なReceiverクラスです。

public  class GcmBroadcastReceiver extends WakefulBroadcastReceiver {

    public GcmBroadcastReceiver() {
        super();
    }

    @Override
    public void onReceive(Context context, Intent intent) {
        // GCMを受信 メッセージ受信用のサービスを起動する
        ComponentName comp = 
                new ComponentName(context.getPackageName(), GcmIntentService.class.getName());
              startWakefulService(context, (intent.setComponent(comp)));
              setResultCode(Activity.RESULT_OK);
    }
}

Receiverクラスが受け取ったGCMのインテントを処理するServiceクラスを作成します。

public class GcmIntentService extends IntentService {
    private static final String TAG = "GcmIntentService";
    
    // 引数なしのコンストラクタを作成しないと例外で落ちてしまった
    public GcmIntentService() {
        super(GcmIntentService.class.getName());
    }
    
    public GcmIntentService(String name) {
        super(name);
    }

    @Override
    protected void onHandleIntent(Intent intent) {
        Bundle extras = intent.getExtras();

        // intentからGCMのメッセージを取得する
        GoogleCloudMessaging gcm = GoogleCloudMessaging.getInstance(this);
        String messageType = gcm.getMessageType(intent);
 
        // GCMのメッセージをタイプ別にフィルタリングする。
        //  将来的に拡張されることを考慮し、存在しないタイプを無視するようにする
        if (!extras.isEmpty()) {
            if (GoogleCloudMessaging.MESSAGE_TYPE_SEND_ERROR.equals(messageType)) { 
                // エラー
                Log.d(TAG,"messageType: " + messageType + ",send error:" + extras.toString());
            } else if (GoogleCloudMessaging.MESSAGE_TYPE_DELETED.equals(messageType)) {
                // サーバー側でメッセージを削除された
                Log.d(TAG,"messageType: " + messageType + ",message deleted:" + extras.toString());
            } else if (GoogleCloudMessaging.MESSAGE_TYPE_MESSAGE.equals(messageType)) { 
                // メッセージ受信
                Log.d(TAG,"messageType: " + messageType + ",received message:" + extras.toString());
            }
        }
        // 処理の終了をReceiverに通知し、ロックを解放する
        GcmBroadcastReceiver.completeWakefulIntent(intent);
    }
}

3. AndroidManifestを更新する

GCMを受け取るためのPermissionおよび、
上記で実装したメッセージ受信するReceiverクラス、
メッセージを処理するServiceクラスの定義をAndroidManifestに記載します。

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.test.gcmtest"
    android:versionCode="1"
    android:versionName="1.0" >

    ・・・

    <!-- Permissionの宣言 -->
    <permission android:name="com.test.gcmtest.permission.C2D_MESSAGE" android:protectionLevel="signature" />
    <uses-permission android:name="com.test.gcmtest.permission.C2D_MESSAGE" /> 
    <uses-permission android:name="com.google.android.c2dm.permission.RECEIVE" />
    <uses-permission android:name="android.permission.WAKE_LOCK"/>

    <application
        android:allowBackup="true"
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name"
        android:theme="@style/AppTheme" >

        ・・・        
        <!-- Receiverクラスの宣言 -->
        <receiver
            android:name=".GcmBroadcastReceiver"
            android:permission="com.google.android.c2dm.permission.SEND" >
            <intent-filter>
                <action android:name="com.google.android.c2dm.intent.RECEIVE" />
                <category android:name="com.test.gcmtest" />
            </intent-filter>
        </receiver>
        <!-- Serviceクラスの宣言 -->
        <service android:name="com.test.gcmtest.GcmIntentService" />
    </application>

・・・

これで、受信側の準備は完了です。

3. GCM送信の準備

次に、サーバーからGCMを送信するために必要なregistration_idを取得するクラスを実装します。

下記サンプルでは、Activity起動時に非同期でregist処理を行っています。
onPostExecute()で取得できる文字列がregistration_idです。


gcm.register()の引数に渡すIDには、Developer Consolに表示されていたProject Numberを指定します。

public class MainActivity extends Activity {
    private GoogleCloudMessaging gcm;
    private Context context;
 
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        context = getApplicationContext();
        
        // GCMクラスを作成して非同期でregist処理を行う。
        gcm = GoogleCloudMessaging.getInstance(this);
        registerInBackground();
    }
 
    private void registerInBackground() {
        new AsyncTask<Void, Void, String>() {
            @Override
            protected String doInBackground(Void... params) {
                String msg = "";
                try {
                    if (gcm == null) {
                        gcm = GoogleCloudMessaging.getInstance(context);
                    }
                    String regid = gcm.register("<app_id>");
                    msg = regid;
                    Log.d("tag", "Device registered, registration ID=" + msg);
                } catch (IOException ex) {
                    msg = "Error :" + ex.getMessage();
                }
                return msg;
            }
 
            @Override
            protected void onPostExecute(String msg) {
                // registration IDを取得
                // 従来であれば、ここから送信サーバーへregistration IDを送信するような流れになる
                Log.d("tag", msg); 
            }
        }.execute(null, null, null);
    }
}

GCMのテスト送信

最後に、サーバーからのGCMテスト送信を行います。

下記、curlコマンドでは、
Developers Consoleの準備手順で取得したAPI Keyと上記で取得したregistration IDを使って、
作成したAndroidアプリへ”Hello”という文字列を送信しています。

curl --header "Authorization: key=<API Key>" --header Content-Type:"application/json" https://android.googleapis.com/gcm/send -d "{\"registration_ids\":[\"<registration ID>\"],\"data\":{\"message\":\"Hello\"}}"

参考

http://zatomiya.blogspot.jp/2013/05/androidnotification.html
http://dev.classmethod.jp/smartphone/android/gcm/
http://developer.android.com/google/gcm/gs.html
http://developer.android.com/google/gcm/client.html

【Android】コンテンツプロバイダ(ContentProvider)を使ってみる

コンテンツプロバイダは、Androidのアプリケーションが管理しているリソースを 他アプリケーションから参照するための仕組みです。

例えば、連絡先アプリなど他アプリケーションから活用できるデータを持つアプリケーションが、
その他アプリケーションへデータを提供する際に使用されます。

今回は、SQLiteデータベースの情報を取得するサンプルを作成しました。

SQLiteデータベースへアクセスするためのクラスを作成

コンテンツプロバイダのデータ提供元となるデータベースを作成します。

testdbという名前のDBにfruitというテーブルを作成しています。

public class ShareDbHelpler extends SQLiteOpenHelper {

    private static final String DB_NAME = "testdb";
    private static final int DB_VERSION = 1;

    // テーブル情報を定義
    public static final String TABLE_NAME = "fruit";
    public static final String COL_NAME = "firstName";

    // カラムには、必ず「_id」という主キーをAUTOINCREMENTで作成する必要がある
    private static final String STRING_CREATE =
            "CREATE TABLE " + TABLE_NAME + " (_id INTEGER PRIMARY KEY AUTOINCREMENT, "
            +COL_NAME+" TEXT);";

    public ShareDbHelpler(Context context) {
        super(context, DB_NAME, null, DB_VERSION);
    }

    @Override
    public void onCreate(SQLiteDatabase sqLiteDatabase) {
        // データベースのテーブルを作成する
        sqLiteDatabase.execSQL(STRING_CREATE);

        // データベースにサンプル値を挿入
        ContentValues cv = new ContentValues(1);
        cv.put(COL_NAME, "Apple");
        sqLiteDatabase.insert(TABLE_NAME, null, cv);

        cv = new ContentValues(3);
        cv.put(COL_NAME, "Orange");
        sqLiteDatabase.insert(TABLE_NAME, null, cv);

        cv = new ContentValues(3);
        cv.put(COL_NAME, "Banana");
        sqLiteDatabase.insert(TABLE_NAME, null, cv);
    }

    @Override
    public void onUpgrade(SQLiteDatabase sqLiteDatabase, int i, int i2) {
        // DBに変更があった場合は、とりあえずテーブルを削除する
        sqLiteDatabase.execSQL("DROP TABLE IF EXISTS " + TABLE_NAME);
    }
}

コンテンツプロバイダを継承したクラスを作成

コンテンツプロバイダを提供するためにContentProviderのサブクラスを定義します。
ContentProviderを継承したクラスは、以下の6つのメソッドを実装する必要があります。

  • query()
  • insert()
  • update()
  • delete()
  • getType()
  • onCreate()

また、 サブクラスには、ContentProviderへアクセスするためのURI(content://~)および、アクセス可能なカラム名を定数として定義します。

ContentProviderがアクセスする先がデータベースの場合、定義するカラム名とデータベースのカラム名を同じにするのが一般的です。

カラム名には、 レコードのIDとして整数型の_id(定数ID)という名前のカラムを必ず含めるようにしなくてはなりません。
※他に全レコードで一意になるであろうフィールド ( URL など ) を持っていても含めなくてはならない
さらに、SQLiteデータベースを使用する場合は、この
ID フィールドをINTEGER PRIMARY KEY AUTOINCREMENTにしなくてはいけません。

以下は、frauitテーブルへアクセスするためのContentProviderのサンプルです。

public class TestProvider extends ContentProvider {

    // コンテンツプロバイダのURIを定義
    public static final Uri CONTENT_URI =
            Uri.parse("content://com.inon.shareddb.testsprovider/frauit");

    // アクセスが可能なカラムを定義
    public static final class Columns {
        public static final String _ID = "_id";
        public static final String NAME = "name";
    }

    /* URI マッチング */
    // 全行取得とidでの取得を行えるよう、UriMatcherを使用する
    private static final int FRAUIT = 1;
    private static final int FRAUIT_ID = 2;

    private static final UriMatcher matcher = new UriMatcher(UriMatcher.NO_MATCH);
    static {
        matcher.addURI(CONTENT_URI.getAuthority(), "frauit", FRAUIT);
        matcher.addURI(CONTENT_URI.getAuthority(), "frauit/#", FRAUIT_ID);
    }

    SQLiteDatabase db;

    @Override
    public boolean onCreate() {
        // 接続DBの作成
        ShareDbHelpler helpler = new ShareDbHelpler(getContext());
        db = helpler.getWritableDatabase();
        return false;
    }

    @Override
    public Cursor query(Uri uri, String[] projection, String selection, String[]
            selectionArgs, String sortOrder) {
        int result = matcher.match(uri);

        // 指定のURIが正しければ、取得結果(Cursor)を返却する
        switch (result) {
            case FRAUIT:
                return db.query(ShareDbHelpler.TABLE_NAME, projection, selection,
                        selectionArgs, null, null, sortOrder);
            case FRAUIT_ID:
                return db.query(ShareDbHelpler.TABLE_NAME, projection, "_ID = ?",
                        new String[]{uri.getLastPathSegment()}, null, null, sortOrder);
            default:
                return null;
        }
    }

    @Override
    public String getType(Uri uri) {
        return null;
    }

    @Override
    public Uri insert(Uri uri, ContentValues contentValues) {
        long id = db.insert(ShareDbHelpler.TABLE_NAME, null, contentValues);

        if (id >= 0) {
            return Uri.withAppendedPath(uri, String.valueOf(id));
        } else {
            return null;
        }
    }

    @Override
    public int delete(Uri uri, String selection, String[] selectionArgs) {
        int result = matcher.match(uri);
        switch (result) {
            case FRAUIT:
                return db.delete(ShareDbHelpler.TABLE_NAME, selection, selectionArgs);

            case FRAUIT_ID:
                return db.delete(ShareDbHelpler.TABLE_NAME, "_ID = ?",
                        new String[] {uri.getLastPathSegment()});

            default:
                return 0;
        }
    }

    @Override
    public int update(Uri uri, ContentValues contentValues, String selection, String[] selectionArgs) {
        int result = matcher.match(uri);
        switch (result) {
            case FRAUIT:
                return db.update(ShareDbHelpler.TABLE_NAME, contentValues, selection, selectionArgs);

            case FRAUIT_ID:
                return db.update(ShareDbHelpler.TABLE_NAME, contentValues, "_ID = ?",
                        new String[]{uri.getLastPathSegment()});

            default:
                return 0;
        }
    }
}

AndroidManifestでコンテンツプロバイダを宣言する

Content Providerを使用するためにAndroidManifestに定義を追加する必要があります。

<provider
    android:authorities="com.sample.shareddb.testsprovider"
    android:name="com.sample.shareddb.TestProvider">
 </provider>

android:nameには、クラス名を定義します。
android:authorities には、外部からアクセスする際に使用するURIを定義します。
これは、パッケージ名を含めたクラス名を定義するのが一般的です。

コンテンツプロバイダへの問い合わせ

コンテンツプロバイダを介してデータを取得するには、ContentResolverインスタンスを利用します。 query()メソッドを使って取得したCursorオブジェクトから各取得データへのアスセスが可能となります。

以下のサンプルでは、ActivityからContentResolverを利用してデータを取得し、ListViewへデータを表示しています。
本来は、取得したCursorオブジェクトは使用しなくなったタイミングで適切に破棄する必要がありますが、
今回は、サンプルのため何もしていません。

public class MainActivity extends Activity {

    Cursor mCoursor;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        String[] projection = new String[] {TestProvider.Columns._ID,
            TestProvider.Columns.NAME};
        mCoursor = getContentResolver().query(TestProvider.CONTENT_URI, projection, null, null,
        null);

        SimpleCursorAdapter adapter = new SimpleCursorAdapter(this, android.R.layout.simple_list_item_1,
                mCoursor,
                new String[] {TestProvider.Columns.NAME},
                new int[] {android.R.id.text1},
                0);

        ListView list = (ListView) findViewById(R.id.list_view);
        list.setAdapter(adapter);
    }
}

参考

http://www.techdoctranslator.com/android/guide/content-providers

【Android】Serviceをつかってみる

バックグラウンドで動くプログラムServiceを使ってみました。

Serviceの使用方法

Serviceの起動方法には、

  • startService()を使用
  • bindService()を使用

の大きく分けて2種類があります。

Service起動方法の違い

起動方法による違いは、以下のとおりです。

startService

  • Service起動中は、ActivityへIntentの発行が可能。
  • Service起動後は、ActivityからServiceを制御する経路がない。
  • Serviceの生存期間はActivityに依存しない。明示的にstopServiceが呼ばれるまで動き続ける。

bindService

  • バインドを使うことでActivityからServiceを制御できる。
  • Serviceの生存期間はコネクションに依存。コネクションが切断されるとServiceは終了する。

startService()を使用した起動

startService()を使用して、Serviceを起動した時のライフサイクルを以下に記載します。

順序 メソッド 説明
1 onCreate() Serviceが初回作成時に呼ばれる。
2 onStartCommand() クライアントが明示的にstartService(Intent)を呼び出したタイミングで呼ばれる。
このメソッドを直接呼び出してはいけない。
APIレベル5以前は、onStart()が使用されるため注意。
3 onDestroy() Serviceが使用されなくなったタイミングで呼ばれる
Serviceの持っているリソースをクリーンアップする。

サンプルソース

■ AndroidManifest.xml

<service android:name="Service名" />

■ Activityの呼び出し

public class MainActivity extends Activity {
    
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        
        Button startButton = (Button) findViewById(R.id.startService);
        startButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                // Serviceの起動
                Intent intent = new Intent(MainActivity.this, MyService.class);
                startService(intent);
            }
        });
        
        Button stopButton = (Button) findViewById(R.id.stopService);
        stopButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                // Serviceの停止
                Intent intent = new Intent(MainActivity.this, MyService.class);
                stopService(intent);
            }
        });
    }
}

■ Serviceクラス

public class MyService extends Service {

    @Override
    public void onCreate() {
        super.onCreate();
        Log.d("service", "onCreate");
    }
    
    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        Log.d("service", "onStartCommand");
        return super.onStartCommand(intent, flags, startId);
    }
    
    @Override
    public void onDestroy() {
        Log.d("service", "onDestroy");
        super.onDestroy();
    }
}

bindService()を使用した起動方法

bindService()を使用して、Serviceを起動した時のライフサイクルを以下に記載します。

順序 メソッド 説明
1 onCreate() Serviceが初回作成時に呼ばれる。
2 onBind() Service接続時に呼ばれる。ServiceとActivityを仲介するIBinderを返却する。
3 ServiceConnection#
onServiceConnected()
Service接続後、Binderが確立したタイミングで呼び出される。
4 unBind() クライアントがServiceと切断されたタイミングで呼ばれる。
5 onDestroy() Serviceが使用されなくなったタイミングで呼ばれる。
Serviceの持っているリソースをクリーンアップする。

ServiceをbindService()経由で呼び出す場合は、インタフェースとしてServiceConnectionを使用します。
ServiceConnectionによって取得するIBinderを介することで、Serviceへの制御を行うことが可能です。

サンプルソース

■ AndroidManifest.xml

<service android:name="Service名" />

■ Activityの呼び出し

public class MainActivity extends Activity {

    // Serviceとのインターフェースクラス
    private ServiceConnection mConnection = new ServiceConnection() {
        BindService mBindService;
        public void onServiceConnected(ComponentName className, IBinder service) {
            // Serviceとの接続確立時に呼び出される。
            // service引数には、Onbind()で返却したBinderが渡される
            mBindService = ((BindService.LocalBinder)service).getService();
            //必要であればmBoundServiceを使ってバインドしたServiceへの制御を行う
        }
     
        public void onServiceDisconnected(ComponentName className) {
            // Serviceとの切断時に呼び出される。
            mBindService = null;
        }
    };

    
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        
        Button bindButton = (Button) findViewById(R.id.bindtService);
        bindButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                // Serviceをbindする
                Intent i = new Intent(MainActivity.this,BindService.class);
                bindService(i, mConnection, Context.BIND_AUTO_CREATE);
            }
        });
        
        Button unbindButton = (Button) findViewById(R.id.unbindService);
        unbindButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                // Serviceをunbindする
                unbindService(mConnection);
            }
        });
    }
}

■ Serviceクラス

public class BindService extends Service {
 
    // Serviceに接続するためのBinderクラスを実装する
    public class LocalBinder extends Binder {
        //Serviceの取得
        BindService getService() {
            return BindService.this;
        }
    }

    // Binderの生成
    private final IBinder mBinder = new LocalBinder();
    
    @Override
    public IBinder onBind(Intent intent) {
        // Service接続時に呼び出される
        // 戻り値として、Serviceクラスとのbinderを返す。
        Log.i("", "onBind" + ": " + intent);
        return mBinder;
    }
 
    @Override
    public void onRebind(Intent intent){
        // Unbind後に再接続する場合に呼ばれる
        Log.i("", "onRebind" + ": " + intent);
    }
 
    @Override
    public boolean onUnbind(Intent intent){
        // Service切断時に呼び出される
        //onUnbindをreturn trueでoverrideすると次回バインド時にonRebildが呼ばれる
        return true;
    }
}

参考

http://www.techdoctranslator.com/android/guide/services/bound-services
http://yuki312.blogspot.jp/2012/07/androidserviceonstartcommand.html
http://techbooster.org/android/application/3270/

【Andoid】AsyncTaskLoaderを使ってみる

Android 3.0から導入されたLoaderについて、
参考サイトをもとに勉強していましたがいまいちよくわからず。。

Loaderを継承した、AsyncTaskLoaderの方が使い方がわかりやすそうだったので、
まずはこちらを使用してみました。

Loaderの基本クラス

Loader

非同期のデータロードを行う為の抽象クラス。

LoaderManager

Loaderインスタンスを管理するためのクラス。
LoaderManagerインスタンスは、1つ以上のLoaderインスタンスを管理する。
Activityまたは、Fragmentごとに1つしか作られない。

LoaderManager.LoaderCallbacks

Activityまたは、FragmentとLoaderManager間で、
双方向にやりとりするためのコールバックインターフェース。

AsyncTaskLoaderを使ってみる

Activity側の実装

Activity側の実装では、Loaderを管理するLoaderManagerインスタンスを初期化する必要があります。
また、LoaderManagerから状態を受け取るためのコールバックとしてLoaderManager.LoaderCallbacksインターフェースが用意されているため、Activityにこれを実装します。

LoaderManagerの初期化には、getLoaderManager().initLoader()を使用します。
initLoader()は、Activity.onStart()よりも早く呼び出す必要があります。

引数について、以下のとおりです。

引数 詳細
第一引数:
int id
Loaderの識別子。
onCreateLoader()第一引数に渡される。
第二引数:
Bundle args
Loaderへのパラメタ。
onCreateLoaderメソッドの第二引数に渡される。
Loaderがすでに生成されている場合は、この値は無視される。
第三引数:
LoaderCallback callback
Loaderの状態変化に使用される。
LoaderCallbackインターフェースを継承したクラスを指定する。

以下に、実装のサンプルを記載します。

// LoaderCallbacksのジェネリクスには、Loaderの戻り値の型を指定する(以下は、String)
public class MainActivity extends Activity implements LoaderCallbacks<String> {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        // LoaderManagerの初期化
        getLoaderManager().initLoader(0, null, this);
    }
    
    @Override
    public Loader<String> onCreateLoader(int id, Bundle args) {
        // Loderを初期化する
        MyLoader loader = new MyLoader(this);
        return loader;
    }

    @Override
    public void onLoadFinished(Loader<String> loader, String data) {
        // dataでは、Loderクラスの戻り値が返される
        // Loderの終了処理
        Log.d("","onLoadFinished");
    }

    @Override
    public void onLoaderReset(Loader<String> arg0) {
        // Loderが、リセットされるときに呼ばれる。
        // ここで、もらっているdataを破棄する必要がある。
        Log.d("","onLoaderReset");
    }
}

Loaderの実装

非同期処理を行うLoaderクラスは、AsyncTaskLoaderクラスを継承して作成します。
Loaderが処理を開始するには、forceLoad()を呼び出す必要があり、
以下のサンプルでは、Loderの準備が完了した際に呼ばれるonStartLoading()で実行しています。
実際にバックグラウンドで行う処理は、loadInBackground()に記載します。

public static class MyLoader extends AsyncTaskLoader<String> {
        
    public MyLoader(Context context) {
        super(context);
    }

    @Override
    public void deliverResult(String data) {
        // Loderが処理した結果を返す。(メインスレッドで実行される)
        super.deliverResult(data);
    }

    @Override
    public String loadInBackground() {
        // Loderが実行するバックグラウンド処理
        return "abc";
    }
        
    @Override
    protected void onStartLoading() {
        // Loder側の準備ができたタイミングで呼び出される
        // UIスレッドで実装される
        forceLoad();
    }
}

注意点

AsyncTaskLoaderを使う上で幾つか注意すべき点があります。


  1. 呼び出し元のActivityがアクティブでないと、onLoadFinished()コールバックが呼ばれないため、 それを考慮した実装を行う必要があります。

  2. LoaderインスタンスonStartLoading()は、Activityがアクティブになったタイミングで呼ばれます。(何度も呼ばれる) その為、一度処理した内容が再度処理されないように意識した実装を行う必要があります。


こちらを参考にしました。

これらを考慮したサンプルを以下に記載します。

public static class MyLoader extends AsyncTaskLoader<String> {

    private String data;

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

    @Override
    public void deliverResult(D data) {
        if (isReset()) {
            return;
        }

        this.data = data;
        super.deliverResult(data);
    }

    @Override
    public String loadInBackground() {
        return "abc";
    }

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

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

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

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

参考

http://www.techdoctranslator.com/android/guide/activities/loaders http://blog.loadlimits.info/2012/09/asynctaskloaderのあるactivityに戻ってきたときに再度loadinbackgroundが呼ば/ http://ijoru.com/ijoru/?p=207

【Android】DialogFragmentを使ってみる

Android 3.0以降、ダイアログの表示には、DialogFragmentを使用することが推奨されているようです。
基本的な表示処理は、Fragmentと変わらないみたいなのですが、 使用したことがなかったので試してみました。

DialogFragmentの表示

ダイアログを表示するには、DialogFragmentを継承したクラスを作成して、
各メソッドを継承します。

最低限ダイアログの表示を確認するのであれば、onCreateDialog()を継承し、
表示したいDIalogクラスのインスタンスを返却してあげればOKです。

public class MyDialogFragment extends DialogFragment {
    
    @Override
    public Dialog onCreateDialog(Bundle savedInstanceState) {
        AlertDialog.Builder alert = new AlertDialog.Builder(getActivity())
            .setTitle("タイトル")
            .setMessage("メッセージ")
            .setPositiveButton("OK", new DialogInterface.OnClickListener() {
                @Override
                public void onClick(DialogInterface dialog, int which) {
                    dismiss();
                }
            });
        return alert.create();
    }    
}

ただし、Fragmentを継承したDialogFragmentは、
Fragmentと同様に再生成時にデフォルトコンストラクタが呼ばれる使用のため、
初期化時は、以下のようにsetArguments()を使用するのがお作法です。

public static final MyDialogFragment newInstance(int flags) {

    Bundle args = new Bundle();
    args.putInt("flags", flags);

    MyDialogFragment fragment = new MyDialogFragment();
    fragment.setArguments(args);
    return fragment;
}

ハマったところ

DialogFragmetの表示処理(初期化を含む)をActivityのOnCreate()に記載していたのですが、
回転処理で異常終了が発生する不具合がありました。

DialogFragmetの実装クラスがインナークラスとして宣言されている場合、
Publicな標準コンストラクタが呼び出せず例外が発生するようです。
そのためDialogFragmetは、Publicなクラスとして使用するのが一般的なようです。

【Android】Fragmentを使ってみる

Fragment は Android 3.0 ( API レベル "Honeycomb" ) から使用できるようになったコンポーネントです。
Fragmentを使用することで、一つのActivityに対して複数のUIを構築することが可能で、
画面の一部に対してのUI切り替えや遷移などを柔軟に行うことができます。

Fragmentの実装

Fragmentを実装するには、最低限、以下のメソッドを実装する必要があります。

Activityの状態 Fragmentで呼ばれるコールバック
onCreate() システムが、Fragmentを作成したときに呼び出される。
コンポーネントの初期化処理などをここで行う。
onCreateView() FragmentのUIが描画されるタイミングでよびだされる。
FragmentのレイアウトのRootになっているViewをここでinflateする。
onPause() Fragmentが停止するときによばれる。
Fragmentで変更されたステータスの保存はここで行う。

他にも様々なライフサイクルメソッドが存在しますが、今回は割愛します。

Fragmentに対してlayoutを紐付けるには、以下のようにonCreateView()でinflateを行います。

public class SampleFragment extends Fragment {
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
            Bundle savedInstanceState) {
        return inflater.inflate(R.layout.sample_fragmet, container, false);
    }
}

Fragmentの表示方法

Activityから、Fragmentを表示するためには、FragmentManagerを使用します。
Fragmentの追加・削除・交換などのアクションは、FragmentTransactionによってコミット単位で管理されており、FragmentManagerから取得できるFragmentTransactionに対して、
各アクションを適用することで、Fragmentの管理を行うことが可能です。

新規で作成したFragmentの表示を行うためには以下のように記載します。
表示したいViewGroupに対して、Fragmentをadd()メソッドにより追加し
最後にcommit()を呼び出すことで対象のFragmentを表示することができます。

FragmentManager fragmentManager = getFragmentManager();
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
        
FirstFragment fragment = new FirstFragment();
fragmentTransaction.add(R.id.fragment_container, fragment, "tag");
fragmentTransaction.commit();

Fragmentをバックスタックに追加する

FragmentをFragmentTransactionのバックスタックに追加することで、
スタック管理を行うことができます。

バックスタックはActivityにより管理されており、
ユーザがBACKキーを押すことにより前のFragmentへ戻ることが可能です。

以下は、表示中のFragmentを新しいFragmentと入れ替えるサンプルです。
addToBackStack(null)を呼び出すことにより元のFragmentをバックスタックへ追加しています。

FragmentManager fragmentManager = getFragmentManager();
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
        
SecondFragment secondFragment = new SecondFragment();
fragmentTransaction.replace(R.id.fragment_container, secondFragment);
fragmentTransaction.addToBackStack(null);
fragmentTransaction.commit();

Fragment生成時の注意事項

Fragmentの生成処理は、一般的に下記のようなstaticメソッドが使用されます。

public static SampleFragment newInstance(int index) {
    SampleFragment f = new SampleFragment();

    Bundle args = new Bundle();
    args.putInt("index", index);
    f.setArguments(args);

    return f;
}

これは、システムがFragmentの再生成を行うときに、デフォルトのコンストラクタが呼ばれる為、
独自のコンストラクタを使用したパラメタの引き渡しができないという理由からです。

システムは、例えばメモリ不足などで破棄されたFragmentを再生成する時に、 Fragment#instantiate(Context context, String fname, Bundle args)) メソッドを使用します。
このメソッドは、引数で渡されたクラス名(fname)のFragmentを標準コンストラクタで呼び出してインスタンスを生成したのち、第3引数でセットしたBundleをsetArguments()でセットしてから返します。

そのため、Fragment生成に必要な引数がある場合は、 初期化時にsetArguments()を使用してパラメタを渡す必要があります。

ライフサイクル

Fragmentのライフサイクルは、Activityに似たつくりになっています。 FragmentはActivityによって保持されているため、お互いのライフサイクルは連動していて、 例えば、ActivityがonPause()を受け取ると、ActivityのそれぞれのFragmentはonPause()を受け取ります。

Activityの状態とFragmentのコールバックメソッドの関連

Activityの状態 Fragmentで呼ばれるコールバック
create OnAttach

OnCreate()

OnCreateView()

OnActivityCreated()
start OnStart()
resume OnResume()
pause OnPause()
stop OnStop()
destroyed OnDestroyView()

OnDestroy()

OnDetach()

参考

https://sites.google.com/a/techdoctranslator.com/jp/android/guide/activities/fragments

【Android】ActionBarのAppIconを使ってみる

ActionBarの左上のAppIcon領域には、自由にロゴ画像を配置することが可能です。
また、タップイベントを有効することで画面の遷移ナビゲーションを管理することも可能です。

AppIconのロゴを変更する

1.AndroidManifest.xmlで指定する

AndroidManifest.xmlからAppIconの画像を変更するには、 Activity要素にあるandroid:logoに表示したい画像リソースを指定します。

<activity
    android:name="com.example.actionbarmove.SampleActivity"
    android:logo="@drawable/icon" >
</activity>

2.ソースから指定する

ソース上から、画像を変更するには、getActionBar()で取得した ActionBarに対してsetLogo()を使用します。

getActionBar().setLogo(R.drawable.icon);

タップイベントを取得する

AppIconのタップイベントを取得するには、 ActionBarに対して、setDisplayHomeAsUpEnabled(true)を実行します。
上記メソッドを実行するとAppIconのアイコンに<マークが付き、 タップイベントを取得できるようになります。

getActionBar().setDisplayHomeAsUpEnabled(true) 

タップイベントを取得するには、
Activityクラスで、onOptionsItemSelected()メソッドをオーバーライドします。
AppIconには、android.R.id.homeというリソースIDが割り振られいるため、
これをハンドリングします。

下記の実装では、AppIconを押下されたタイミングで、ホーム画面へ遷移を行っています。
Intent.FLAG_ACTIVITY_CLEAR_TOPを付与して画面遷移を行うことでActivityスタックをクリアし、
どの画面から遷移してもホーム画面へ戻ることが可能です。

@Override
public boolean onOptionsItemSelected(MenuItem item) {
    switch (item.getItemId()) {
        case android.R.id.home:
            // app icon in Action Bar clicked; go home
            Intent intent = new Intent(this, HomeActivity.class);
            intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
            startActivity(intent);
            return true;
        default:
            return super.onOptionsItemSelected(item);
    }
}

参考

http://www.techdoctranslator.com/android/guide/ui/actionbar
http://dev.classmethod.jp/smartphone/android-tips-10-AppIcon/