Bucket Place

[Ruby on Rails & Android] GCM(Android Push) and Ruby On Rails 연동 Part 2.

Cloud Travel 2014. 6. 17. 15:31


  2 단계: Application 개발   


1. Application 개발을 위해서 Google Play Service 패키지를 다운 받아서 라이브러리에 추가해야 합니다.

   - SDK Manager를 열어서 Extra의 Google Play Services 패키지를 다운받으시면 됩니다.

    

   - Pakage import

    > 패키지 위치 = {sdk-home}/extras/google/google_play_services/libproject/google-play-services-lib

  

2. 새로운 프로젝트를 생성하여 Library로 연결을 해줍니다.

   - 프로젝트 이름을 우클릭하여 Properties 메뉴를 선택합니다.

   - Android 탭으로 이동하여 Google-play-service 라이브러리를 Add를 시켜줍니다.

    

    ( Volley 도 추가되어있는데, 이건 앞으로 Rails와 통신하기 위해서 Import시킨 라이브러리입니다. )


3. Mainfest 설정

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="[PACKAGE_NAME]"
    android:versionCode="1"
    android:versionName="1.0" >

    <uses-sdk
        android:minSdkVersion="8"
        android:targetSdkVersion="19" />

    <!-- GCM Permission -->
    <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" />
    <uses-permission android:name="android.permission.VIBRATE" />

    <permission
        android:name="[PACKAGE_NAME].permission.C2D_MESSAGE"
        android:protectionLevel="signature" />

    <uses-permission android:name="[PACKAGE_NAME].permission.C2D_MESSAGE" />
    <!-- GCM Permission END -->

    <application
        ... >
        <activity
            ...
            android:launchMode="singleTask" >
            ...
        </activity>

        <!-- GCM Receiver and service enroll -->
        <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="[PACKAGE_NAME]" />
            </intent-filter>
        </receiver>

        <service android:name=".GcmIntentService" />

        <meta-data
            android:name="com.google.android.gms.version"
            android:value="@integer/google_play_services_version" />
    </application>

</manifest>


4. Main Activity

public class MainActivity extends Activity implements OnClickListener {

	// for GCM
	private final static int PLAY_SERVICES_RESOLUTION_REQUEST = 9000;
	private static final String SENDER_ID = "PROJEC_ID"; 	// input Project Id!!

	private GoogleCloudMessaging _gcm;
	private String _regId;

	// for component in main activity
	private Button mBtnSignUp;
	private EditText mEtId;
	private EditText mEtPwd;

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

		Intent intent = getIntent();
		int condition = intent.getIntExtra("condition", -1);

		if (condition != -1) {
			Toast.makeText(getApplicationContext(), condition + "clicked", Toast.LENGTH_SHORT).show();
		}

		mBtnSignUp = (Button) findViewById(R.id.btn_sign_up);
		mBtnSignUp.setOnClickListener(this);
		mEtId = (EditText) findViewById(R.id.et_id);
		mEtPwd = (EditText) findViewById(R.id.et_pwd);

		// google play service가 사용가능한가
		if (checkPlayServices()) {
			_gcm = GoogleCloudMessaging.getInstance(this);
			_regId = getRegistrationId();

			if (TextUtils.isEmpty(_regId))
				registerInBackground();
		} else {
			Log.i("MainActivity.java | onCreate", "|No valid Google Play Services APK found.|");
		}
	}

	// google play service가 사용가능한가
	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("MainActivity.java | checkPlayService", "|This device is not supported.|");
				finish();
			}
			return false;
		}
		return true;
	}

	// registration id를 가져온다.
	private String getRegistrationId() {
		String registrationId = PreferenceUtil.instance(getApplicationContext()).regId();
		if (TextUtils.isEmpty(registrationId)) {
			Log.i("MainActivity.java | getRegistrationId", "|Registration not found.|");
			return "";
		}
		int registeredVersion = PreferenceUtil.instance(getApplicationContext()).appVersion();
		int currentVersion = getAppVersion();
		if (registeredVersion != currentVersion) {
			Log.i("MainActivity.java | getRegistrationId", "|App version changed.|");
			return "";
		}
		return registrationId;
	}

	// app version을 가져온다. 뭐에 쓰는건지는 모르겠다.
	private int getAppVersion() {
		try {
			PackageInfo packageInfo = getPackageManager().getPackageInfo(getPackageName(), 0);
			return packageInfo.versionCode;
		} catch (NameNotFoundException e) {
			// should never happen
			throw new RuntimeException("Could not get package name: " + e);
		}
	}

	// gcm 서버에 접속해서 registration id를 발급받는다.
	private void registerInBackground() {
		new AsyncTask() {
			@Override
			protected String doInBackground(Void... params) {
				String msg = "";
				try {
					if (_gcm == null) {
						_gcm = GoogleCloudMessaging.getInstance(getApplicationContext());
					}
					_regId = _gcm.register(SENDER_ID);
					msg = "Device registered, registration ID=" + _regId;

					// For this demo: we don't need to send it because the
					// device
					// will send upstream messages to a server that echo back
					// the
					// message using the 'from' address in the message.

					// Persist the regID - no need to register again.
					storeRegistrationId(_regId);
				} catch (IOException ex) {
					msg = "Error :" + ex.getMessage();
					// If there is an error, don't just keep trying to register.
					// Require the user to click a button again, or perform
					// exponential back-off.
				}

				return msg;
			}

			@Override
			protected void onPostExecute(String msg) {
				Log.i("MainActivity.java | onPostExecute", "|" + msg + "|");
			}
		}.execute(null, null, null);
	}

	// registration id를 preference에 저장한다.
	private void storeRegistrationId(String regId) {
		int appVersion = getAppVersion();
		Log.i("MainActivity.java | storeRegistrationId", "|" + "Saving regId on app version " + appVersion + "|");
		PreferenceUtil.instance(getApplicationContext()).putRedId(regId);
		PreferenceUtil.instance(getApplicationContext()).putAppVersion(appVersion);
	}

	public void onClick(View v) {
		// TODO Auto-generated method stub
		switch (v.getId()) {
		case R.id.btn_sign_up:
			String id = mEtId.getText().toString();
			String pwd = mEtPwd.getText().toString();

			JSONObject userData = new JSONObject();
			JSONObject user = new JSONObject();
			try {
				user.put("name", id);
				user.put("password_digest", pwd);

				userData.put("user", user);

			} catch (JSONException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}

			Uri.Builder uri = Uri.parse("URI HERE").buildUpon();
			// set to URI!!

			JsonObjectRequest request = new JsonObjectRequest(Method.POST, uri.toString(), userData,
					new Listener() {

						public void onResponse(JSONObject response) {
							// TODO Auto-generated method stub
							System.out.println(response.toString());
						}

					}, null);

			Volley.newRequestQueue(getApplicationContext()).add(request);

			break;
		}
	}
}

MainActivity 에서는 해줘야 할 것이 있습니다.

 1) Sender의 아이디 즉, 프로젝트 아이디를 적어 주셔야 합니다.

 2) 행동을 설정해줍니다.

   (상위 코드에서는 회원 등록을 서버로 보냅니다. 또, Notification에 들어온 정보중 일부를 전달받아 Toast로 보여줍니다.)

   a) 행동이란 Notification을 눌러서 어플리케이션에 들어왔을때 처리(상위 코드에서는 20-25번째줄 : 인자를 통한 행동 결정)

   b) 어플리케이션에서 행동을 했을 때 서버로 보내는 통로를 설정

       (상위 코드에서는 Volley와 Json을 통해서 서버로 보냄(132~169번째줄))

 3) Ruby와의 통신을 위한 URI를 설정해줍니다. 위 코드의 152번째줄


 MainActivity를 실행한 후에는 Log를 통해서 출력되는 Registration Id를 적어 두셔야 합니다.

 본래는 서버와 통신을 하면서 로그인시 Reg_id를 저장해 둬서 Push시에 사용하는 것을 처리해줘야 합니다.


5. GcmBroadcastReceiver

 이것은 푸쉬 메세지가 왔을 때 처리할 행동을 정의해 주시면 됩니다. 하위 코드에서는 간단하게 토스트를 진동 및 Notification을 주게 하였습니다. 사실은 어플리케이션이 동작중일때와 아닐때를 나눠두면 더 좋을 것이라고 생각됩니다. 사실은 이것은 인텐트 부분에서 해결(카테고리 6. GcmIntentService)해주는 일이지만 어째서인지 작동하지 않아서 이곳에서 했답니다.

public class GcmBroadcastReceiver extends WakefulBroadcastReceiver {
	public static final int NOTIFICATION_ID = 1;
	
	@Override
	public void onReceive(Context context, Intent intent) {
		String msg = null;
		Bundle bundle = intent.getExtras();
		
		Object value = bundle.get("msg");
		msg = value.toString();
		
		Object contentValue = bundle.get("condition");
		int condition = Integer.parseInt(contentValue.toString());

		NotificationManager mNM = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
		
		Intent parameter = new Intent(context, MainActivity.class);
		parameter.putExtra("condition", condition);
		
		PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, parameter, PendingIntent.FLAG_UPDATE_CURRENT); 
		NotificationCompat.Builder mNoti = new NotificationCompat.Builder(context)
				.setContentTitle("GCM Notification")
				.setContentText(msg+condition)
				.setSmallIcon(R.drawable.ic_launcher)
				.setTicker("알림!!!!")
				.setAutoCancel(true)
				.setContentIntent(pendingIntent)
				.setVibrate(new long[] { 0, 500 });
		mNM.notify(NOTIFICATION_ID, mNoti.build());

		// Explicitly specify that GcmIntentService will handle the intent.
		ComponentName comp = new ComponentName(context.getPackageName(), GcmIntentService.class.getName());
		// Start the service, keeping the device awake while it is launching.
		startWakefulService(context, intent.setComponent(comp));
		setResultCode(Activity.RESULT_OK);
	}
}

 여기서도 설정해 주서야 할 옵션이 있습니다.


  1) 서버에서 보내오는 Json형식을 파악하여 받아온 메세지를 분석

  2) 서버에서 받은 내용을 기반으로 작성할 Notification 정의 및 기타 옵션 설정 (위의 경우에는 진동을 0.5초간)


6. GcmIntentService

 서비스가 종료되어있을때 작동하는 부분입니다.

public class GcmIntentService extends IntentService {
	public static final int NOTIFICATION_ID = 1;

	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)) {
				String msg = intent.getStringExtra("msg");
				// Post notification of received message.
				// sendNotification("Received: " + extras.toString());
				sendNotification("Received: " + msg);
				Log.i("GcmIntentService.java | onHandleIntent", "Received: " + extras.toString());
			}
		}
		// Release the wake lock provided by the WakefulBroadcastReceiver.
		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) {
		NotificationManager mNotificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);

		Intent intent = new Intent(getApplicationContext(), MainActivity.class);
		intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
		intent.putExtra("msg", msg);

		PendingIntent contentIntent = PendingIntent.getActivity(this, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);

		NotificationCompat.Builder mBuilder = new NotificationCompat.Builder(this)
				.setSmallIcon(R.drawable.ic_launcher).setContentTitle("GCM Notification")
				.setStyle(new NotificationCompat.BigTextStyle().bigText(msg)).setContentText(msg).setAutoCancel(true)
				.setVibrate(new long[] { 0, 500 });

		mBuilder.setContentIntent(contentIntent);
		mNotificationManager.notify(NOTIFICATION_ID, mBuilder.build());
	}
}

 작동 및 동작 여부는 솔직히 잘 모르겠습니다. 이 부분은... 아무리 꺼놔도 노티가 안와서 브로드캐스트부분에 구현해놨습니다. 없으면 안될 거같아서 같이 올려드립니다.


7. BasePreferenceUtil

 쎄미님의 블러그를 보고 한 것인데... 이 부분은 솔직히 잘 모르겠다. 키에 대한 환경설정을 관리하는 부분인것 같지만... 필요 없는 것 같기도 하고... 잘 모르겠다...

public class BasePreferenceUtil {
	private SharedPreferences _sharedPreferences;

	protected BasePreferenceUtil(Context context) {
		super();
		_sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context);
	}

	protected void put(String key, String value) {
		SharedPreferences.Editor editor = _sharedPreferences.edit();
		editor.putString(key, value);
		editor.commit();
	}

	protected String get(String key) {
		return _sharedPreferences.getString(key, null);
	}

	protected void put(String key, boolean value) {
		SharedPreferences.Editor editor = _sharedPreferences.edit();
		editor.putBoolean(key, value);
		editor.commit();
	}

	protected boolean get(String key, boolean _default) {
		return _sharedPreferences.getBoolean(key, _default);
	}

	protected void put(String key, int value) {
		SharedPreferences.Editor editor = _sharedPreferences.edit();
		editor.putInt(key, value);
		editor.commit();
	}

	protected int get(String key, int _default) {
		return _sharedPreferences.getInt(key, _default);
	}
}

이에 이어서 Base를 Extends한... PreferenceUtil

public class PreferenceUtil extends BasePreferenceUtil {
	private static PreferenceUtil _instance = null;

	private static final String PROPERTY_REG_ID = "registration_id";
	private static final String PROPERTY_APP_VERSION = "appVersion";

	public static synchronized PreferenceUtil instance(Context _context) {
		if (_instance == null)
			_instance = new PreferenceUtil(_context);
		return _instance;
	}

	protected PreferenceUtil(Context _context) {
		super(_context);
	}

	public void putRedId(String _regId) {
		put(PROPERTY_REG_ID, _regId);
	}

	public String regId() {
		return get(PROPERTY_REG_ID);
	}

	public void putAppVersion(int _appVersion) {
		put(PROPERTY_APP_VERSION, _appVersion);
	}

	public int appVersion() {
		return get(PROPERTY_APP_VERSION, Integer.MIN_VALUE);
	}
}


이상으로 Push에 관련된 내용이 클라이언트 부분이 끝났다. 다음 글에서 서버쪽을 보겠다.

서버부분은 매우 단순하다.