Flutter aspectd(五)全域性監控flutter生命週期

老毛發表於2021-07-26

簡介

當我們在做效能收集時,需要全域性的知道哪個頁面目前在展示,哪個頁面關閉了,從而做一些收集工作,在Android中我們可以通過registerActivityLifecycleCallbacks來得到任何一個正在展示頁面的生命週期

如下:

applicationContext.registerActivityLifecycleCallbacks(object : Application.ActivityLifecycleCallbacks {
  override fun onActivityCreated(activity: Activity, savedInstanceState: Bundle?) {

  }

  override fun onActivityStarted(activity: Activity) {
  }

  override fun onActivityResumed(activity: Activity) {
    
  }

  override fun onActivityPaused(activity: Activity) {
    
  }

  override fun onActivityStopped(activity: Activity) {
  }

  override fun onActivitySaveInstanceState(activity: Activity, outState: Bundle) {
  }

  override fun onActivityDestroyed(activity: Activity) {
  }
})
複製程式碼

而在Flutter中並沒有提供類似方式,當然我們可以通過一個一個頁面的監聽,但這樣侵入性太強了,現在嘗試用aspectd來hook具體執行方法實現生命週期監聽。

頁面啟動

當我們要開啟一個新的flutter頁面會執行如下:

Navigator.pushNamed(context, RouteHelper.firstPage);
複製程式碼

最終會執行navigator.dart中的handlePush方法,hook該方法:

@Execute("package:flutter/src/widgets/navigator.dart", "_RouteEntry", "-handlePush")
@pragma("vm:entry-point")
void _handlePush(PointCut pointCut) {
    print("++++_handlePush++++");
    pointCut.proceed();
    dynamic target = pointCut.target;
    dynamic previousRoute= pointCut.namedParams["previousPresent"];
    HookImpl.getInstance().handlePush(target.route,previousRoute);
}
複製程式碼

從該方法中可以得到我們要啟動頁面的Route,以及當前的頁面Route

接下來會呼叫buildPage方法:

@Execute("package:flutter/src/material/page.dart","MaterialRouteTransitionMixin", "-buildPage")
@pragma("vm:entry-point")
dynamic _buildPage(PointCut pointCut) {
    print("++++_buildPage++++");
    Route target = pointCut.target;
    Semantics widgetResult = pointCut.proceed();
    HookImpl.getInstance().buildPage(
        target, widgetResult.child, pointCut.positionalParams[0]);
    return widgetResult;
}
複製程式碼

通過該方法能夠得到要啟動頁面的Route,Widget(更具它能取到頁面標題)及BuildContext物件。

最終會繪製這個要啟動的頁面:

@Execute("package:flutter/src/scheduler/binding.dart", "SchedulerBinding","-handleDrawFrame")
@pragma("vm:entry-point")
void _handleDrawFrame(PointCut pointCut) {
    print("++++_handleDrawFrame++++");
    pointCut.proceed();
    HookImpl.getInstance().handleDrawFrame();
}
複製程式碼

當該方法執行完後,頁面被繪製出來

頁面關閉

當關閉一個頁面時,會呼叫pop方法:

@Execute("package:flutter/src/widgets/navigator.dart", "_RouteEntry", "-handlePop")
@pragma("vm:entry-point")
void _handlePop(PointCut pointCut) {
    print("++++handlePop++++");
    dynamic target = pointCut.target;
    dynamic previousPresent = pointCut.namedParams["previousPresent"];
    pointCut.proceed();
    HookImpl.getInstance().handlePop(target.route, previousPresent);
}
複製程式碼

這裡的target就是當前要pop的頁面對應的Route,而previousPresent是我們要回到的頁面對應的Route。

接著就會再次走handleDrawFrame進行繪製我們要回到的頁面。

通過整合上面的方法,就可以得到頁面的開啟和關閉的回撥。但是,這些只是Flutter頁面之間的跳轉,當中間參和原生頁面,或者我們切換到後臺,再回來就監聽不到了。

監聽和原生之間切換

flutter頁面本質上還是原生的殼,那麼我們可以通過監聽原生的生命週期,通過channel通知flutter,而在flutter中通過aspectd hook到生命週期切換,拿到當前的頁面,從而實現監聽

原生監聽生命週期

 applicationContext.registerActivityLifecycleCallbacks(object : Application.ActivityLifecycleCallbacks {
  override fun onActivityCreated(activity: Activity, savedInstanceState: Bundle?) {

  }

  override fun onActivityStarted(activity: Activity) {
  }

  override fun onActivityResumed(activity: Activity) {
    channel.invokeMethod("onActivityResumed", mapOf("activityName" to activity.javaClass.simpleName))
  }

  override fun onActivityPaused(activity: Activity) {
    channel.invokeMethod("onActivityPaused", mapOf("activityName" to activity.javaClass.simpleName))
  }

  override fun onActivityStopped(activity: Activity) {
  }

  override fun onActivitySaveInstanceState(activity: Activity, outState: Bundle) {
  }

  override fun onActivityDestroyed(activity: Activity) {
  }
})
複製程式碼

上面在onActivityResumed和onActivityPaused的時候通過channel傳遞給flutter。

flutter這裡接收:

  void init() {
    _channel.setMethodCallHandler(flutterMethod);
  }

  Future<dynamic> flutterMethod(MethodCall call) async{
    if (call.method == 'onActivityResumed') {
      onActivityResumed(call.arguments['activityName']);
    } else if (call.method == 'onActivityPaused') {
      onActivityPaused(call.arguments['activityName']);
    }
  }

  void onActivityResumed(String activityName) {
    print('onActivityResumed:::$activityName');
  }

  void onActivityPaused(String activityName) {
    print('onActivityPaused:::$activityName');
  }
複製程式碼

這裡抽出來2個方法,供aspect進行hook:

 @Execute(
      "package:lifecycle_detect/lifecycle_detect.dart", "LifecycleDetect", "-onActivityResumed")
  @pragma("vm:entry-point")
  void _onActivityResumed(PointCut pointCut) {
    print("++++onActivityResumed++++");
    pointCut.proceed();
    HookImpl.getInstance().onActivityResumed();
  }

  @Execute(
      "package:lifecycle_detect/lifecycle_detect.dart", "LifecycleDetect", "-onActivityPaused")
  @pragma("vm:entry-point")
  void _onActivityPaused(PointCut pointCut) {
    print("++++onActivityPaused++++");
    pointCut.proceed();
    HookImpl.getInstance().onActivityPaused();
  }
複製程式碼

這樣,flutter和原生之間切換時,生命週期也可以拿到了。

效果

image

githbu地址

相關文章