본문 바로가기

dev/flutter

플러터에서 네이티브 앱 생명주기 감지

플러터에서 WidgetBindingObserver 를 이용해 앱이 백그라운드인지 포그라운드인지 등등을 알 수 있다.

하지만 앱이 꺼졌을 때 detach가 감지되지 않았다.

 

그래서 네이티브단에서 감지하여야 한다.

MethodChannel을 이용해서 앱이 종료되었을 때 플러터로 값을 넘기고 앱이 종료되게 해야한다.

 

플러터

getx컨트롤러 이용

init부분에 MethodChannel 수신 코드 등록

 

class MyController extends GetxController {
  final MethodChannel _methodChannel = const MethodChannel("my.channel"); // MethodChannel

  @override
  void onInit() {
    super.onInit();

    // methodChannel 수신
    _methodChannel.setMethodCallHandler((call) async {
      if (call.arguments == "onDestroy") {
        ...
      }
      if (call.arguments == "applicationWillTerminate") {
        ...
      }
    });

  }

  @override
  void onClose() {
    super.onClose();
  }

}

 

안드로이드

MethodChannel을 선언하고 configureFlutterEngine부분에 세팅해준다.

앱이 시작되면 configureFlutterEngine부분에서 플러터엔진을 초기화하는 단계를 거치기 때문에 이 메소드를 오버라이드해서 초기 세팅을 해주면 된다.

그리고 onDestory()를 오버라이드하여 MethodChannel을 이용해 앱이 종료될 때 플러터로 값을 던지게 해준다.

 

class MainActivity: FlutterActivity() {
	
    // MethodChannel 선언
    lateinit var methodChannel: MethodChannel

    override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
        super.configureFlutterEngine(flutterEngine)
        GeneratedPluginRegistrant.registerWith(flutterEngine)
		
        // MethodChannel 세팅
        methodChannel = MethodChannel(
            flutterEngine.dartExecutor,
            "my.channel"
        )
		
        // MethodChannel 수신
        methodChannel.setMethodCallHandler { call, result ->
            result.success("result: ${call.method}")
        }

    }


    override fun onDestroy() {
    	// 앱 종료 시 플러터로 값 전달
        methodChannel.invokeMethod("invokeMethod", "onDestroy")
        super.onDestroy()

    }

}

 

하지만 onDestroy()가 실행될 때 플러터로 값이 전달되지 않았다.

특히 methodChannel이 파괴되어 찾지 못한다는 오류가 떴다. 그래서 쓰레드를 잠시 멈춰주는 코드를 추가하였다.

 

override fun onDestroy() {
    // 앱 종료 시 플러터로 값 전달
    methodChannel.invokeMethod("invokeMethod", "onDestroy")
    Thread.sleep(2000) // 메인 쓰레드 잠시 멈추기
    Log.d("onDestroy","onDestroy")
    super.onDestroy()

}

 

IOS

똑같이 methodChannel을 선언하고 세팅한다.

applicationWillTerminate()이 실행되므로 해당 부분에 값을 보내면 된다.

 

@UIApplicationMain
@objc class AppDelegate: FlutterAppDelegate {

  var methodChannel: FlutterMethodChannel! // methodChannel 선언
    
  override func application(
    _ application: UIApplication,
    didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
  ) -> Bool {
    GeneratedPluginRegistrant.register(with: self)

    // FlutterViewController
    let controller : FlutterViewController = window?.rootViewController as! FlutterViewController

    // methodChannel 세팅
    methodChannel = FlutterMethodChannel(
      name: "my.channel",
      binaryMessenger: controller.binaryMessenger
    )
	
    // methodChannel 수신
    methodChannel.setMethodCallHandler { [weak self] (call, result) in
      result("result : \(call.method)")
    }
      
    return super.application(
      application, 
      didFinishLaunchingWithOptions: launchOptions
    )
  }

  // 앱 종료
  override func applicationWillTerminate(_ application: UIApplication) {
    methodChannel.invokeMethod("invokeMethod", arguments: "applicationWillTerminate")
  }

}

 

IOS도 앱 종료 시 채널로 값을 던지지 못한다.

applicationWillTerminate()에 잠시 멈춰주는 코드를 추가해주면 된다.

 

  override func applicationWillTerminate(_ application: UIApplication) {
    methodChannel.invokeMethod("invokeMethod", arguments: "applicationWillTerminate")
    sleep(2) // 잠시 멈추기
  }

 


+

안드로이드폰의 특성상 백키를 이용해서 앱을 온전히 종료시킬 수 있지만,

가운데 버튼을 클릭하여 백그라운드로 보내고 백그라운드에서 실행되고 있는 앱 리스트에서 종료할 수 있는 방법도 있었다.

이렇게 앱을 강제종료 하는 것은 따로 따로 생명주기가 실행되지 않는다.

 

비정상적인 앱 종료가 있을 때는 또다르게 처리해줘야하는데 서비스를 이용하는 것이다.

앱이 시작될 때 등록하였다가 서비스가 종료될 때 처리해주면 된다.

 

override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
    super.configureFlutterEngine(flutterEngine)
    GeneratedPluginRegistrant.registerWith(flutterEngine)

    methodChannel = MethodChannel(
        flutterEngine.dartExecutor,
        "my.channel"
    )

    methodChannel.setMethodCallHandler { call, result ->
        result.success("result: ${call.method}")
    }

    // Service 등록
    startService(Intent(applicationContext, MyService::class.java))
}

class MyService : Service() {
    override fun onBind(intent: Intent?): IBinder? {
        return null
    }

    override fun onTaskRemoved(rootIntent: Intent) {
        super.onTaskRemoved(rootIntent)
		// 앱 종료 시 처리
        Log.d("MyApp", "앱 종료!")
    }
}