【Android】GCMを使ってみる
GCM(Google Cloud Messaging for Android)は、
開発者がサーバからAndroidデバイス上のAndroidアプリケーションに対して、
データを送信できるようにする仕組みです。
Developers Consoleの準備
Developers Consoleにて、アプリケーションを登録してAPI実行に必要なKey情報を取得します。
1.任意のアプリケーションを作成する
アプリケーションを作成すると、APIコンソールのDashboardにProject Number
が表示されます。
この数字は、後ほど作成するGCM受信アプリにて、登録ID(registration ID)取得の際に必要になるので覚えておきます。
2.GCMの設定を有効にする
APIs & auth
を選択し、表示されるリストの中からGoogle Cloud Messaging for Android
を探してONにします。
3. API Keyを発行する
API Keyを作成するには、左メニューのAPIs & auth
> credential
ボタンを押下し、
Public API access
領域にあるCREATE NEW KEY
ボタンを押下します 。
以下のように作成するキーを選択するダイアログが表示されるます。
今回は、Pushの確認テストとしてサーバーからリクエストを送るため、Server key
を選択します。
Server key
ボタンを押下すると、アクセスを許可するIPアドレスを設定するためのダイアログが表示されるので、
アクセスを許可するグローバルIPを入力します。
処理が正常に完了すると下記のようにAPI keyが発行されます。
このKeyを使用して、サーバー側からのGCMのメッセージを送信します。(後述)
Androidアプリの準備
GCMを受信するためのAndroidアプリケーションを実装します。
1.GSM用のライブラリをeclipseにimportする
Android SDK Manager
を起動し、Extras
配下のGoogle Play services
を選択し、
importします。
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を使う上で幾つか注意すべき点があります。
呼び出し元のActivityがアクティブでないと、
onLoadFinished()
コールバックが呼ばれないため、 それを考慮した実装を行う必要があります。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/