1. China Push란?
중국 내부에서는 기본적으로 Google Firebase FCM 서비스가 아닌 Baidu 서비스를 사용하고 있다. Baidu는 디바이스에 별도로 인증을 받아야 하기 때문에(White List) 일반적으로 중국디바이스로는 푸시 서비스를 이용할 수 없다. 하여 몇가지 특별한 방법으로 중국디바이스에 푸시서비스를 받을 수 있도록 한다.
- Baidu : 중국 디바이스의 공식적인 푸시 서비스
- Mqtt : Rabbit MQ를 이용한 푸시 서비스
- Pushy : 독자적인 글로벌 푸시 서비스
2. 중국 디바이스의 종류
중국 디바이스는 글로벌용과 내수용 두가지로 나뉜다. 푸시서비스에 있어 두가지 종류의 큰 차이는 없으나 내수용은 한글 번역을 지원하지 않는 경우도 있다. 중국은 각각의 제조사가 서로 다른 커스텀 사항을 가지고 있어 이부분을 파악해야 한다.
- OPPO
- VIVO
- Huawei
- Honor
- Xaomi
3. Doze Mode란?
사용자가 충전을 하고 있지 않은 상황에서 화면이 꺼진 상태로 오랫동안 대기 모드가 지속되면 도즈모드로 진입하게 된다. 도즈모드는 사용자가 휴대폰의 베터리를 오랫동안 사용할 수 있도록 베터리 최적화를 진행한다. 그 과정에서 특정 앱의 Wifi혹은 데이터 연결상태를 끊거나, 휴먼상태로 전환한다. 중국 디바이스의 푸시서비스는 이러한 문제점을 해결하고 푸시를 받아야 한다.
4. Push
기본적으로 Firebase의 FCM과 비슷한 구조를 가지고 있다. 사용하는 부분에 있어서는 크게 차이가 없으나 FCM과 같이 사용한다면 별도의 분기처리를 해주도록 하자.
(1) PushReceiver.class
public class PushReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
// Attempt to extract the "title" property from the data payload, or fallback to app shortcut label
String notificationTitle = intent.getStringExtra("title") != null ? intent.getStringExtra("title") : context.getPackageManager().getApplicationLabel(context.getApplicationInfo()).toString();
// Attempt to extract the "message" property from the data payload: {"message":"Hello World!"}
//String notificationText = intent.getStringExtra("message") != null ? intent.getStringExtra("message") : "Test notification";
String notificationText = intent.getStringExtra("k3") != null ? intent.getStringExtra("k3") : "Test notification";
Log.d(TAG, "onReceive get : " + notificationTitle + " " + notificationText);
// Prepare a notification with vibration, sound and lights
NotificationCompat.Builder builder = new NotificationCompat.Builder(context)
.setAutoCancel(true)
.setSmallIcon(android.R.drawable.ic_dialog_info)
.setContentTitle(notificationTitle)
.setContentText(notificationText)
.setLights(Color.RED, 1000, 1000)
.setVibrate(new long[]{0, 400, 250, 400})
.setSound(RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION))
.setContentIntent(PendingIntent.getActivity(context, 0, new Intent(context, MainActivity.class), PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE));
// Automatically configure a Notification Channel for devices running Android O+
Pushy.setNotificationChannel(builder, context);
// Get an instance of the NotificationManager service
NotificationManager notificationManager = (NotificationManager) context.getSystemService(context.NOTIFICATION_SERVICE);
// Build the notification and display it
// Use a random notification ID so multiple
// notifications don't overwrite each other
notificationManager.notify((int)(Math.random() * 100000), builder.build());
}
}
(2) MainActivity.class
public class MainActivity extends AppCompatActivity {
private final static String TAG = MainActivity.class.getSimpleName();
TextView mInstructions;
TextView mDeviceToken;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
PowerManager powerManager = (PowerManager) getSystemService(Context.POWER_SERVICE);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && !powerManager.isIgnoringBatteryOptimizations(getPackageName())) {
startActivity(new Intent(Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS, Uri.parse("package:"+getPackageName())));
}
// Cache TextView objects
mDeviceToken = findViewById(R.id.deviceToken);
mInstructions = findViewById(R.id.instructions);
//Push
if (!Pushy.isRegistered(this)) {
new RegisterForPushNotificationsAsync().execute();
}else {
// Start Pushy notification service if not already running
Pushy.listen(this);
// Update UI with device token
updateUI();
}
// Enable FCM Fallback Delivery
//Pushy.toggleFCM(true, this);
}
private class RegisterForPushNotificationsAsync extends AsyncTask<String, Void, Exception> {
ProgressDialog mLoading;
public RegisterForPushNotificationsAsync() {
// Create progress dialog and set it up
mLoading = new ProgressDialog(MainActivity.this);
mLoading.setMessage(getString(R.string.registeringDevice));
mLoading.setCancelable(false);
// Show it
mLoading.show();
}
protected Exception doInBackground(String... params) {
try {
// Register the device for notifications (replace MainActivity with your Activity class name)
String deviceToken = Pushy.register(MainActivity.this);
// Save token locally / remotely
saveDeviceToken(deviceToken);
// Registration succeeded, log token to logcat
Log.d("Pushy", "Pushy device token: " + deviceToken);
// Send the token to your backend server via an HTTP GET request
new URL("https://{YOUR_API_HOSTNAME}/register/device?token=" + deviceToken).openConnection();
// Provide token to onPostExecute()
} catch (Exception exc) {
// Registration failed, provide exception to onPostExecute()
return exc;
}
// Success
return null;
}
@Override
protected void onPostExecute(Exception exc) {
// Activity died?
if (isFinishing()) {
return;
}
// Hide progress bar
mLoading.dismiss();
// Registration failed?
if (exc != null) {
// Write error to logcat
Log.e("Pushy", "Registration failed: " + exc.getMessage());
// Display error dialog
new AlertDialog.Builder(MainActivity.this).setTitle(R.string.registrationError)
.setMessage(exc.getMessage())
.setPositiveButton(R.string.ok, null)
.create()
.show();
}
// Update UI with registration result
updateUI();
}
}
private void updateUI() {
// Get device token from SharedPreferences
String deviceToken = getDeviceToken();
// Registration failed?
if (deviceToken == null) {
// Display registration failed in app UI
mInstructions.setText(R.string.restartApp);
mDeviceToken.setText(R.string.registrationFailed);
// Stop execution
return;
}
// Display device token
mDeviceToken.setText(deviceToken);
// Display "copy from logcat" instructions
mInstructions.setText(R.string.copyLogcat);
// Write device token to logcat
Log.d("Pushy", "Device token: " + deviceToken);
}
private String getDeviceToken() {
// Get token stored in SharedPreferences
return getSharedPreferences().getString("deviceToken", null);
}
private void saveDeviceToken(String deviceToken) {
// Save token locally in app SharedPreferences
getSharedPreferences().edit().putString("deviceToken", deviceToken).commit();
// Your app should store the device token in your backend database
//new URL("https://{YOUR_API_HOSTNAME}/register/device?token=" + deviceToken).openConnection();
}
private SharedPreferences getSharedPreferences() {
return PreferenceManager.getDefaultSharedPreferences(this);
}
}
중국 디바이스에 국내에서 제공되는 FCM 정도의 푸시 서비스를 제공하기란 어려운 것 같다. 참고로 중국 내부에서 승인된 화이트리스트에 들 수 없다면 스와이프 종료(onDestroy) 상황에서는 대응이 불가능하다. 중국 디바이스에는 글로벌 용도 있지만 내수용까지 모두 다하면 10가지 정도가 된다. 전부 설정값 확인하고 테스트하기에 쉽지 않은 과정이었다. 충전 꽂으면 도즈 모드가 풀리고 화면을 켜도 도즈 모드가 풀린다. 처음엔 이걸 모르고 푸시가 들어왔다 안 들어왔다 해서 고생을 많이 했다. 또 기기마다 퍼미션을 넣어도 승인 시 설정이 적용되지 않는 폰이 있지 않은가?(사실 거의 다 그럼) 오랜 기간 동안 중국 디바이스에 푸시 서비스를 넣으려고 했고 실제로 성공했지만 권장하지는 않는다.
Reference
https://developer.android.com/training/monitoring-device-state/doze-standby?hl=ko
'Android > Android Java' 카테고리의 다른 글
[Android Java] TreeView (0) | 2023.12.20 |
---|---|
[Android Java] 사진, 동영상 촬영(ActivityResultLauncher, Scoped Storage) (0) | 2023.11.22 |
[Android Java] Scoped Storage 대응 (1) | 2023.11.21 |
[Android Java] Notification Action, RemoteInput (1) | 2023.11.13 |
[Android Java] Notification (0) | 2023.11.09 |