firebase_messaging 백그라운드 작업 통제 android, part 2

flunge·2021년 12월 30일
0

시계는 와치

목록 보기
4/5


실제 패키지의 코드를 분석하는데 시간이 꽤나 걸렸고 이러한 과정을 거친다는걸 알게 되었고 이제 해야할 일은 패키지를 수정하는 것이고 firebase_messaging패키지를 fork해서 내 github로 가져와 수정할 것이다.

fork

flutterfire라는 github저장소가 있고 여기엔 flutter에서 사용하는 firebase관련 패키지들이 모여있다. fork를 해본 경험이 지금까지 없었고 검색해서 나오는 방법들은 단일 패키지에 대한 방법들 뿐이라 여러개가 모여있는 저장소에서 가져오는 방법에 대해서도 고민해야했다.

flutterfire github 저장소로 가서 Fork버튼을 눌러 내 저장소에 가져온다.


이 후에 내 프로젝트의 pubspec.yaml파일의 dependencies안에 이미지처럼 수정해준다.
url은 빨간 네모안에 복사 버튼을 누르고 붙여넣기 하면되고 path는 저장소에 firebase_messaging폴더(pubspec.yaml파일이 있는)를 지정하면된다.

이렇게 가져온 패키지를 사용하면 되는데 firebase_messaging안의 여러 로직들은 firebase_messaging_interface패키지를 통해 사용할 수 있다. 높은 확률로 두 개의 패키지 모두를 수정해야 하는데 firebase_messaging의 pubspec.yaml안의 firebase_messaging_interface패키지의 경로도 수정해야 한다.

//firebase_messaging_platform_interface의 pubspec.yaml
name: firebase_messaging

...

dependencies:
  firebase_core: ^1.10.0
  firebase_core_platform_interface: ^4.2.3
  firebase_messaging_platform_interface:
    git:
      url: https://github.com/shawn-flunge/flutterfire.git
      path: packages/firebase_messaging/firebase_messaging_platform_interface
      ref: master

  # firebase_messaging_web: ^2.2.5
  firebase_messaging_web:
    git:
      url: https://github.com/shawn-flunge/flutterfire.git
      path: packages/firebase_messaging/firebase_messaging_web
      ref: master

  ...

firebase_messaging_platform_interface의 경로를 위에서 한 것처럼 내 저장소로 바꾼다.

수정 전략


Background Isolate가 Isolate.spawn이나 compute로 생성된게 아니라 Isolate간의 소통 방법이 떠오르지 않아 중간 매채체인 android단의 MethodCallHandler를 통해 간접적으로 소통하는 걸 전략으로 생각했다.

  1. 백그라운드 핸들러의 루프안에서 MethodChannel을 통해 지속적으로 isSelected의 상태를 검사한다.
  2. 루프를 종료시키고 싶은 타이밍에 마찬가지로 MethodChannel을 통해 isSelected의 상태를 변경한다.


android단 코드가 위치한 경로

해당 경로안에 있는 파일 중 백그라운드 서비스 로직이 위치한 파일은 빨간 네모 안의 3개이고 가장 아래에 있는 파일이 이번에 수정할 파일이다.

firebase_messaging : Native

public class FlutterFirebaseMessagingPlugin extends BroadcastReceiver
    implements FlutterFirebasePlugin,
        MethodCallHandler,
        NewIntentListener,
        FlutterPlugin,
        ActivityAware {

  private final HashMap<String, Boolean> consumedInitialMessages = new HashMap<>();
  private MethodChannel channel;
  private Activity mainActivity;
  private RemoteMessage initialMessage;
  private static final String TAG = "FLTFireMsgPlugin";
  private static boolean isSelected = false;

가장 아래에 있는것처럼 isSelected변수를 만들어준다. 참고로 Java코드라서 bool이 아니라 boolean이다.

@Override
  public void onMethodCall(final MethodCall call, @NonNull final Result result) {
    Task<?> methodCallTask;

    switch (call.method) {
        
      ...
      
      case "Messaging#askIsSelected":
        Log.i(TAG, "askIsSelected");
        // result.success(isSelected);
        channel.invokeMethod("Messaging#answerIsSelected", isSelected);
        methodCallTask = Tasks.forResult(null);
        break;
      case "Messaging#setIsSelectedFalse":
        Log.i(TAG, "setIsSelectedFalse");
        isSelected = false;
        methodCallTask = Tasks.forResult(null);
        break;
      case "Messaging#setIsSelectedTrue":
        Log.i(TAG, "setIsSelectedTrue");
        isSelected = true;
        methodCallTask = Tasks.forResult(null);
        break;
      default:
        result.notImplemented();
        return;
    }

    ...
    
  }

클래스의 onMethodCall메소드의 구현부이다. 3개의 case를 추가했다. 현재 상태를 답해주는 케이스와 상태를 바꿔주는 2개의 케이스가 있다.

firebase_messaging : Flutter



작업할 파일들이 위치한 경로
이 안의
platform_interface_messaging.dart,
method_channel_messaging.dart가 각각 위치해 있다.

platform_interface_messaging.dart를 method_channel_messaging.dart가 상속받는 구조이고 method_channel_messaging.dart안에 MethodChannel을 이용한 로직들을 구현한다.

platform_interface_messaging.dart

클래스 안에 두 개의 메소드를 구현한다.

  void setIsSelectedTrue(){
    throw UnimplementedError('setIsSelectedTrue() is not implemented');
  }

  void setIsSelectedFalse(){
    throw UnimplementedError('setIsSelectedTrue() is not implemented');
  }

method_channel_messaging.dart

클래스 안에 위에서 만든 메소드 두 개를 재정의한다.

  @override
  void setIsSelectedTrue() {
    channel.invokeMethod('Messaging#setIsSelectedTrue');    
  }

  @override
  void setIsSelectedFalse(){
    channel.invokeMethod('Messaging#setIsSelectedFalse');
  }

내 프로젝트의 백그라운드 핸들러

Future<void> onBackgroundHandler(RemoteMessage message) async {
  const MethodChannel methodChannel = MethodChannel('plugins.flutter.io/firebase_messaging');

  bool isSelected = false;

  methodChannel.setMethodCallHandler((call){

    if(call.method == 'Messaging#answerIsSelected'){
      isSelected = call.arguments as bool;
    }
    return Future.value();
  });
  
  
  Timer.periodic(const Duration(seconds: 5), (timer) {
    methodChannel.invokeMethod('Messaging#askIsSelected');

    if(isSelected) timer.cancel();
    NotificationController().showNotification();
  });

  return Future.value();
}

MethodChannel객체를 생성하고 메소드를 받았을 때 처리하기 위한 로직을 등록하는 setMethodCallHandler메소드 안에 Messaging#answerIsSelected를 받았을 경우 isSeleted변수에 arguments값을 저장하도록 한다.
그러면 아래의 반복 루프안에서 isSeleted의 값을 인지할 수 있게된다.

그런데 코드 분석하고 테스트해본거에 비해 실제로 추가하는 코드가 적은듯;

결과

원래는 앱이 켜지고 화면을 이동해도 계속 notification이 생성 됐지만 이제 그렇지 않다. 그런데 의문인점이 루프의 시간에 상관없이 2번 더 반복을 하고 루프를 벗어난다. 예상되는건 메시지를 주고받는 시간이 있어서 그런게 아닌가 싶다.

전체코드는 github에서 확인할 수 있습니다.
수정한 패키지 링크
현재 진행중인 프로젝트 링크

마치며

이번 프로젝트를 진행하며 처음 시작할 땐 크게 어려울게 없을 줄 알았는데 시작부터 inner shadow구현한다고 생전 안써본 CustomPaint써보고 이번엔 MethodChannel에 Native코드까지 건드렸다. 물론 배운것도 많고 이걸 해내다니 하는 자신감도 생겼지만 역경은 여기서 끝났으면 좋겠다.

0개의 댓글