Android/Android Java

[Android Java] China Push, Doze Mode

Bell91 2023. 11. 23. 15:30
반응형

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

반응형