Android 메시지 푸시 연동 문서
자동 회수 방식
자동 회수 방식은 SDK 내부에서 각 플랫폼(현재는 FCM과 JPush)의 푸시 token과 푸시 메시지 클릭 이벤트(te_ops_push_click)를 자동으로 전송하는 방식입니다.
연동 절차:
- 데이터 수집 SDK를 추가합니다. 버전은 3.0.1 이상이어야 합니다
implementation 'cn.thinkingdata.android:ThinkingAnalyticsSDK:3.0.1'
- 자동 수집 플러그인을 추가합니다. 플러그인 버전은 데이터 수집 SDK 버전에 따라 선택합니다. SDK 3.0.1~3.1.0은 2.1.0을, 3.1.0보다 높은 버전은 2.2.0을 사용합니다
buildscript {
repositories {
google()
jcenter()
}
dependencies {
classpath 'cn.thinkingdata.android:android-gradle-plugin2:2.1.0'
}
}
프로젝트의 build.gradle 파일에서 플러그인 관련 파라미터를 설정합니다
apply plugin: 'cn.thinkingdata.android'
android {
}
- 푸시 경로 자동 회수를 활성화합니다
val config = TDConfig.getInstance(
this,
TA_APP_ID,
TA_SERVER_URL
)
//푸시 자동 회수 활성화
config.enableAutoPush()
TDAnalytics.init(config)
- 계정 전환
계정을 전환한 후에는 login 인터페이스를 호출해야 합니다. SDK 내부에서 푸시 token을 user_set 인터페이스를 통해 새 계정으로 자동 전송합니다.
TDAnalytics.login("new_account_id")
1. FCM 푸시
FirebaseMessagingService를 커스터마이즈합니다. 이 방식은 컴파일 시점에 FirebaseMessagingService의 onNewToken 메서드를 hook하는 원리이므로 이 클래스를 제공해야 합니다.
class MyFirebaseMessagingService extends FirebaseMessagingService {
@Override
public void onNewToken(@NonNull String token) {
}
}
manifest 파일에 등록합니다
<service
android:name="커스텀 service"
android:exported="false">
<intent-filter>
<action android:name="com.google.firebase.MESSAGING_EVENT" />
</intent-filter>
</service>
2. JPush 푸시
JPushMessageReceiver를 커스터마이즈합니다. 이 방식은 컴파일 시점에 JPushMessageReceiver의 onRegister 메서드를 hook하는 원리이므로 이 클래스를 제공해야 합니다.
public class PushMessageReceiver extends JPushMessageReceiver {
@Override
public void onRegister(Context context, String registrationId) {
}
}
manifest 파일에 등록합니다
<receiver
android:name="커스텀 Receiver"
android:enabled="true"
android:exported="false">
<intent-filter>
<action android:name="cn.jpush.android.intent.RECEIVE_MESSAGE" />
<category android:name="앱 패키지 이름" />
</intent-filter>
</receiver>
수동 회수 방식
1. FCM 푸시
1.1 "푸시 ID" 전송
- AE login을 호출하거나 계정을 전환한 후 FCM의 Token을 업로드합니다.
// AE SDK 초기화
TDAnalytics.init(this, APPID, SERVER_URL);
TDAnalytics.login("user_id");
FirebaseMessaging.getInstance().getToken()
.addOnCompleteListener(new OnCompleteListener<String>() {
@Override
public void onComplete(@NonNull Task<String> task) {
if (!task.isSuccessful()) {
Log.w(TAG, "Fetching FCM registration token failed", task.getException());
return;
}
// Get new FCM registration token
String token = task.getResult();
JSONObject properties = new JSONObject();
properties.put("fcm_token",token);
TDAnalytics.userSet(properties);
}
});
- FCM Token이 변경되면 유저 속성을 업데이트합니다:
@Override
public void onNewToken(@NonNull String token) {
JSONObject properties = new JSONObject();
properties.put("fcm_token",token);
TDAnalytics.userSet(properties);
}
1.2. 푸시 클릭 이벤트 수집
유저가 알림을 클릭할 때 푸시 클릭 이벤트를 업로드합니다. onCreate 또는 onNewIntent에서 푸시 파라미터를 가져올 수 있습니다.
public class PushOpenClickActivity extends AppCompatActivity {
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
handlePushOpen();
}
@Override
protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
handlePushOpen();
}
private void handlePushOpen() {
try {
Intent intent = getIntent();
if (intent == null) {
return;
}
Bundle bundle = intent.getExtras();
if (bundle == null) {
return;
}
String teExtras = bundle.getString("te_extras");
TEPushUtil.trackAppOpenNotification(teExtras);
} catch (Exception e) {
e.printStackTrace();
}
}
}
1.3. 푸시 메시지 처리
다음 두 가지 방식 중 하나를 선택할 것을 권장합니다
- 일반 파라미터: handleTEPushAction 메서드를 호출합니다.
- 패스스루 파라미터: handleTEPassThroughAction 메서드를 호출합니다.
public class PushOpenClickActivity extends AppCompatActivity {
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
handlePushOpen();
}
@Override
protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
handlePushOpen();
}
private void handlePushOpen() {
try {
Intent intent = getIntent();
if (intent == null) {
return;
}
Bundle bundle = intent.getExtras();
if (bundle == null) {
return;
}
String teExtras = bundle.getString("te_extras");
//일반 파라미터를 처리하는 경우 다음 메서드를 호출
TEPushUtil.handleTEPushAction(teExtras);
//패스스루 파라미터를 처리하는 경우 다음 메서드를 호출
TEPushUtil.handleTEPassThroughAction(teExtras);
} catch (Exception e) {
e.printStackTrace();
}
}
}
2. JPush 푸시
2.1 "푸시 ID" 전송
- AE login을 호출하거나 계정을 전환한 후 JPush의 Registration ID를 업로드합니다.
//로그인하거나 계정을 전환한 후 호출
TDAnalytics.login("user_id");
JSONObject properties = new JSONObject();
properties.put("jiguang_id",JPushInterface.getRegistrationID(context));
//instance는 AE 인스턴스
TDAnalytics.userSet(properties);
- JPush가 제공하는 onRegister 인터페이스에서 JPush의 Registration ID를 업로드합니다.
public class PushMessageReceiver extends JPushMessageReceiver {
@Override
public void onRegister(Context context, String registrationId) {
JSONObject properties = new JSONObject();
properties.put("jiguang_id",registrationId);
//instance는 AE 인스턴스
TDAnalytics.userSet(properties);
}
}
2.2. 푸시 클릭 이벤트 수집
제조사 채널이 아닌 경우 onNotifyMessageOpened 인터페이스에서 푸시 클릭 이벤트를 보낼 수 있습니다.
public class PushMessageReceiver extends JPushMessageReceiver {
@Override
public void onNotifyMessageOpened(Context context, NotificationMessage message) {
//알림 클릭 콜백에서 알림 클릭 이벤트 전송
String te_extras = TEPushUtil.getPushExtras(message.notificationExtra);
TEPushUtil.trackAppOpenNotification(te_extras);
}
}
제조사 채널을 사용하는 경우 제조사 채널 Activity의 onCreate, onNewIntent에서 intent를 가져와 해당 파라미터를 파싱한 후 푸시 클릭 이벤트를 보내야 합니다.
public class PushOpenClickActivity extends AppCompatActivity {
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
handlePushOpen();
}
@Override
protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
handlePushOpen();
}
private void handlePushOpen() {
try {
Intent intent = getIntent();
if (intent == null) {
return;
}
String pushData = null;
// Huawei 채널 메시지 데이터
if (getIntent().getData() != null) {
pushData = getIntent().getData().toString();
}
// Xiaomi, vivo, OPPO, FCM 채널 메시지 데이터(Meizu는 onNotifyMessageOpened 콜백 호출)
if (TextUtils.isEmpty(pushData) && getIntent().getExtras() != null) {
pushData = getIntent().getExtras().getString("JMessageExtra");
}
if (TextUtils.isEmpty(pushData)) {
return;
}
JSONObject jsonObject = new JSONObject(pushData);
// 푸시 메시지 추가 필드
String extras = jsonObject.optString("n_extras");
String te_extras = TEPushUtil.getPushExtras(extras);
TEPushUtil.trackAppOpenNotification(te_extras);
} catch (Exception e) {
e.printStackTrace();
}
}
}
2.3. 푸시 메시지 처리
다음 두 가지 방식 중 하나를 선택할 것을 권장합니다
- 일반 파라미터: handleTEPushAction 메서드를 호출합니다.
- 패스스루 파라미터: handleTEPassThroughAction 메서드를 호출합니다.
제조사 채널이 아닌 경우 onNotifyMessageOpened 인터페이스에서 푸시 메시지를 처리할 수 있습니다.
public class PushMessageReceiver extends JPushMessageReceiver {
@Override
public void onNotifyMessageOpened(Context context, NotificationMessage message) {
//알림 클릭 콜백에서 푸시 파라미터 처리
String teExtras = TEPushUtil.getPushExtras(message.notificationExtra);
//일반 파라미터를 처리하는 경우 다음 메서드를 호출
TEPushUtil.handleTEPushAction(teExtras);
//패스스루 파라미터를 처리하는 경우 다음 메서드를 호출
TEPushUtil.handleTEPassThroughAction(teExtras);
}
}
제조사 채널을 사용하는 경우 제조사 채널 Activity의 onCreate, onNewIntent에서 intent를 가져와 해당 파라미터를 파싱한 후 푸시 메시지를 처리해야 합니다.
public class PushOpenClickActivity extends AppCompatActivity {
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
handlePushOpen();
}
@Override
protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
handlePushOpen();
}
private void handlePushOpen() {
try {
Intent intent = getIntent();
if (intent == null) {
return;
}
String pushData = null;
// Huawei 채널 메시지 데이터
if (getIntent().getData() != null) {
pushData = getIntent().getData().toString();
}
// Xiaomi, vivo, OPPO, FCM 채널 메시지 데이터(Meizu는 onNotifyMessageOpened 콜백 호출)
if (TextUtils.isEmpty(pushData) && getIntent().getExtras() != null) {
pushData = getIntent().getExtras().getString("JMessageExtra");
}
if (TextUtils.isEmpty(pushData)) {
return;
}
JSONObject jsonObject = new JSONObject(pushData);
// 푸시 메시지 추가 필드
String extras = jsonObject.optString("n_extras");
String teExtras = TEPushUtil.getPushExtras(extras);
//일반 파라미터를 처리하는 경우 다음 메서드를 호출
TEPushUtil.handleTEPushAction(teExtras);
//패스스루 파라미터를 처리하는 경우 다음 메서드를 호출
TEPushUtil.handleTEPassThroughAction(teExtras);
} catch (Exception e) {
e.printStackTrace();
}
}
}
부록
클라이언트가 수신하는 푸시 파라미터 예시
다음은 클라이언트가 수신하는 확장 필드 파라미터만 보여 줍니다
{
"te_extras": {
//푸시 클릭 시 이동 방식
"ops_loading_type": "OPEN_APP",
//패스스루 파라미터
"passthrough_params": {
"param1": "abc",
"param2": 101,
"param3": [{
"subText1": "xyz",
"subText2": 2
}]
},
//AE 운영 채널 회신 속성
"#ops_receipt_properties": {
"ops_task_id": "0082",
"ops_project_id": 1,
"ops_task_instance_id": "0082_20230331",
"ops_push_language": "default",
"ops_task_exec_detail_id": "55"
}
}
}
고객의 푸시 회수 경로 연동 성공 여부를 확인하는 방법
- 푸시를 연동한 후 처음 실행하거나 푸시 token이 변경될 때 다음 이벤트가 전송되는지 확인합니다.
{
"#type": "user_set",
"#time": "2023-11-13 15:50:55.729",
"#distinct_id": "distinct",
"properties": {
"jiguang_id": "190e35f7e15c8481caa"
},
"#uuid": "9f233c31-a664-46ff-94d6-f767a3098c3a"
}
- 푸시 알림을 클릭하여 앱을 실행한 후 te_ops_push_click 이벤트가 업로드되는지, 이벤트 속성에 ops_receipt_properties가 포함되어 있는지 확인합니다.
{
"#type": "track",
"#time": "2023-03-16 16:08:32.191",
"#distinct_id": "90d80464-6832-43f1-80d9-bd93fc09c4fe",
"#event_name": "te_ops_push_click",
"properties": {
"#lib_version": "3.0.1-beta.1",
"#carrier": "中国移动",
"#os": "Android",
"#device_id": "6262ca7f71e6aca3",
"#screen_height": 2400,
"#bundle_id": "cn.thinkingdata.random",
"#device_model": "M2012K11AC",
"#screen_width": 1080,
"#system_language": "zh",
"#install_time": "2023-03-10 11:24:44.285",
"#simulator": false,
"#lib": "Android",
"#manufacturer": "Xiaomi",
"#os_version": "11",
"#app_version": "1.0",
"#fps": 60,
"#network_type": "WIFI",
"#ram": "2.7\/7.4",
"#disk": "4.6\/106.3",
"#device_type": "Phone",
"ops_receipt_properties": {
"ops_project_id": 1,
"ops_request_id": "3b21d2a8-8d3d-44fa-b460-3bb311ed3bcd"
},
"#zone_offset": 8
},
"#uuid": "7a977e23-b78a-4433-baae-ead17ad2fde9"
}
trackAppOpenNotification
public static void trackAppOpenNotification(String extras) {
try {
if (TextUtils.isEmpty(extras)) {
return;
}
JSONObject jsonObject = new JSONObject(extras);
JSONObject properties = new JSONObject();
Object obj = jsonObject.opt("#ops_receipt_properties");
JSONObject ops = null;
if (obj instanceof String) {
ops = new JSONObject(( String ) obj);
} else if (obj instanceof JSONObject) {
ops = ( JSONObject ) obj;
}
properties.put("#ops_receipt_properties", ops);
TDAnalytics.track("te_ops_push_click", properties);
//즉시 전송
TDAnalytics.flush();
} catch (Exception e) {
e.printStackTrace();
}
}
handleTEPushAction
public static void handleTEPushAction(String extras) {
try {
if (TextUtils.isEmpty(extras)) {
return;
}
JSONObject jsonObject = new JSONObject(extras);
String type = jsonObject.optString("ops_loading_type");
if ("OPEN_APP".equals(type)) {
// TODO App 열기 메시지 처리 --> App을 실행하십시오
} else if ("OPEN_URL".equals(type)) {
String url = jsonObject.optString("ops_url");
if (!TextUtils.isEmpty(url)) {
// TODO URL 열기 메시지 처리 --> URL을 처리하십시오
}
} else if ("CUSTOMIZED".equals(type)) {
String custom = jsonObject.optString("ops_customized");
if (!TextUtils.isEmpty(custom)) {
// TODO 커스텀 메시지 처리 --> 커스텀 메시지를 처리하십시오
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
handleTEPassThroughAction
public static void handleTEPassThroughAction(String extras) {
try {
if (TextUtils.isEmpty(extras)) {
return;
}
JSONObject jsonObject = new JSONObject(extras);
String params = jsonObject.optString("passthrough_params");
//params는 패스스루 파라미터이며, 이어서 구체적인 비즈니스 로직을 구현합니다
} catch (Exception e) {
e.printStackTrace();
}
}
TEPushUtil 클래스
public class TEPushUtil {
public static void trackAppOpenNotification(String extras) {
try {
if (TextUtils.isEmpty(extras)) {
return;
}
JSONObject jsonObject = new JSONObject(extras);
JSONObject properties = new JSONObject();
Object obj = jsonObject.opt("#ops_receipt_properties");
JSONObject ops = null;
if (obj instanceof String) {
ops = new JSONObject(( String ) obj);
} else if (obj instanceof JSONObject) {
ops = ( JSONObject ) obj;
}
properties.put("#ops_receipt_properties", ops);
TDAnalytics.track("te_ops_push_click", properties);
//즉시 전송
TDAnalytics.flush();
} catch (Exception e) {
e.printStackTrace();
}
}
public static void handleTEPushAction(String extras) {
try {
if (TextUtils.isEmpty(extras)) {
return;
}
JSONObject jsonObject = new JSONObject(extras);
String type = jsonObject.optString("ops_loading_type");
if ("OPEN_APP".equals(type)) {
// TODO App 열기 메시지 처리 --> App을 실행하십시오
} else if ("OPEN_URL".equals(type)) {
String url = jsonObject.optString("ops_url");
if (!TextUtils.isEmpty(url)) {
// TODO URL 열기 메시지 처리 --> URL을 처리하십시오
}
} else if ("CUSTOMIZED".equals(type)) {
String custom = jsonObject.optString("ops_customized");
if (!TextUtils.isEmpty(custom)) {
// TODO 커스텀 메시지 처리 --> 커스텀 메시지를 처리하십시오
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
public static void handleTEPassThroughAction(String extras) {
try {
if (TextUtils.isEmpty(extras)) {
return;
}
JSONObject jsonObject = new JSONObject(extras);
String params = jsonObject.optString("passthrough_params");
//params는 패스스루 파라미터이며, 이어서 구체적인 비즈니스 로직을 구현합니다
} catch (Exception e) {
e.printStackTrace();
}
}
public static String getPushExtras(Object notificationExtras) {
String teExtras = "";
try {
if (notificationExtras != null) {
if (notificationExtras instanceof String) {
teExtras = new JSONObject((String) notificationExtras).optString("te_extras");
} else if (notificationExtras instanceof Map) {
teExtras = new JSONObject((Map) notificationExtras).optString("te_extras");
}
}
} catch (Exception e) {
e.printStackTrace();
}
return teExtras;
}
}

