Category Archives: Android

AndroidアプリでWebViewを使用しているときに、GooglePlayで配信しているアプリのダウンロードページへのリンクをクリックしても、GooglePlayアプリが起動しないことがあります。

 

(以下の画像は、弊社で配信している『カラダノートforAndroid』で動作させているものです。)

20131120_184633

アプリのダウンロードページへのリンクをクリックすると……

20131120_184750

普通のブラウザでアクセスしようとしてエラーになってしまう。

 

そういう場合は、WebViewをコードで制御して、アクセスするURLによってGooglePlayアプリを起動するなどの処理を実装しましょう。

WebView webView;
webView = (WebView) findViewById(R.id.○○○);

//各種設定
webView.getSettings().setUseWideViewPort(true);
webView.getSettings().setLoadWithOverviewMode(true);
webView.setVerticalScrollbarOverlay(true);
webView.getSettings().setBuiltInZoomControls(true);
webView.setWebChromeClient(new WebChromeClient());
webView.getSettings().setJavaScriptEnabled(true);

//WebViewに独自のWebViewCliantをセット
webView.setWebViewClient(new MyWebViewClient(MainActivity.this));

このようにWebViewを宣言したのち、セットするWebViewCliantの方で、

public class MyWebViewClient extends WebViewClient {
	Activity mainActivity;

    public MyWebViewClient(Activity mainActivity) {
        super();
        this.mainActivity = mainActivity;
    }

    //ページの読み込み開始
    @Override
    public void onPageStarted(WebView view, String url, Bitmap favicon) {
    	mainActivity.setProgressBarIndeterminateVisibility(true);
    }

    //ページの読み込み完了
    @Override
    public void onPageFinished(WebView view, String url) {
    	mainActivity.setProgressBarIndeterminateVisibility(false);
    }

    //ページの読み込み失敗
    @Override
    public void onReceivedError(WebView view, int errorCode, String description, String failingUrl) {
        Toast.makeText(view.getContext(), "エラー", Toast.LENGTH_LONG).show();
    }

	// リンクをタップしたときに標準ブラウザを起動させない。
    @Override
    public boolean shouldOverrideUrlLoading(WebView webView, String url) {
    	// アンドロイドマーケットのときは例外
    	if(url.startsWith("http:") || url.startsWith("https:")) {
    		return false;
    	}
    	Uri uri = Uri.parse(url);
    	Intent intent = new Intent(Intent.ACTION_VIEW, uri);
    	mainActivity.startActivity(intent);
    	return true;
    }
}

こう書くと、
20131120_184447
リンクをクリックすると……
20131120_184505

このようにGooglePlayが起動します!

 

やっぱりAndroidアプリでWebを見ている以上、GooglePlayへのリンクをクリックしたら自動でGooglePlayアプリが立ち上がるようにしたいですよね!

コードがたくさんあって大変でしたね。ここまでお疲れ様でした。今回の記事でプッシュ機能は完成です。

前回の記事のMainActivityのソース内、登録ボタンを押したときの処理に、こんな記述があったと思います。

// レジストレーション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();

これは通信部分をHandlerで処理しているのですが、その中身がこんな感じです

[RegisterTask.java]

public class RegisterTask extends Thread {
	// レスポンスコード
	int res_code;

	Context context;
	Handler mHandler;
	ProgressDialog mProgress;
	TextView tv_id;

	String regid;
	SharedPreferences prefs;

	// レジストレーションIDをPOSTするURL
	String post_url = "○○○○○○○○○.php";

	public static String message = "少々お待ちください……";
	String TAG = "RegisterTask";

	public RegisterTask(Context context, Handler mHandler,
			ProgressDialog mProgress, String regid, SharedPreferences prefs,
			TextView tv_id) {
		this.res_code = 0;

		this.context = context;
		this.mHandler = mHandler;
		this.mProgress = mProgress;
		this.tv_id = tv_id;

		this.regid = regid;
		this.prefs = prefs;
	}

	// スレッド内処理
	public void run() {
		// 時間がかかる処理
		Log.d(TAG, "postします");

		PostData();

		// スレッドが終了した場合、終了したことをHandlerに知らせる。
		mHandler.post(new Runnable() {
			public void run() {
				// ダイアログを消す
				mProgress.dismiss();
}
		});
	}

	// POST
	private void PostData() {
		// 結果
		String ret = "";

		// インスタンス取得
		GoogleCloudMessaging gcm = GoogleCloudMessaging.getInstance(context);

		try {
			// GCMサーバーへ登録する
			regid = gcm.register(Const.SENDER_ID);

			Log.d(TAG, "regid:" + regid);
		} catch (IOException e) {
			e.printStackTrace();
		}

		// レジストレーションIDを自分のサーバーへ送信する
		URI url = null;
		try {
			url = new URI(post_url);
			Log.d(TAG, "URLはOK:" + post_url);
		} catch (URISyntaxException e) {
			e.printStackTrace();
		}

		// POSTするJSONをつくる
		JSONObject kv1 = new JSONObject();
		try {
			kv1.put("regid", regid);
			kv1.put("param1", "param");
			kv1.put("param2", -1);
			kv1.put("param3", false);
		} catch (JSONException e2) {
			e2.printStackTrace();
		}

		String json = kv1.toString();
		Log.d(TAG, "jsonデータ:" + json);

		// POSTリクエストを実行
		DefaultHttpClient httpClient = new DefaultHttpClient();
		try {
			Log.d(TAG, "POST開始");
			HttpPost httpPost = new HttpPost(url);
			httpPost.setEntity(new StringEntity(json, "UTF-8"));
			httpPost.setHeader("Content-Type", "application/json");
			httpPost.setHeader("Accept-Encoding", "application/json");
			httpPost.setHeader("Accept-Language", "en-US");

			ret = httpClient.execute(httpPost, new ResponseHandler() {
				@Override
				public String handleResponse(HttpResponse response)
						throws IOException {
					Log.d(TAG, "レスポンスコード:"
							+ response.getStatusLine().getStatusCode());
					res_code = response.getStatusLine().getStatusCode();

					// 正常に受信できた場合は200
					switch (response.getStatusLine().getStatusCode()) {
					case HttpStatus.SC_OK:
						Log.d(TAG, "レスポンス取得に成功");

						// レスポンスデータをエンコード済みの文字列として取得する
						return EntityUtils.toString(response.getEntity(),
								"UTF-8");

					case HttpStatus.SC_NOT_FOUND:
						Log.d(TAG, "データが存在しない");
						return null;

					default:
						Log.d(TAG, "通信エラー");
						return null;
					}
				}
			});
		} catch (IOException e) {
			Log.d(TAG, "通信に失敗:" + e.toString());
		} finally {
			// shutdownすると通信できなくなる
			httpClient.getConnectionManager().shutdown();
		}

		// 受信結果をUIに表示
		Log.d(TAG, "結果:" + ret);

		// 成功したら
		if (res_code == HttpStatus.SC_OK) {
			// レジストレーションIDを端末に保存
			Log.d(TAG, "regid:::" + regid);
			storeRegistrationId(regid);

			//MainActivity画面に反映
			tv_id.setText(regid);
		}
	}

	/*
	 * レジストレーションIDの保存
	 */
	private void storeRegistrationId(String regId) {
		// 今のバージョン
		int currentVersion = 0;
		try {
			PackageInfo packageInfo = context.getPackageManager()
					.getPackageInfo(context.getPackageName(), 0);
			currentVersion = packageInfo.versionCode;
		} catch (NameNotFoundException e) {
		}

		// 保存
		Editor editor = prefs.edit();
		editor.putString(Const.PROPERTY_REG_ID, regId);
		editor.putInt(Const.PROPERTY_APP_VERSION, currentVersion);
		editor.commit();
	}
}

途中、POSTするためのJSONを作っています。
実際に動作させてログを見れば解りますが、ここでは、

{
    "regid"   : 123456,		//int型
    "param1"  : "param",	//String型
    "param2"  : -1,			//int型
    "param3"  : false		//boolean型
}

という形式のJSONを作っています。
このあたりは、どういう形式のJSONを受け取るのか、サーバー側の処理を作成する人と話し合ってください。また、エラーコードやレスポンスについても、サーバーの処理によるので、話し合いが必要です。

さて、これで、レジストレーションIDの登録まで完了しました。
あとはサーバーからのプッシュ通知を受け取るレシーバーを作るだけです。

[GcmBroadcastReceiver.java]

public class GcmBroadcastReceiver extends WakefulBroadcastReceiver {
	String TAG = "GcmBroadcastReceiver";

	@Override
	public void onReceive(Context context, Intent intent) {
		// 送られてきたデータを受け取る
		GoogleCloudMessaging gcm = GoogleCloudMessaging.getInstance(context);
		String messageType = gcm.getMessageType(intent);

		// 送られてきたデータのメッセージ
		Bundle extras = intent.getExtras();
		String mess = extras.toString();

		if (!extras.isEmpty()) {
			// エラー
			if (messageType
					.equals(GoogleCloudMessaging.MESSAGE_TYPE_SEND_ERROR)) {
				Log.d(TAG, "MESSAGE_TYPE_SEND_ERROR:" + mess);
			}
			// サーバーでメッセージ削除
			else if (messageType
					.equals(GoogleCloudMessaging.MESSAGE_TYPE_DELETED)) {
				Log.d(TAG, "MESSAGE_TYPE_DELETED:" + mess);
			}
			// 正常に受信
			else if (messageType
					.equals(GoogleCloudMessaging.MESSAGE_TYPE_MESSAGE)) {
				Resources res = context.getResources();

				Notification n = new Notification(); // Notificationの生成
				n.icon = R.drawable.ic_launcher; // アイコンの設定

				// 通知されたときに通知バーに表示される文章
				n.tickerText = mess + "(short)"; // メッセージの設定
				n.flags = Notification.FLAG_AUTO_CANCEL; // 通知を選択した時に自動的に通知が消えるための設定

				// 通常の着信音を選択する
				Uri uri = RingtoneManager
						.getDefaultUri(RingtoneManager.TYPE_ALARM); // アラーム音
				n.sound = uri; // サウンド

				Intent i = new Intent(context, MainActivity.class);
				i.putExtra("MESS", mess);

				PendingIntent pi = PendingIntent.getActivity(context, 0, i,
						PendingIntent.FLAG_UPDATE_CURRENT);
				// 上から通知バーを下してきたときに表示される文章をセット
				n.setLatestEventInfo(context, res.getString(R.string.app_name),
						mess + "(long)", pi);

				long[] vibrate_ptn = { 0, 100, 300, 1000 }; // 独自バイブレーションパターン
				n.vibrate = vibrate_ptn; // 独自バイブレーションパターンを設定

				n.defaults |= Notification.DEFAULT_LIGHTS; // デフォルトLED点滅パターンを設定

				// NotificationManagerのインスタンス取得
				NotificationManager nm = (NotificationManager) context
						.getSystemService(Context.NOTIFICATION_SERVICE);
				nm.notify(1, n); // 設定したNotificationを通知する
			}
		}
	}
}

受け取った通知に問題がなければ、Notificationを使って、端末のアラームやバイブレーションを動作させて知らせます。
このあたりはそれぞれお好みの仕様にしてください。

最後に、定数を宣言するクラスを追加すれば完成です。
[Const.java]

public final class Const {
	//プロダクトID
	static final String SENDER_ID = "○○○○○○○○○○○○";

	// SharedPreferences用
	public static final String EXTRA_MESSAGE = "message";
	public static final String PROPERTY_REG_ID = "registration_id";
	public static final String PROPERTY_APP_VERSION = "appVersion";
}

ここまで上手くいっていれば、プッシュ通知を受け取ると、下記の画像のようになっているのではないでしょうか。

20131119_191348

20131119_191408

20131119_191421

以上でプッシュ通知のアプリ側の実装は完了です!

あとは受け取ったメッセージを煮るなり焼くなり好きにしてください!

あ、「削除」ボタンを押したときの処理を書くのを忘れていました……。

基本的にはRegisterTaskと同じように、サーバーに情報を投げて、受け取ったサーバー側はプッシュ通知を配信しないようにするなどのときに使います。RegisterTaskのほぼコピペでOKです。

それでは!

今回の記事では、前回から引き続き、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ファイル内でその情報をもとにした判別処理を組み込み、ユーザーによって通知するメッセージを変更する、みたいな動作も可能です。

Androidアプリで、カメラアプリから受け取った画像データをSDカードに保存することがあると思います。
それをあとから呼び出そうと思ったらぬるぽになってアプリが強制終了!なんて現象が発生することがあります。

それは、ギャラリーに画像が反映されておらず、読み取ろうとしたときに「画像がねえよ!」って怒られているわけです。

今回は、画像をSDカードに保存した後、明示的にデータをコンテンツプロバイダーに登録する方法を書きます。

//登録する枚数
int new_nums = (登録する画像の枚数);

//配列をnew
String[] paths = new String[new_nums];
String[] mimeTypes = new String[new_nums];

//画像のパスと形式を配列に格納
for (int i = 0; i < new_nums; i++) {
	paths[i] = (登録する画像のパス);
	mimeTypes[i] = "image/jpeg";
}

//ファイルを登録
MediaScannerConnection.scanFile(
		getApplicationContext(), paths, mimeTypes,
		new OnScanCompletedListener() {
			@Override
			public void onScanCompleted(String path,
					Uri uri) {
				//登録が全て完了すると、このイベントが呼ばれる
				// System.out.println("MediaScannerConnection --- Scanned "
				// + path + ":");
				// System.out.println("MediaScannerConnection --- uri="
				// + uri);
			}

		});