최근에 Android Push Service를 사용 할 일이 생겨서 Demo를 만들어보았다.
기존 Eclipse(Ant 기반)으로 만든 Demo는 있었지만...IDE도 Android Studio로 변경 되었고, 이에 따른 빌드 방식도 Gradle로 변경 되었기에 다시 새로운(?) 환경에서 Demo를 생성 하였다.
일단 본격적으로 진행하기 전에..Android Atudio를 이용하여 GcmSample 프로젝트를 하나 생성하자.
(프로젝트 생성 방법은 따로 언급하지 않겠다.)
GCM을 사용하기 위해서는 앱을 등록해야 한다. 등록은 아래 경로에서 진행한다.
https://developers.google.com/cloud-messaging/
오랜만에 접속했더니...UI가 싹 바뀌었다...;;;
TRY IT ON ANDROID 버튼을 클릭하자.
뭐라고 쏼라쏼라 나온다..프로젝트는 위에서 생성했고...
GCM을 추가 해야 하니 add Cloud Messaging to your existing app을 클릭
스크롤을 좀 내리다보면 위와같은 버튼이 보인다. 클릭하자.
앱 등록 화면이 나온다. 처음에 생성한 앱 이름과 패키지를 입력해주고 Choose and configure services 버튼을 클릭하자.
앞에 입력한 데이터로 앱이 등록이 되었다. 앱을 등록하면 이런 서비스를 사용할 수 있다고 보여준다.
내용은 시간 될 때 한번쯤 보시면 될 듯 하고..ENABLE GOOGLE CLOUD MESSAGING 버튼 클릭.
(이 버튼을 클릭해줘야 GCM Service 가 활성화 된다.)
이제 GCM Service 가 활성화 되었다.
이제 설정 파일을 만들어보자. Generate configuration files 버튼을 클릭.
화면이 이동되면 설정파일을 다운로드 할 수 있고, API Key와 Sender ID(Project Number)를 확인할 수 있다.
(나중에 찾아다니면 귀찮으니 따로 메모해서 쉽게 찾을 수 있게 잘 저장해주자.)
Download Google-services.json 버튼을 클릭해서 설정파일을 다운로드 한다.
이제 GCM을 사용 할 수 있는 기본적은 작업이 완료 되었다.
다운로드 받은 파일을 처음에 생성한 프로젝트의 app 디렉토리 안에 복사해주자.
파일을 열어보면 Project ID와 Project Number을 확인 할 수 있다.
json 파일 내용 중 client_id를 찾아서 값을 보면 "android:패키지명" 으로 되어 있는 것을 볼 수 있다.
패키지명 앞에 붙어 있는 android: 를 삭제해주자.
삭제하는 이유는 나중에 push가 정상적으로 수신이 안되는 경우가 발생 될 수 있기에 미리 삭제해 준다.
이제 build.grable 설정을 해야한다.
프로젝트에서 build.grable 파일을 열어서 아래와 같이 classpath를 추가해 주자.
GCM에 필요한 라이브러리(Google Play Services)의 사용을 위하여 app을 설정하는 build.grable 파일을 열어서 아래와 같이 추가하자.
이제 AndroidManifest.xml 설정을 해줘야 한다. (권한, 서비스, 리시브)
- GCM Permission : 디바이스에 GCM 서비스를 사용하기 위한 권한 설정
- GCM Receiver : GCM을 받았을 때 동작하기 위한 리시버
- GCM Listener Service : GCM을 요청을 대기하고 있는 리스너 서비스
- InstanceID Listener Service : InstanceID 요청을 대기하고 있는 리스너 서비스
- GCM Registration Service : GCM을 등록하기 위한 서비스
- com.google.android.c2dm.permission.RECEIVE : GCM은 원래 c2dm 이름으로 베타 운영되다가 정식으로 GCM 이름으로 변경이된다. 그래서 패키지 이름이 c2dm 그대로 유지하게 된듯핟.
- android.permission.WAKE_LOCK : 디바이스가 잠금이되어 화면이 꺼져있을 경우에도 GCM을 받을 수 있기 위해서 디바이스를 깨우는 권한이 필요하다.
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.yang.gcmsample">
<!-- [START gcm_permission] -->
<uses-permission android:name="com.google.android.c2dm.permission.RECEIVE" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<!-- [END gcm_permission] -->
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity
android:name=".MainActivity"
android:label="@string/app_name"
android:theme="@style/AppTheme.NoActionBar">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<!-- [START gcm_receiver] -->
<receiver
android:name="com.google.android.gms.gcm.GcmReceiver"
android:exported="true"
android:permission="com.google.android.c2dm.permission.SEND">
<intent-filter>
<action android:name="com.google.android.c2dm.intent.RECEIVE" />
<category android:name="com.yang.gcmsample" />
</intent-filter>
</receiver>
<!-- [END gcm_receiver] -->
<!-- [START gcm_listener_service] -->
<service
android:name=".MyGcmListenerService"
android:exported="false">
<intent-filter>
<action android:name="com.google.android.c2dm.intent.RECEIVE" />
</intent-filter>
</service>
<!-- [END gcm_listener_service] -->
<!-- [START instanceId_listener_service] -->
<service
android:name=".MyInstanceIDListenerService"
android:exported="false">
<intent-filter>
<action android:name="com.google.android.gms.iid.InstanceID" />
</intent-filter>
</service>
<!-- [END instanceId_listener_service] -->
<!-- [START gcm_registration_service] -->
<service
android:name="com.yang.gcmsample.RegistrationIntentService"
android:exported="false">
</service>
<!-- [END gcm_registration_service] -->
</application></manifest>
Demo에 필요한 다른 파일들을 코딩하자.
string.xml
<resources>
<string name="app_name">GcmSample</string>
<string name="action_settings">Settings</string>
<string name="registering_message_ready">InstanceID 토큰 가져오기</string>
<string name="registering_message_generating">InstanceID 토큰 생성중...</string>
<string name="registering_message_complete">완료!</string>
</resources>
style.xml
<resources>
<!-- Base application theme. -->
<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
<!-- Customize your theme here. -->
<item name="colorPrimary">@color/colorPrimary</item>
<item name="colorPrimaryDark">@color/colorPrimaryDark</item>
<item name="colorAccent">@color/colorAccent</item>
<item name="android:textColor">@android:color/black</item>
<item name="android:textSize">16sp</item>
</style>
<style name="AppTheme.NoActionBar">
<item name="windowActionBar">false</item>
<item name="windowNoTitle">true</item>
</style>
<style name="AppTheme.AppBarOverlay" parent="ThemeOverlay.AppCompat.Dark.ActionBar" />
<style name="AppTheme.PopupOverlay" parent="ThemeOverlay.AppCompat.Light" />
</resources>
dimens.xml
<resources>
<!-- Default screen margins, per the Android Design guidelines. -->
<dimen name="activity_horizontal_margin">16dp</dimen>
<dimen name="activity_vertical_margin">16dp</dimen>
<dimen name="fab_margin">16dp</dimen>
</resources>
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
android:orientation="vertical"
app:layout_behavior="@string/appbar_scrolling_view_behavior"
tools:context="com.yang.gcmsample.MainActivity"
tools:showIn="@layout/activity_main">
<Button android:id="@+id/registrationButton"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="@string/registering_message_ready" />
<TextView android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/informationTextView"
android:textAppearance="?android:attr/textAppearanceMedium"/>
<ProgressBar
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/registrationProgressBar" />
</LinearLayout>
QuickstartPreferences.java
package com.yang.gcmsample;
/**
* Created by Yang on 2015-11-25.
*/
public class QuickstartPreferences {
public static final String REGISTRATION_READY = "registrationReady";
public static final String REGISTRATION_GENERATING = "registrationGenerating";
public static final String REGISTRATION_COMPLETE = "registrationComplete";
}
MainActivity.java
package com.yang.gcmsample;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.Bundle;
import android.support.design.widget.FloatingActionButton;
import android.support.design.widget.Snackbar;
import android.support.v4.content.LocalBroadcastManager;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.Toolbar;
import android.util.Log;
import android.view.View;
import android.view.Menu;
import android.view.MenuItem;
import android.widget.Button;
import android.widget.ProgressBar;
import android.widget.TextView;
import com.google.android.gms.common.ConnectionResult;
import com.google.android.gms.common.GooglePlayServicesUtil;
public class MainActivity extends AppCompatActivity {
private static final int PLAY_SERVICES_RESOLUTION_REQUEST = 9000;
private static final String TAG = "MainActivity";
private Button mRegistrationButton;
private ProgressBar mRegistrationProgressBar;
private BroadcastReceiver mRegistrationBroadcastReceiver;
private TextView mInformationTextView;
/**
* Instance ID를 이용하여 디바이스 토큰을 가져오는 RegistrationIntentService를 실행한다.
*/
public void getInstanceIdToken() {
if (checkPlayServices()) {
// Start IntentService to register this application with GCM.
Intent intent = new Intent(this, RegistrationIntentService.class);
startService(intent);
}
}
/**
* LocalBroadcast 리시버를 정의한다. 토큰을 획득하기 위한 READY, GENERATING, COMPLETE 액션에 따라 UI에 변화를 준다.
*/
public void registBroadcastReceiver() {
mRegistrationBroadcastReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
Log.i(TAG, "action : " + action);
if(action.equals(QuickstartPreferences.REGISTRATION_READY)){
// 액션이 READY일 경우
mRegistrationProgressBar.setVisibility(ProgressBar.GONE);
mInformationTextView.setVisibility(View.GONE);
} else if(action.equals(QuickstartPreferences.REGISTRATION_GENERATING)){
// 액션이 GENERATING일 경우
mRegistrationProgressBar.setVisibility(ProgressBar.VISIBLE);
mInformationTextView.setVisibility(View.VISIBLE);
mInformationTextView.setText(getString(R.string.registering_message_generating));
} else if(action.equals(QuickstartPreferences.REGISTRATION_COMPLETE)){
// 액션이 COMPLETE일 경우
mRegistrationProgressBar.setVisibility(ProgressBar.GONE);
//mRegistrationButton.setText(getString(R.string.registering_message_complete));
//mRegistrationButton.setEnabled(false);
String token = intent.getStringExtra("token");
mInformationTextView.setText(token);
}
}
};
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
FloatingActionButton fab = (FloatingActionButton) findViewById(R.id.fab);
fab.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Snackbar.make(view, "Replace with your own action", Snackbar.LENGTH_LONG)
.setAction("Action", null).show();
}
});
registBroadcastReceiver();
// 토큰을 보여줄 TextView를 정의
mInformationTextView = (TextView)findViewById(R.id.informationTextView);
mInformationTextView.setVisibility(View.GONE);
// 토큰을 가져오는 동안 인디케이터를 보여줄 ProgressBar를 정의
mRegistrationProgressBar = (ProgressBar)findViewById(R.id.registrationProgressBar);
mRegistrationProgressBar.setVisibility(ProgressBar.GONE);
// 토큰을 가져오는 Button을 정의
mRegistrationButton = (Button)findViewById(R.id.registrationButton);
mRegistrationButton.setVisibility(Button.GONE);
mRegistrationButton.setOnClickListener(new View.OnClickListener() {
/**
* 버튼을 클릭하면 토큰을 가져오는 getInstanceIdToken() 메소드를 실행한다.
* @param view
*/
@Override
public void onClick(View view) {
getInstanceIdToken();
}
});
}
/**
* 앱이 실행되어 화면에 나타날때 LocalBoardcastManager에 액션을 정의하여 등록한다.
*/
@Override
protected void onResume() {
super.onResume();
LocalBroadcastManager.getInstance(this).registerReceiver(mRegistrationBroadcastReceiver,
new IntentFilter(QuickstartPreferences.REGISTRATION_READY));
LocalBroadcastManager.getInstance(this).registerReceiver(mRegistrationBroadcastReceiver,
new IntentFilter(QuickstartPreferences.REGISTRATION_GENERATING));
LocalBroadcastManager.getInstance(this).registerReceiver(mRegistrationBroadcastReceiver,
new IntentFilter(QuickstartPreferences.REGISTRATION_COMPLETE));
getInstanceIdToken();
}
/**
* 앱이 화면에서 사라지면 등록된 LocalBoardcast를 모두 삭제한다.
*/
@Override
protected void onPause() {
LocalBroadcastManager.getInstance(this).unregisterReceiver(mRegistrationBroadcastReceiver);
super.onPause();
}
/**
* 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(TAG, "This device is not supported.");
finish();
}
return false;
}
return true;
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.menu_main, menu);
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
// Handle action bar item clicks here. The action bar will
// automatically handle clicks on the Home/Up button, so long
// as you specify a parent activity in AndroidManifest.xml.
int id = item.getItemId();
//noinspection SimplifiableIfStatement
if (id == R.id.action_settings) {
return true;
}
return super.onOptionsItemSelected(item);
}
}
RegistrationIntentService.java
package com.yang.gcmsample;
import android.annotation.SuppressLint;
import android.app.IntentService;
import android.content.Intent;
import android.support.v4.content.LocalBroadcastManager;
import android.util.Log;
import com.google.android.gms.gcm.GoogleCloudMessaging;
import com.google.android.gms.iid.InstanceID;
import java.io.IOException;
/**
* Created by saltfactory on 6/8/15.
*/
public class RegistrationIntentService extends IntentService {
private static final String TAG = "RegistrationIntentService";
public RegistrationIntentService() {
super(TAG);
}
/**
* GCM을 위한 Instance ID의 토큰을 생성하여 가져온다.
* @param intent
*/
@SuppressLint("LongLogTag")
@Override
protected void onHandleIntent(Intent intent) {
// GCM Instance ID의 토큰을 가져오는 작업이 시작되면 LocalBoardcast로 GENERATING 액션을 알려 ProgressBar가 동작하도록 한다.
LocalBroadcastManager.getInstance(this)
.sendBroadcast(new Intent(QuickstartPreferences.REGISTRATION_GENERATING));
// GCM을 위한 Instance ID를 가져온다.
InstanceID instanceID = InstanceID.getInstance(this);
String token = null;
try {
synchronized (TAG) {
// GCM 앱을 등록하고 획득한 설정파일인 google-services.json을 기반으로 SenderID를 자동으로 가져온다.
String default_senderId = getString(R.string.gcm_defaultSenderId);
// GCM 기본 scope는 "GCM"이다.
String scope = GoogleCloudMessaging.INSTANCE_ID_SCOPE;
// Instance ID에 해당하는 토큰을 생성하여 가져온다.
token = instanceID.getToken(default_senderId, scope, null);
Log.i(TAG, "GCM Registration Token: " + token);
}
} catch (IOException e) {
e.printStackTrace();
}
// GCM Instance ID에 해당하는 토큰을 획득하면 LocalBoardcast에 COMPLETE 액션을 알린다.
// 이때 토큰을 함께 넘겨주어서 UI에 토큰 정보를 활용할 수 있도록 했다.
Intent registrationComplete = new Intent(QuickstartPreferences.REGISTRATION_COMPLETE);
registrationComplete.putExtra("token", token);
LocalBroadcastManager.getInstance(this).sendBroadcast(registrationComplete);
}
}
MyInstanceIDListenerService.java
package com.yang.gcmsample;
import android.content.Intent;
import android.util.Log;
import com.google.android.gms.iid.InstanceIDListenerService;
public class MyInstanceIDListenerService extends InstanceIDListenerService {
private static final String TAG = "MyInstanceIDLS";
@Override
public void onTokenRefresh() {
Log.i(TAG, "onTokenRefresh");
Intent intent = new Intent(this, RegistrationIntentService.class);
startService(intent);
}
}
MyGcmListenerService.java
package com.yang.gcmsample;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.media.RingtoneManager;
import android.net.Uri;
import android.os.Bundle;
import android.os.IBinder;
import android.support.v4.app.NotificationCompat;
import android.util.Log;
import com.google.android.gms.gcm.GcmListenerService;
public class MyGcmListenerService extends GcmListenerService {
private static final String TAG = "MyGcmListenerService";
/**
*
* @param from SenderID 값을 받아온다.
* @param data Set형태로 GCM으로 받은 데이터 payload이다.
*/
@Override
public void onMessageReceived(String from, Bundle data) {
String title = data.getString("title");
String message = data.getString("message");
Log.d(TAG, "From: " + from);
Log.d(TAG, "Title: " + title);
Log.d(TAG, "Message: " + message);
// GCM으로 받은 메세지를 디바이스에 알려주는 sendNotification()을 호출한다.
sendNotification(title, message);
}
/**
* 실제 디바에스에 GCM으로부터 받은 메세지를 알려주는 함수이다. 디바이스 Notification Center에 나타난다.
* @param title
* @param message
*/
private void sendNotification(String title, String message) {
Intent intent = new Intent(this, MainActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
PendingIntent pendingIntent = PendingIntent.getActivity(this, 0 /* Request code */, intent,
PendingIntent.FLAG_ONE_SHOT);
Uri defaultSoundUri= RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION);
NotificationCompat.Builder notificationBuilder = new NotificationCompat.Builder(this)
.setSmallIcon(R.drawable.icon_38) // 본인이 사용할 아이콘을 설정하자.
.setContentTitle(title)
.setContentText(message)
.setAutoCancel(true)
.setSound(defaultSoundUri)
.setContentIntent(pendingIntent);
NotificationManager notificationManager =
(NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
notificationManager.notify(0 /* ID of notification */, notificationBuilder.build());
}
}
이제 registrationID를 받아 올 수 있고..이를 이용해서 Push를 받을 준비가 되었다.
자 이제 Device에서 실제로 Push가 오는지 테스트를 하자.
테스트는 아래 사이트에서 가능하다.
https://gcmsender.herokuapp.com/
위의 사이트에 접속해서 API_Key, RegistrationID를 입력하고 내용을 입력하고 Send 버튼을 딱 누르면 해당 앱이 설치된 Device로 메시지가 슝슝 날라오는걸 확인 할 수 있다.
현재 node.js를 이용하여 Push Server를 구성 중 이다. 현재 DB 연동해서 데이터를 가져오는 것 까지 완료된 상태이다. 이것도 정상적으로 완료 되어서 여기에 올릴 수 있으려나...?
참고 사이트 -> http://blog.saltfactory.net/android/implement-push-service-via-gcm.html
'Android' 카테고리의 다른 글
IntentFilter - 전화걸기 (0) | 2016.08.05 |
---|---|
Zxing 라이브러리를 사용한 바코드 스캔 (0) | 2016.01.12 |
APK 파일 디컴파일(Decompile) 하는 방법 (0) | 2015.12.01 |
APK파일 추출하는 방법 (0) | 2015.11.30 |
Failure [INSTALL_FAILED_OLDER_SDK] (0) | 2015.11.25 |