androidのプッシュ通知実装:ClassNotFoundException
プッシュ通知実装を行っているのですが、サーバからのデータを受け取る段階でクラッシュしてしまいます。 環境はEclipseでgoogle play service libはプロジェクトを作ってimportし、ライブラリとして追加。 suport v4 はandroid toolsからadd support libraryして追加しました。
エラー:
FATAL EXCEPTION: main
java.lang.RuntimeException: Unable to instantiate receiver org.techbooster.gcmsample.GCMBroadcastReceiver: java.lang.ClassNotFoundException: Didn't find class "org.techbooster.gcmsample.GCMBroadcastReceiver" on path: /data/app/org.techbooster.gcmsample-1.apk
at android.app.ActivityThread.handleReceiver(ActivityThread.java:2371)
at android.app.ActivityThread.access$1500(ActivityThread.java:149)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1322)
at android.os.Handler.dispatchMessage(Handler.java:99)
at android.os.Looper.loop(Looper.java:213)
at android.app.ActivityThread.main(ActivityThread.java:5092)
at java.lang.reflect.Method.invokeNative(Native Method)
at java.lang.reflect.Method.invoke(Method.java:511)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:797)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:564)
at dalvik.system.NativeStart.main(Native Method)
Caused by: java.lang.ClassNotFoundException: Didn't find class "org.techbooster.gcmsample.GCMBroadcastReceiver" on path: /data/app/org.techbooster.gcmsample-1.apk
at dalvik.system.BaseDexClassLoader.findClass(BaseDexClassLoader.java:65)
at java.lang.ClassLoader.loadClass(ClassLoader.java:501)
at java.lang.ClassLoader.loadClass(ClassLoader.java:461)
at android.app.ActivityThread.handleReceiver(ActivityThread.java:2366)
... 10 more
アクティビティ
package org.techbooster.gcmsample;
import java.io.IOException;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;
import com.google.android.gms.common.ConnectionResult;
import com.google.android.gms.common.GooglePlayServicesUtil;
import com.google.android.gms.gcm.GoogleCloudMessaging;
import org.techbooster.gcmsample.CommonUtilities;
import org.techbooster.gcmsample.*;
import android.os.AsyncTask;
import android.os.Bundle;
import android.app.Activity;
import android.content.Context;
import android.content.SharedPreferences;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager.NameNotFoundException;
import android.util.Log;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
public class MainActivity extends Activity implements OnClickListener{
private static final String TAG = "TechBoosterSample";
public static final String EXTRA_MESSAGE = "message";
public static final String PROPERTY_REG_ID = "registration_id";
private static final String PROPERTY_APP_VERSION = "appVersion";
private final static int PLAY_SERVICES_RESOLUTION_REQUEST = 9000;
GoogleCloudMessaging gcm;
String regid;
Context context;
/**
* サーバー通信用AsyncTask
*/
AsyncTask<Void, Void, Void> mRegisterTask;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Button btnRegist = (Button) findViewById(R.id.btn_regist);
btnRegist.setOnClickListener(this);
Button btnUnregist = (Button) findViewById(R.id.btn_unregist);
btnUnregist.setOnClickListener(this);
context = getApplicationContext();
// デバイスにPlayサービスAPKが入っているか検証する
if (checkPlayServices()) {
gcm = GoogleCloudMessaging.getInstance(context);
regid = getRegistrationId(context);
} else {
Log.i(TAG, "Google Play Services APKが見つかりません");
}
}
/*
* PlayサービスのAPKチェック
*/
private boolean checkPlayServices() {
int resultCode = GooglePlayServicesUtil.isGooglePlayServicesAvailable(this);
if (resultCode != ConnectionResult.SUCCESS) {
if (GooglePlayServicesUtil.isUserRecoverableError(resultCode)) {
GooglePlayServicesUtil.getErrorDialog(resultCode, this,
PLAY_SERVICES_RESOLUTION_REQUEST).show();
} else {
Log.i(TAG, "Playサービスがサポートされていない端末です");
finish();
}
return false;
}
return true;
}
/*
* レジストレーションIDの取得
*/
private String getRegistrationId(Context context) {
final SharedPreferences prefs = getGCMPreferences(context);
String registrationId = prefs.getString(PROPERTY_REG_ID, "");
if (regid == null) return "";
// プリファレンスに格納されていない場合は空で返却
if (regid.equals("")) {
Log.i(TAG, "レジストレーションIDが見つかりません");
return "";
}
// アプリケーションがバージョンアップされていた場合、レジストレーションIDを必ずクリアしないといけません
// すでにレジストレーションIDが存在していた場合、再生成は行いません。
int registeredVersion = prefs.getInt(PROPERTY_APP_VERSION, Integer.MIN_VALUE);
int currentVersion = getAppVersion(context);
if (registeredVersion != currentVersion) {
Log.i(TAG, "アプリケーションバージョンが変更されています");
return "";
}
return registrationId;
}
/*
* アプリケーションバージョン情報を取得する
*/
private static int getAppVersion(Context context) {
try {
PackageInfo packageInfo = context.getPackageManager()
.getPackageInfo(context.getPackageName(), 0);
return packageInfo.versionCode;
} catch (NameNotFoundException e) {
throw new RuntimeException("パッケージ名が見つかりません : " + e);
}
}
/*
* SharedPreferencesを取得
*/
private SharedPreferences getGCMPreferences(Context context) {
return getSharedPreferences(MainActivity.class.getSimpleName(),
Context.MODE_PRIVATE);
}
/*
* レジストレーションIDの保存
*/
private void storeRegistrationId(Context context, String regId) {
final SharedPreferences prefs = getGCMPreferences(context);
int appVersion = getAppVersion(context);
Log.i(TAG, "レジストレーションIDを登録。登録時のアプリケーションバージョン: " + appVersion);
SharedPreferences.Editor editor = prefs.edit();
editor.putString(PROPERTY_REG_ID, regId);
editor.putInt(PROPERTY_APP_VERSION, appVersion);
editor.commit();
}
@Override
protected void onResume() {
super.onResume();
checkPlayServices();
}
@Override
protected void onDestroy() {
if (mRegisterTask != null) {
mRegisterTask.cancel(true);
}
gcm.close();
super.onDestroy();
}
@Override
public void onClick(View v) {
if(v.getId() == R.id.btn_regist){
if (regid.equals("")) {
// GCM登録用AsyncTaskの実行
mRegisterTask = new AsyncTask<Void, Void, Void>() {
@Override
protected Void doInBackground(Void... params) {
if (gcm == null) {
// インスタンスがなければ取得する
gcm = GoogleCloudMessaging.getInstance(context);
}
try {
// GCMサーバーへ登録する
regid = gcm.register(CommonUtilities.SENDER_ID);
} catch (IOException e) {
e.printStackTrace();
}
// レジストレーションIDを自分のサーバーへ送信する
// レジストレーションIDをつかえば、アプリケーションにGCMメッセージを送信できるようになります
Log.i(TAG,"送信対象のレジストレーションID: " + regid);
register(regid);
// レジストレーションIDを端末に保存
storeRegistrationId(context, regid);
return null;
}
@Override
protected void onPostExecute(Void result) {
mRegisterTask = null;
}
};
mRegisterTask.execute(null, null, null);
}
}else if(v.getId() == R.id.btn_unregist){
//GCMサーバーから登録を解除するAsyncTaskの実行
mRegisterTask = new AsyncTask<Void, Void, Void>() {
@Override
protected Void doInBackground(Void... params) {
if (gcm == null) {
// インスタンスがなければ取得する
gcm = GoogleCloudMessaging.getInstance(context);
}
try {
// GCMサーバーの登録を解除する
gcm.unregister();
} catch (IOException e) {
e.printStackTrace();
}
// レジストレーションIDを自分のサーバーでも削除する
unregister(regid);
return null;
}
@Override
protected void onPostExecute(Void result) {
mRegisterTask = null;
}
};
mRegisterTask.execute(null, null, null);
}
}
/*
*
*/
public static boolean register(String regId) {
String serverUrl = CommonUtilities.SERVER_URL + "/register";
Map<String, String> params = new HashMap<String, String>();
params.put("key", regId);
try {
post(serverUrl, params);
return true;
} catch (IOException e) {
e.printStackTrace();
}
return false;
}
public static void unregister(String regId) {
String serverUrl = CommonUtilities.SERVER_URL + "/unregister";
Map<String, String> params = new HashMap<String, String>();
params.put("key", regId);
try {
post(serverUrl, params);
} catch (IOException e) {
}
}
public static void post(String endpoint, Map<String, String> params)
throws IOException {
URL url;
try {
url = new URL(endpoint);
} catch (MalformedURLException e) {
throw new IllegalArgumentException("invalid url: " + endpoint);
}
StringBuilder bodyBuilder = new StringBuilder();
Iterator<Entry<String, String>> iterator = params.entrySet().iterator();
// POSTするパラメータ
while (iterator.hasNext()) {
Entry<String, String> param = iterator.next();
bodyBuilder.append(param.getKey()).append('=')
.append(param.getValue());
if (iterator.hasNext()) {
bodyBuilder.append('&');
}
}
String body = bodyBuilder.toString();
byte[] bytes = body.getBytes();
HttpURLConnection conn = null;
try {
conn = (HttpURLConnection) url.openConnection();
conn.setDoOutput(true);
conn.setUseCaches(false);
conn.setFixedLengthStreamingMode(bytes.length);
conn.setRequestMethod("POST");
conn.setRequestProperty("Content-Type",
"application/x-www-form-urlencoded;charset=UTF-8");
// ポスト送信
OutputStream out = conn.getOutputStream();
out.write(bytes);
out.close();
// サーバーレスポンス受信
int status = conn.getResponseCode();
if (status != 200) {
throw new IOException("Post failed with error code " + status);
}
} finally {
if (conn != null) {
conn.disconnect();
}
}
}
}
レシーバクラス
package org.techbooster.gcmsample;
import com.google.android.gms.gcm.GoogleCloudMessaging;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.content.res.Resources;
import android.media.RingtoneManager;
import android.net.Uri;
import android.os.Bundle;
import android.support.v4.content.WakefulBroadcastReceiver;
import android.util.Log;
public class GcmBroadcastReceiver extends WakefulBroadcastReceiver {
private static final String TAG = "gcm debug";
@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を通知する
}
}
}
}
サービスクラス
package org.techbooster.gcmsample;
import com.google.android.gms.gcm.GoogleCloudMessaging;
import android.app.IntentService;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.os.SystemClock;
import android.support.v4.app.NotificationCompat;
import android.util.Log;
public class GcmIntentService extends IntentService {
private static final String TAG = "TechBoosterSample";
public static final int NOTIFICATION_ID = 1;
private NotificationManager mNotificationManager;
NotificationCompat.Builder builder;
public GcmIntentService() {
super("GcmIntentService");
}
@Override
protected void onHandleIntent(Intent intent) {
Bundle extras = intent.getExtras();
GoogleCloudMessaging gcm = GoogleCloudMessaging.getInstance(this);
// The getMessageType() intent parameter must be the intent you received
// in your BroadcastReceiver.
String messageType = gcm.getMessageType(intent);
if (!extras.isEmpty()) { // has effect of unparcelling Bundle
/*
* Filter messages based on message type. Since it is likely that GCM
* will be extended in the future with new message types, just ignore
* any message types you're not interested in, or that you don't
* recognize.
*/
if (GoogleCloudMessaging.
MESSAGE_TYPE_SEND_ERROR.equals(messageType)) {
sendNotification("Send error: " + extras.toString());
} else if (GoogleCloudMessaging.
MESSAGE_TYPE_DELETED.equals(messageType)) {
sendNotification("Deleted messages on server: " +
extras.toString());
// If it's a regular GCM message, do some work.
} else if (GoogleCloudMessaging.
MESSAGE_TYPE_MESSAGE.equals(messageType)) {
// This loop represents the service doing some work.
for (int i=0; i<5; i++) {
Log.i(TAG, "Working... " + (i+1)
+ "/5 @ " + SystemClock.elapsedRealtime());
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
}
}
Log.i(TAG, "Completed work @ " + SystemClock.elapsedRealtime());
// Post notification of received message.
sendNotification("Received: " + extras.toString());
Log.i(TAG, "Received: " + extras.toString());
}
}
// Release the wake lock provided by the WakefulBroadcastReceiver.
org.techbooster.gcmsample.GcmBroadcastReceiver.completeWakefulIntent(intent);
}
// Put the message into a notification and post it.
// This is just one simple example of what you might choose to do with
// a GCM message.
private void sendNotification(String msg) {
mNotificationManager = (NotificationManager)
this.getSystemService(Context.NOTIFICATION_SERVICE);
PendingIntent contentIntent = PendingIntent.getActivity(this, 0,
new Intent(this, MainActivity.class), 0);
NotificationCompat.Builder mBuilder =
new NotificationCompat.Builder(this)
.setSmallIcon(R.drawable.ic_launcher)
.setContentTitle("GCM Notification")
.setStyle(new NotificationCompat.BigTextStyle()
.bigText(msg))
.setContentText(msg);
mBuilder.setContentIntent(contentIntent);
mNotificationManager.notify(NOTIFICATION_ID, mBuilder.build());
}
}
マニュフェスト
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="org.techbooster.gcmsample"
android:versionCode="1"
android:versionName="1.0" >
<uses-sdk
android:minSdkVersion="9"
android:targetSdkVersion="17" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.GET_ACCOUNTS" />
<uses-permission android:name="android.permission.READ_CONTACTS"/>
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="com.google.android.c2dm.permission.RECEIVE" />
<permission
android:name="org.techbooster.gcmsample.permission.C2D_MESSAGE"
android:protectionLevel="signature" />
<uses-permission android:name="org.techbooster.gcmsample.permission.C2D_MESSAGE" />
<application
android:allowBackup="true"
android:icon="@drawable/ic_launcher"
android:label="@string/app_name"
android:theme="@style/AppTheme" >
<meta-data
android:name="com.google.android.gms.version"
android:value="@integer/google_play_services_version" />
<activity
android:name="org.techbooster.gcmsample.MainActivity"
android:label="@string/app_name" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<receiver
android:name="org.techbooster.gcmsample.GCMBroadcastReceiver"
android:permission="com.google.android.c2dm.permission.SEND" >
<intent-filter>
<action android:name="com.google.android.c2dm.intent.RECEIVE" />
<action android:name="com.google.android.c2dm.intent.REGISTRATION" />
<category android:name="org.techbooster.gcmsample" />
</intent-filter>
</receiver>
<service android:name="org.techbooster.gcmsample.GCMIntentService" />
</application>
</manifest>