Category Archives: Java

今回の記事では、前回から引き続き、Androidアプリでプッシュ通知を実装するやり方についてです。

○アプリを作り、アプリから[レジストレーションID]を取得する

アプリを作成し、前回の記事でGoogleAPIコンソールから引っ張ってきたプロジェクトIDを入れ込み、サーバーからの通知を受け取ります。

アプリ構築にあたりこちらの記事をかなり参考にしました。
GCMを使用してAndroid-PHPでPUSH通知を実装する [Google Play services対応]

プッシュ通知に対応したアプリを作るだけならば、↑の記事の冒頭に書いてあるGCMSampleというサンプルアプリをダウンロードすれば簡単です。
2か所ほど間違いがあるので、それを修正すれば動きます。

[AndroidManifest.xml]39行目

android:name=".GCMBroadcastReceiver"

android:name=".GcmBroadcastReceiver"

[MainActivity.java]111行目

if (regid.equals("")) {

if (registrationId.equals("")) {

こちらのサンプルを参考に、自分なりに作り直しました。
それが下記のアプリです。

【GCM_test_application2】

「2」になっているのに特に意味はありません。私が2通りのパターンで試して、うまくいったのが2の方だっただけです。

4

まずはアプリのMainActivityを作っていきます。
画面はこの一画面だけで完結するようにしました。

AndroidManifest.xmlに、パーミッションを記述してください。

[AndroidManifest.xml]

<!-- プッシュ通知を受け取った際にバイブレーション機能を使うためのパーミッション -->
<uses-permission android:name="android.permission.VIBRATE" />

<!-- GCMメッセージ受信のためのパーミッション -->
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.GET_ACCOUNTS" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="com.google.android.c2dm.permission.RECEIVE" />
<permission
    android:name="○○○(パッケージ名).permission.C2D_MESSAGE"
    android:protectionLevel="signature" />
<uses-permission
    android:name="○○○(パッケージ名).permission.C2D_MESSAGE" />

同じくAndroidManifest.xmlに、プッシュ通知を受け取るためのレシーバーを登録してください。MainActivityはアプリ生成時に自動で記述されているので省いています。

[AndroidManifest.xml]

<!-- GCMを受け取るレシーバー -->
<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="○○○(パッケージ名)" />
    </intent-filter>
</receiver>

次に、MainActivityのレイアウトxmlを作ります。

[activity_main.xml]

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:gravity="center"
    android:orientation="vertical" >

    <LinearLayout
        android:gravity="center"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal" >
        <Button
            android:id="@+id/btn_regist"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="登録"
            android:onClick="btREGIST" />
        <Button android:id="@+id/btn_unregist"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="削除"
            android:onClick="btDELETE"/>
    </LinearLayout>

    <LinearLayout
        android:gravity="center"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal" >
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="登録ID:" />
        <TextView
            android:id="@+id/text_id"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text=""/>
    </LinearLayout>

    <LinearLayout
        android:gravity="center"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal" >
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="受け取ったメッセージ:" />
        <TextView
            android:id="@+id/text_mess"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text=""/>
    </LinearLayout>

</LinearLayout>

そして、いよいよMainActivityの中身です。

[MainActivity.java]

public class MainActivity extends Activity {
	private Context context;

	private String regid;

	private TextView tv_id;
	private TextView tv_mess;

	// 通信用ダイアログ
	private ProgressDialog mProgress;

	// 保存していた値を取得
	SharedPreferences prefs;

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

		context = MainActivity.this;

		// 保存するプリファレンス
		prefs = getSharedPreferences(MainActivity.class.getSimpleName(),
				Context.MODE_PRIVATE);

		tv_id = (TextView) findViewById(R.id.text_id);
		tv_mess = (TextView) findViewById(R.id.text_mess);

		// レジストレーションIDの取得
		regid = getRegistrationId();
		tv_id.setText(regid);

		// もし通知メッセージからアプリを起動したらここでGcmBroadcastReceiverからの値を受け取る
		Intent intent = getIntent();
		String mess = intent.getStringExtra("MESS");
		tv_mess.setText(mess);
	}

	public void btREGIST(View v) {
		// すでにレジストレーションIDを取得ずみなら
		if (!regid.equals("")) {
			// 処理を中断
			return;
		}

		// デバイスにPlayサービスAPKが入っているか検証する
		if (!checkPlayServices()) {
			showToast("Google Play開発者サービスをインストールしてください");

			Intent intent = new Intent(
					Intent.ACTION_VIEW,
					Uri.parse("https://play.google.com/store/apps/details?id=com.google.android.gms&hl=ja"));
			startActivity(intent);

			// 処理を中段
			return;
		}

		// レジストレーションIDを取得
		mProgress = null;
		Handler mHandler = new Handler();

		mProgress = new ProgressDialog(context);
		mProgress.setMessage(RegisterTask.message);

		mProgress.show();
		RegisterTask t = new RegisterTask(context, mHandler, mProgress, regid,
				prefs, tv_id);
		t.start();
	}

	public void btDELETE(View v) {
	}

	//PlayサービスのAPKチェック
	private boolean checkPlayServices() {
		int resultCode = GooglePlayServicesUtil
				.isGooglePlayServicesAvailable(this);
		if (resultCode != ConnectionResult.SUCCESS) {
			return false;
		}
		return true;
	}

	//レジストレーションIDの取得
	private String getRegistrationId() {
		// ID
		String registrationId = prefs.getString(Const.PROPERTY_REG_ID, "");
		// アプリのバージョン
		int registeredVersion = prefs.getInt(Const.PROPERTY_APP_VERSION, 0);
		// 今のバージョン
		int currentVersion = 0;
		try {
			PackageInfo packageInfo = context.getPackageManager()
					.getPackageInfo(context.getPackageName(), 0);
			currentVersion = packageInfo.versionCode;
		} catch (NameNotFoundException e) {
		}

		// アプリケーションがバージョンアップされていたときは再生成
		if (registeredVersion != currentVersion) {
			registrationId = "";
		}

		// IDを生成しなくてはいけない場合は空で返却
		return registrationId;
	}

	private void showToast(String text) {
		Toast.makeText(this, text, Toast.LENGTH_LONG).show();
	}

}

想定するアプリの動作の流れは、
・「登録」ボタンを押して、GoogleCloudMessagingインスタンスレジストレーションIDを取得する
・取得したレジストレーションIDをサーバーにPOSTして、サーバーの方でDBに保存する
・送られてきた通知をレシーバーで受け取って、MainActivity画面に反映させる
といったものです。

次回の記事では、POST処理とレシーバー処理について書きます。

Androidでサーバーからのアプリへのプッシュ通知を実装する方法を解説します。
WEB上の情報だけではアプリが正常に動かなかったのと、解りづらかったので、アプリの挙動とソースをメインに解説していきます。

大まかな流れとしては

○GoogleAPIのコンソールで[プロジェクトID][API key]を取得する

○アプリを作り、アプリから[レジストレーションID]を取得する

○サーバーからアプリへ通知する

という3つの柱になります。

○GoogleAPIのコンソールで[プロジェクトID][API key]を取得する

こちらについては、下記のサイトを参考になさってください。

Androidアプリでプッシュ通知を利用する

この記事の、<< 事前準備 >>と書かれた部分を手順通り行っていくと、プロジェクトIDAPI keyを取得できるはずです。
確認の仕方は下記を参照してください。

[プロジェクトID]

プロジェクトのトップページのどこかに「Project Number: ○○○○○○○○○○○○ 」という表記があり、それがプロジェクトIDになります。または、APIコンソールのURLの一部に「project:○○○○○○○○○○○○」という文字列が含まれており、そちらでも確認できます。

[API key]

古いコンソールだと記事の通りの場所に、新しいコンソール画面であれば、左メニューの「APIs & auth」から「Registered apps」をクリックして、該当のappを選択すると表示されます。

○アプリを作り、アプリから[レジストレーションID]を取得する

次回記事を参照

○サーバーからアプリへ通知する

こちらについては、下記のサイトを参考になさってください。

GCMを使用してAndroid-PHPでPUSH通知を実装する [Google Play services対応]

基本的には、アプリからサーバーに送られてきた情報(レジストレーションID+その他)をDBに保存して、API keyレジストレーションIDをセットにしてPUSH通知要求をGCMサーバーに送信します。

上記の記事では、アプリからレジストレーションIDのみを受け取っていますが、他の情報を受け取り、phpファイル内でその情報をもとにした判別処理を組み込み、ユーザーによって通知するメッセージを変更する、みたいな動作も可能です。

こんにちはこんばんわイッシーです。すっかりブログの更新をサボってしましました。( ✖ Δ ✖)。。。そろそろ書いておかないとアレがアレなので書いておきます。

今回のネタは今更にはなりますがAsyncTaskについてです。通信や巨大ファイルの操作などをする時にメインスレッドで行っている方はいませんよね?(いたらすぐにやめてくださいね)

時間がかかる処理は別スレッドで行いUIスレッドでは行わないのは鉄則です。androidではメインスレッドが5秒以上応答しないと“Application Not Responding”(通称:ANR)が表示されて強制終了のダイアログが表示されます。従って、通信をメインスレッドで行ってはいけません。

さて、以下のようなコードを考えましょう。

public class Main extends ListActivity {
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        if(isConencted(this)){
            Task task = new Task();
            task.execute(URL);
        }
    }
   
    /**
     * isConencted
     * ネットワークの有無を確認する
     * @param context
     * @return boolean
     */

    protected boolean isConencted(Context context)
    {
        ConnectivityManager connectivityManger = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
        NetworkInfo networkInfo = connectivityManger.getActiveNetworkInfo();
        if(networkInfo == null){
            return false;
        }
        return (networkInfo.isConnected());
    }
   
    /**
     * Task
     * バックグラウンド処理用のクラス
     */

    protected class Task extends AsyncTask<String, String, String>
    {
        @Override
        protected String doInBackground(String... params)
        {
            HttpClient client = new DefaultHttpClient();
            HttpGet get = new HttpGet(params[0]);
            byte[] result = null;
            String str = "";
            try{
                HttpResponse response = client.execute(get);
                StatusLine statusLine = response.getStatusLine();
                if(statusLine.getStatusCode() == HttpURLConnection.HTTP_OK){
                    result = EntityUtils.toByteArray(response.getEntity());
                    str = new String(result, "UTF-8");
                }
            }
            catch (Exception e) {
            }
            return str;
        }
       
        /**
         * onPostExecute
         * @param String result 通信結果
         * 結果を受け取ったときに・・・メインスレッド
         */

        @Override
        protected void onPostExecute(String result)
        {
            try {
                JSONObject json = new JSONObject(result);
            }
            catch (JSONException e) {
                e.printStackTrace();
            }
        }
    }
}

■ポイント

ポイント1

isConenctedで圏外をチェックしてから通信(バックグラウンドタスクを実行)します。電波が無いところで通信しようとするのは無駄というものです。まあ、当然ですね。

ポイント2

別スレッドで実行されるのはdoInBackgroundだけであって、onPostExecuteはメインスレッドで実行されます。時間がかかる処理をonPostExecuteに書かないようにしましょう。

ポイント3

onPostExecuteが実行されたからといって、必ずしも通信が完了したとは考えないほうが良いと思います。地下鉄などではすぐに電波の状態が悪くなります。通信中に圏外になった場合、文字列が途中まで返ってくるのですがJSONでしたら当然パースできません。JSONExceptionできっちり処理してあげてください。

ポイント4

サンプルコードには書いてませんが同時に多数のAsyncTaskを実行するとメモリ不足で落ちる場合、synchronized(activity)して1本しか走らせないようにしましょう。

手短ですが本日はここまで。次回もandroidネタにしようかと思います。