主頁 > 移動端開發 > Flutter 繪制動機 VSYNC 流程原始碼全方位分析

Flutter 繪制動機 VSYNC 流程原始碼全方位分析

2021-08-20 08:35:41 移動端開發

Flutter 系列文章連載~
《Flutter Android 工程結構及應用層編譯原始碼深入分析》
《Flutter 命令本質之 Flutter tools 機制原始碼深入分析》
《Flutter 的 runApp 與三棵樹誕生流程原始碼分析》
《Flutter Android 端 Activity/Fragment 流程原始碼分析》
《Flutter Android 端 FlutterInjector 及依賴流程原始碼分析》
《Flutter Android 端 FlutterEngine Java 相關流程原始碼分析》
《Flutter Android 端 FlutterView 相關流程原始碼分析》
《Flutter 繪制動機 VSYNC 流程原始碼全方位分析》

背景

前面系列我們依賴 Android 平臺實作分析了端側很多機制,但是有一個知識點一直比較迷糊,那就是 Flutter 是怎么被觸發繪制的?這個問題在網上的答案基本都說 VSYNC,但是少有人說這個 VSYNC 是怎么被關聯起來的,本文就針對這個問題進行一個 Platform 到 Engine 到 Dart Framework 分析,原始碼依賴 Flutter 2.2.3,

Android 平臺 Java 層

還記得我們前面系列文章分析過的io.flutter.embedding.engine.FlutterJNI嗎,FlutterJNI 的作用就是架起 Android 端 Java 與 Flutter Engine C/C++ 端的一座介面橋梁,記不記得當時我們分析 FlutterEngine 時(《Flutter Android 端 FlutterEngine Java 相關流程原始碼分析》)在他的實體化程序中有這么一段呼叫邏輯:

-> 呼叫 FlutterEngine 構造方法
-> 呼叫 FlutterLoader 的 startInitialization(context.getApplicationContext()) 方法
-> 呼叫 VsyncWaiter.getInstance((WindowManager) appContext.getSystemService(Context.WINDOW_SERVICE)).init() 方法

基于上面流程,我們把重點轉向 Java 端的 VsyncWaiter 類及其 init 方法,如下:

//行程單例實體類
public class VsyncWaiter {
  //......
  //一個來自engine觸發的回呼
  private final FlutterJNI.AsyncWaitForVsyncDelegate asyncWaitForVsyncDelegate =
      new FlutterJNI.AsyncWaitForVsyncDelegate() {
        @Override
        public void asyncWaitForVsync(long cookie) {
          //每逢回呼回來就向Choreographer post一個繪制VSYNC請求,
          Choreographer.getInstance()
              .postFrameCallback(
                  new Choreographer.FrameCallback() {
                    @Override
                    public void doFrame(long frameTimeNanos) {
                      float fps = windowManager.getDefaultDisplay().getRefreshRate();
                      long refreshPeriodNanos = (long) (1000000000.0 / fps);
                      //呼叫FlutterJNI的nativeOnVsync邏輯通知VSYNC
                      FlutterJNI.nativeOnVsync(
                          frameTimeNanos, frameTimeNanos + refreshPeriodNanos, cookie);
                    }
                  });
        }
      };
  //......
  //唯一被呼叫的方法
  public void init() {
    //設定委托實體回呼參考
    FlutterJNI.setAsyncWaitForVsyncDelegate(asyncWaitForVsyncDelegate);

    //傳輸fps值給engine
    float fps = windowManager.getDefaultDisplay().getRefreshRate();
    FlutterJNI.setRefreshRateFPS(fps);
  }
}

我們簡單看下 FlutterJNI 里面是怎么做的,如下:

@Keep
public class FlutterJNI {
  //......
  //VsyncWaiter.init方法設定的委托回呼實作就是賦值給了他
  private static AsyncWaitForVsyncDelegate asyncWaitForVsyncDelegate;
  //......
  //VsyncWaiter.init方法中呼叫
  public static void setAsyncWaitForVsyncDelegate(@Nullable AsyncWaitForVsyncDelegate delegate) {
    asyncWaitForVsyncDelegate = delegate;
  }

  //這個方法的注釋明確說了被 netive engine 呼叫,也就是 JNI 的 C/C++ 端呼叫
  private static void asyncWaitForVsync(final long cookie) {
    if (asyncWaitForVsyncDelegate != null) {
      asyncWaitForVsyncDelegate.asyncWaitForVsync(cookie);
    } else {
      throw new IllegalStateException("An AsyncWaitForVsyncDelegate must be registered with FlutterJNI before asyncWaitForVsync() is invoked.");
    }
  }

  //java端呼叫native實作
  public static native void nativeOnVsync(
      long frameTimeNanos, long frameTargetTimeNanos, long cookie);

  public interface AsyncWaitForVsyncDelegate {
    void asyncWaitForVsync(final long cookie);
  }
}

對于安卓仔來說,上面代碼中熟悉的系統 API 比較多,所以我們先回到純 Android 平臺,老司機都知道,現代 Android 系統至少都是基于 VSYNC 的 Double Buffer(雙緩沖)機制實作繪制,而雙緩沖機制背后的核心思想是讓繪制和顯示擁有各自的影像緩沖區,也就是說 GPU 始終將完成的一幀影像資料寫入到 Back Buffer,而顯示幕使用 Frame Buffer 資料進行顯示,這樣雙緩沖 Frame Buffer 中的資料一定不會存在撕裂(類似并發不安全的寫),VSYNC 信號負責調度從 Back Buffer 到 Frame Buffer 的交換操作,這里并不是真正的資料 copy,實際是交換各自的記憶體地址,可以認為該操作是瞬間完成,

在這里插入圖片描述
看過我 Android 原始碼分析系列文章或者其他網文的小伙伴一定都知道,Android中有一個 ViewRootImpl,他的 mView 成員是 DecorView(本質 FrameLayout),而 DecorView 是一個 Activity 的根 View,整個界面的重繪入口都是 ViewRootImpl 類的 scheduleTraversals 方法(不懂就去看歷史文章),我們自己呼叫 View 的 invalidate 方法也是類似,如下:

void scheduleTraversals() {
    if (!mTraversalScheduled) {
        //......
        mChoreographer.postCallback(Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
        //......
    }
}

上面的 mTraversalRunnable 就是呼叫了 Activity 中 View 樹的 measure、layout、draw 進行繪制,而 mChoreographer 就是 Choreographer,在安卓平臺上,Choreographer 通過 postXXX 呼叫 FrameDisplayEventReceiver(繼承自 DisplayEventReceiver) 的 nativeScheduleVsync 方法進行 VSYNC 請求;同時 Choreographer 也通過 FrameDisplayEventReceiver 的 onVsync 方法監聽了系統 VSYNC 脈沖信號,該監聽方法中會觸發 Choreographer 的 doFrame 方法,該方法會把我們 post 進去的 callback 佇列拿出來執行,然后將已經執行過的 callback 進行移除,整個程序如下圖:
在這里插入圖片描述
簡單總結下結論,安卓應用程式如果有繪制(包括影片)需求的話,必須向系統框架發起 VSYNC 請求,請求在下一次 VSYNC 信號到來時繪制應用界面,

看到上面這個結論其實如果你有一定悟性應該能猜到 Flutter 的 VSYNC 是怎么作業的了,他其實也實作了類似標準安卓繪制觸發的流程,即發送 VSYNC 請求,等待下一個 VSYNC 信號到來執行 callback 回呼,我們在繼續分析前可以基于上面 VsyncWaiter 和 FlutterJNI 相關介面進行一個猜想如下:
在這里插入圖片描述

Flutter Framework Dart 層

Android 平臺 Java 層面的問題我們都分析完畢了,通過上面 Flutter VSYNC 猜想時序圖我們知道重點都在 Flutter Engine 里面,也就是說 Flutter Engine 呼叫 FlutterJNI 的 asyncWaitForVsync 方法通過安卓平臺的 Choreographer 發送 VSYNC 請求,請求在安卓平臺下一次 VSYNC 信號到來時通過 FlutterJNI 的 nativeOnVsync 方法向 Flutter Engine 傳遞繪制信號,整個程序像極了安卓 View 統管的 ViewRootImpl 實作,

我們知道,Flutter Engine 是 Flutter Dart Framework 與 Android 平臺之間的一個橋梁,抽象如下:
在這里插入圖片描述
所以我們基于前面 Flutter 系列分析及上面 Android 繪制機制大膽猜測可以知道,VSYNC 請求來自 Flutter Dart Framework,下一次 VSYNC 信號到來觸發繪制也呼叫到了 Flutter Dart Framework,Flutter Engine 只是一個橋梁處理程序,

發起繪制 VSYNC 請求

前面我們分析 Flutter App Dart main 方法時有提到 scheduleWarmUpFrame 方法最終呼叫了 SchedulerBinding 的 scheduleFrame 方法,進而呼叫window.scheduleFrame(),再呼叫 PlatformDispatcher 的scheduleFrame(),代碼如下:

class PlatformDispatcher {
  /// 發起VSYNC請求,等待下一幀呼叫onBeginFrame和onDrawFrame回呼,
  void scheduleFrame() native 'PlatformConfiguration_scheduleFrame';
}

PlatformDispatcher 的 scheduleFrame 方法實作其實是 Dart 呼叫 C/C++ native 代碼,對應的也是 PlatformConfiguration_scheduleFrame,我們可以在 engine 的 C/C++ 中搜其注冊入口,

上面方法就是 Flutter 層真正發起 VSYNC 請求的地方,然后等系統下一個 VSYNC 信號到來進行繪制操作(即來自 FlutterJni 的 nativeOnVsync 方法觸發),也就是最終呼叫到 Dart 層的 onBeginFrame 和 onDrawFrame,

其實我們日常中呼叫 Flutter Dart StatefulWidget 的 setState 方法也是呼叫了上面 scheduleFrame 方法,也就是說繪制的發起都來自 Widget 的變更主動呼叫觸發,包括影片效果等也是同樣道理,

收到下一幀 VSYNC 繪制信號

當上面 VSYNC 請求發出且等到下一個 VSYNC 信號到來時會通過 Java 到 C/C++ 再到 Dart Framework 層,對應到 Dart 層入口在hooks.dart檔案(呼叫詳見下面 Flutter Engine C/C++ 層分析),如下:

@pragma('vm:entry-point')
// ignore: unused_element
void _beginFrame(int microseconds) {
  PlatformDispatcher.instance._beginFrame(microseconds);
}

@pragma('vm:entry-point')
// ignore: unused_element
void _drawFrame() {
  PlatformDispatcher.instance._drawFrame();
}

本質在 PlatformDispatcher 中呼叫對應方法,即如下:

class PlatformDispatcher {
  FrameCallback? get onBeginFrame => _onBeginFrame;
  FrameCallback? _onBeginFrame;
  Zone _onBeginFrameZone = Zone.root;
  set onBeginFrame(FrameCallback? callback) {
    _onBeginFrame = callback;
    _onBeginFrameZone = Zone.current;
  }

  VoidCallback? get onDrawFrame => _onDrawFrame;
  VoidCallback? _onDrawFrame;
  Zone _onDrawFrameZone = Zone.root;
  set onDrawFrame(VoidCallback? callback) {
    _onDrawFrame = callback;
    _onDrawFrameZone = Zone.current;
  }
}

也就是呼叫了 PlatformDispatcher 通過 onBeginFrame、onDrawFrame 設定的對應回呼,我們反過來推看誰設定了這個回呼賦值,首先看到的呼叫賦值位于 SingletonFlutterWindow:

class SingletonFlutterWindow extends FlutterWindow {
  FrameCallback? get onBeginFrame => platformDispatcher.onBeginFrame;
  set onBeginFrame(FrameCallback? callback) {
    platformDispatcher.onBeginFrame = callback;
  }
}

接著看 SingletonFlutterWindow 的 onBeginFrame 屬性是誰賦值的,發現賦值呼叫如下:

mixin SchedulerBinding on BindingBase {
  void ensureFrameCallbacksRegistered() {
    //回呼實作位于SchedulerBinding中
    window.onBeginFrame ??= _handleBeginFrame;
    window.onDrawFrame ??= _handleDrawFrame;
  }

  void scheduleFrame() {
    //......
    ensureFrameCallbacksRegistered();
    window.scheduleFrame();
    //......
  }
}

可以看到本質回到了 SchedulerBinding 的 scheduleFrame 方法,也就是說第一次 Dart 發起 VSYNC 請求前先設定了回呼,當下一個系統 VSYNC 信號到來時就呼叫了 onBeginFrame、onDrawFrame 的回呼賦值,也就是說真正的繪制到 Dart 層入口在 SchedulerBinding 的void handleBeginFrame(Duration? rawTimeStamp)handleDrawFrame()中,關于他們的具體內容不在本文分析范圍,本文關注 VSYNC 動機程序,

Dart 層大致流程如下:
在這里插入圖片描述

Flutter Engine C/C++ 層

有了上面 Dart 層及 Java 層的分析,我們其實分析 Engine 層的 C/C++ 時就大致知道關鍵入口是什么了,所以下面依然基發起 VSYNC 請求和下一幀回呼 VSYNC 信號流程進行分析,

發起繪制 VSYNC 請求

通過 Dart 分析得知 VSYNC 信號的發起的實作是通過 Dart 呼叫了 engine C/C++ 的PlatformConfiguration_scheduleFrame native 方法,所以我們搜索可以看到對應 C/C++ 只有一處注冊且位于lib/ui/window/platform_configuration.cc檔案:

void PlatformConfiguration::RegisterNatives(
    tonic::DartLibraryNatives* natives) {
  natives->Register({
      //......
      {"PlatformConfiguration_scheduleFrame", ScheduleFrame, 1, true},
      //......
  });
}

void ScheduleFrame(Dart_NativeArguments args) {
  UIDartState::ThrowIfUIOperationsProhibited();
  //client()本質PlatformConfigurationClient,也就是RuntimeController
  UIDartState::Current()->platform_configuration()->client()->ScheduleFrame();
}

通過上面代碼可以看到,Dart 呼叫 engine C/C++ 的PlatformConfiguration_scheduleFrame native 方法走進了lib/ui/window/platform_configuration.cc檔案的void ScheduleFrame(Dart_NativeArguments args)方法,通過platform_configuration.h檔案中可以知道,client 是 PlatformConfigurationClient 型別,而 RuntimeController 類是他的實作,即runtime/runtime_controller.h中如下:

class RuntimeController : public PlatformConfigurationClient {
    //......
}

因此我們把目光轉向 RuntimeController 類,即runtime/runtime_controller.h中如下:

void RuntimeController::ScheduleFrame() {
  //client_ 型別為 RuntimeDelegate,也就是 engine instance
  client_.ScheduleFrame();
}

通過runtime/runtime_controller.h中 client 的注釋可以知道,client_ 其實是 Engine 實體,即shell/common/engine.h中如下:

class Engine final : public RuntimeDelegate, PointerDataDispatcher::Delegate {
    //......
}

對應實作shell/common/engine.cc如下:

void Engine::ScheduleFrame(bool regenerate_layer_tree) {
  animator_->RequestFrame(regenerate_layer_tree);
}

類似同上分析模式查看對應 h 和 cpp 檔案可以知道 animator_ 位于shell/common/animator.cc,如下:

void Animator::RequestFrame(bool regenerate_layer_tree) {
  //......
  task_runners_.GetUITaskRunner()->PostTask(//......
       frame_request_number = frame_request_number_]() {
        //......
        self->AwaitVSync();
      });
}

在引擎的 UITaskRunner 中執行shell/common/animator.cc檔案的 AwaitVSync 方法,如下:

void Animator::AwaitVSync() {
  waiter_->AsyncWaitForVsync(
      [self = weak_factory_.GetWeakPtr()](
          std::unique_ptr<FrameTimingsRecorder> frame_timings_recorder) {
        if (self) {
          if (self->CanReuseLastLayerTree()) {
            self->DrawLastLayerTree(std::move(frame_timings_recorder));
          } else {
            self->BeginFrame(std::move(frame_timings_recorder));
          }
        }
      });
}

類似同上分析模式查看對應 h 和 cpp 檔案可以知道 waiter_ 位于shell/common/vsync_waiter.cc,在 Android 平臺的實作類是 VsyncWaiterAndroid,位于shell/platform/android/vsync_waiter_android.cc如下:

//父類VsyncWaiter的AsyncWaitForVsync呼叫子類VsyncWaiterAndroid的AwaitVSync方法
void VsyncWaiter::AsyncWaitForVsync(const Callback& callback) {
  //......
  callback_ = std::move(callback);
  //......
  //對應VsyncWaiterAndroid::AwaitVSync()
  AwaitVSync();
}

void VsyncWaiterAndroid::AwaitVSync() {
  //......
  task_runners_.GetPlatformTaskRunner()->PostTask([java_baton]() {
    JNIEnv* env = fml::jni::AttachCurrentThread();
    env->CallStaticVoidMethod(
        //io/flutter/embedding/engine/FlutterJNI
        g_vsync_waiter_class->obj(),  
        //asyncWaitForVsync方法   
        g_async_wait_for_vsync_method_,
        //引數
        java_baton
    );
  });
}

真相大白,最后通過 Engine 的 PlatformTaskRunner 呼叫了 JNI 方法 asyncWaitForVsync,也就是我們上面分析 Android 平臺 Java 層小節提到的 FlutterJNI java 類的 asyncWaitForVsync 靜態方法,哈哈,Flutter 發起 VSYNC 請求的流程就這樣從 Java 到 C++ 到 Dart,再從 Dart 到 C++ 到 Java 全串起來了,

收到下一幀 VSYNC 繪制信號

剛剛發起繪制 VSYNC 請求最終走進了 java 層的Choreographer.getInstance().postFrameCallback(callback)方法,上面分析 Java 部分代碼時也提到了,等下一幀 VSYNC 信號到來會觸發 java 層 FlutterJNI 類的 nativeOnVsync 方法,經過 C/C++ 搜索分析可知,上面 FlutterJNI 中的 nativeOnVsync 方法呼叫點位于 engine 的shell/platform/android/vsync_waiter_android.cc中,如下:

void VsyncWaiterAndroid::OnNativeVsync(JNIEnv* env, jclass jcaller, jlong frameTimeNanos,
                                       jlong frameTargetTimeNanos, jlong java_baton) {
  //......
  ConsumePendingCallback(java_baton, frame_time, target_time);
}

void VsyncWaiterAndroid::ConsumePendingCallback( jlong java_baton,
    fml::TimePoint frame_start_time, fml::TimePoint frame_target_time) {
  //......
  shared_this->FireCallback(frame_start_time, frame_target_time);
}

void VsyncWaiter::FireCallback(fml::TimePoint frame_start_time,
                               fml::TimePoint frame_target_time,
                               bool pause_secondary_tasks) {
    //......
    PauseDartMicroTasks();
    //......
    task_runners_.GetUITaskRunner()->PostTaskForTime(
        [ui_task_queue_id, callback, flow_identifier, frame_start_time,
         frame_target_time, pause_secondary_tasks]() {
          //......
          callback(std::move(frame_timings_recorder));
          //......
          ResumeDartMicroTasks(ui_task_queue_id);
          //......
        }, frame_start_time);
  }
  //......
}

其實上面繞一圈最終就是在引擎的 UITaskRunner 中執行了上面 dart 發起 VSYNC 請求小節分析的 callback 引數,即,這里的callback(std::move(frame_timings_recorder))等價于Animator::AwaitVSync()方法中呼叫waiter_->AsyncWaitForVsync方法傳遞的引數 callback,callback 賦值代碼位于shell/common/animator.cc檔案的 AwaitVSync 方法,如下:

void Animator::AwaitVSync() {
  //AsyncWaitForVsync函式引數就是callback
  waiter_->AsyncWaitForVsync(
      [self = weak_factory_.GetWeakPtr()](
          std::unique_ptr<FrameTimingsRecorder> frame_timings_recorder) {
        if (self) {
          if (self->CanReuseLastLayerTree()) {
            self->DrawLastLayerTree(std::move(frame_timings_recorder));
          } else {
            self->BeginFrame(std::move(frame_timings_recorder));
          }
        }
      });
}

真相大白,callback 被回呼(即 VSYNC 繪制信號過來)時呼叫了 Animator 的 DrawLastLayerTree 或者 BeginFrame 方法,具體取決于是否需要重新生成 LayerTree 樹進行繪制,

由于本文我們主要關心繪制動機流程,所以上面 DrawLastLayerTree 就先不分析了,我們看看 Animator 的 BeginFrame 方法,可以發現其呼叫了delegate_.OnAnimatorBeginFrame(frame_target_time, frame_number);,也就是 Shell 的 OnAnimatorBeginFrame 方法,本質就是 Engine 的 BeginFrame 方法,如下shell/common/engine.cc

void Engine::BeginFrame(fml::TimePoint frame_time, uint64_t frame_number) {
  TRACE_EVENT0("flutter", "Engine::BeginFrame");
  runtime_controller_->BeginFrame(frame_time, frame_number);
}

RuntimeController 的 BeginFrame 方法呼叫了 PlatformConfiguration 的 BeginFrame 方法,如下lib/ui/window/platform_configuration.cc

void PlatformConfiguration::BeginFrame(fml::TimePoint frameTime,
                                       uint64_t frame_number) {
  //......
  tonic::LogIfError(
      tonic::DartInvoke(begin_frame_.Get(), {
          Dart_NewInteger(microseconds),
          Dart_NewInteger(frame_number),
      }));
  UIDartState::Current()->FlushMicrotasksNow();
  tonic::LogIfError(tonic::DartInvokeVoid(draw_frame_.Get()));
}

void PlatformConfiguration::DidCreateIsolate() {
  Dart_Handle library = Dart_LookupLibrary(tonic::ToDart("dart:ui"));
  //......
                   Dart_GetField(library, tonic::ToDart("_beginFrame")));
  draw_frame_.Set(tonic::DartState::Current(),
                  Dart_GetField(library, tonic::ToDart("_drawFrame")));
  //......
}

哈哈,這就呼應了上面 Flutter Framework Dart 層小節收到下一幀 VSYNC 繪制信號部分被呼叫的入庫,即下一個 VSYNC 繪制信號過來最終引擎 engine 呼叫了 Dart 層入口在hooks.dart檔案的_beginFrame_drawFrame等方法觸發 dart 層進行繪制操作,

C++ 層流程大致總結如下:
在這里插入圖片描述

總結

到此我想你應該就能大概看懂 Flutter 官網貼的這張經典繪制流程圖了:
在這里插入圖片描述
關于上圖中的每一步細節不在本文分析范圍之內,但是關于上圖從發起 Flutter VSYNC 請求到收到系統下一個 VSYNC 繪制信號進行繪制操作的全流程我們算是徹底搞明白了,也從一定程度上理解了 Flutter 架構分層圖的整個架構流轉機制,

其實搞懂本文 VSYNC 信號從 Dart 到 C++ 到 Java,再從 Java 到 C++ 到 Dart,可以不夸張的說你已經掌握了 Flutter 架構的精髓,缺少的只是這條鏈條上的各個細節節點而已,

轉載請註明出處,本文鏈接:https://www.uj5u.com/yidong/294975.html

標籤:其他

上一篇:android匿名共享記憶體原理淺讀

下一篇:Android開發 OkHttp+客戶端與服務器

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 【從零開始擼一個App】Dagger2

    Dagger2是一個IOC框架,一般用于Android平臺,第一次接觸的朋友,一定會被搞得暈頭轉向。它延續了Java平臺Spring框架代碼碎片化,注解滿天飛的傳統。嘗試將各處代碼片段串聯起來,理清思緒,真不是件容易的事。更不用說還有各版本細微的差別。 與Spring不同的是,Spring是通過反射 ......

    uj5u.com 2020-09-10 06:57:59 more
  • Flutter Weekly Issue 66

    新聞 Flutter 季度調研結果分享 教程 Flutter+FaaS一體化任務編排的思考與設計 詳解Dart中如何通過注解生成代碼 GitHub 用對了嗎?Flutter 團隊分享如何管理大型開源專案 插件 flutter-bubble-tab-indicator A Flutter librar ......

    uj5u.com 2020-09-10 06:58:52 more
  • Proguard 常用規則

    介紹 Proguard 入口,如何查看輸出,如何使用 keep 設定入口以及使用實體,如何配置壓縮,混淆,校驗等規則。

    ......

    uj5u.com 2020-09-10 06:59:00 more
  • Android 開發技術周報 Issue#292

    新聞 Android即將獲得類AirDrop功能:可向附近設備快速分享檔案 谷歌為安卓檔案管理應用引入可安全隱藏資料的Safe Folder功能 Android TV新主界面將顯示電影、電視節目和應用推薦內容 泄露的Android檔案暗示了傳說中的谷歌Pixel 5a與折疊屏新機 谷歌發布Andro ......

    uj5u.com 2020-09-10 07:00:37 more
  • AutoFitTextureView Error inflating class

    報錯: Binary XML file line #0: Binary XML file line #0: Error inflating class xxx.AutoFitTextureView 解決: <com.example.testy2.AutoFitTextureView android: ......

    uj5u.com 2020-09-10 07:00:41 more
  • 根據Uri,Cursor沒有獲取到對應的屬性

    Android: 背景:呼叫攝像頭,拍攝視頻,指定保存的地址,但是回傳的Cursor檔案,只有名稱和大小的屬性,沒有其他諸如時長,連ID屬性都沒有 使用 cursor.getInt(cursor.getColumnIndexOrThrow(MediaStore.Video.Media.DURATIO ......

    uj5u.com 2020-09-10 07:00:44 more
  • Android連載29-持久化技術

    一、持久化技術 我們平時所使用的APP產生的資料,在記憶體中都是瞬時的,會隨著斷電、關機等丟失資料,因此android系統采用了持久化技術,用于存盤這些“瞬時”資料 持久化技術包括:檔案存盤、SharedPreference存盤以及資料庫存盤,還有更復雜的SD卡記憶體儲。 二、檔案存盤 最基本存盤方式, ......

    uj5u.com 2020-09-10 07:00:47 more
  • Android Camera2Video整合到自己專案里

    背景: Android專案里呼叫攝像頭拍攝視頻,原本使用的 MediaStore.ACTION_VIDEO_CAPTURE, 后來因專案需要,改成了camera2 1.Camera2Video 官方demo有點問題,下載后,不能直接整合到專案 問題1.多次拍攝視頻崩潰 問題2.雙擊record按鈕, ......

    uj5u.com 2020-09-10 07:00:50 more
  • Android 開發技術周報 Issue#293

    新聞 谷歌為Android TV開發者提供多種新功能 Android 11將自動填表功能整合到鍵盤輸入建議中 谷歌宣布Android Auto即將支持更多的導航和數字停車應用 谷歌Pixel 5只有XL版本 搭載驍龍765G且將比Pixel 4更便宜 [圖]Wear OS將迎來重磅更新:應用啟動時間 ......

    uj5u.com 2020-09-10 07:01:38 more
  • 海豚星空掃碼投屏 Android 接收端 SDK 集成 六步驟

    掃碼投屏,開放網路,獨占設備,不需要額外下載軟體,微信掃碼,發現設備。支持標準DLNA協議,支持倍速播放。視頻,音頻,圖片投屏。好點意思。還支持自定義基于 DLNA 擴展的操作動作。好像要收費,沒體驗。 這里簡單記錄一下集成程序。 一 跟目錄的build.gradle添加私有mevan倉庫 mave ......

    uj5u.com 2020-09-10 07:01:43 more
最新发布
  • 歡迎頁輪播影片

    如圖,引導開始,球從上落下,同時淡入文字,然后文字開始輪播,最后一頁時停止,點擊進入首頁。 在來看看效果圖。 重力球先不講,主要歡迎輪播簡單實作 首先新建一個類 TextTranslationXGuideView,用于影片展示 文本是類似的,最后會有個圖片箭頭影片,布局很簡單,就是一個 TextVi ......

    uj5u.com 2023-04-20 08:40:31 more
  • 【FAQ】關于華為推送服務因營銷訊息頻次管控導致服務通訊類訊息

    一. 問題描述 使用華為推送服務下發IM訊息時,下發訊息請求成功且code碼為80000000,但是手機總是收不到訊息; 在華為推送自助分析(Beta)平臺查看發現,訊息發送觸發了頻控。 二. 問題原因及背景 2023年1月05日起,華為推送服務對咨詢營銷類訊息做了單個設備每日推送數量上限管理,具體 ......

    uj5u.com 2023-04-20 08:40:11 more
  • 歡迎頁輪播影片

    如圖,引導開始,球從上落下,同時淡入文字,然后文字開始輪播,最后一頁時停止,點擊進入首頁。 在來看看效果圖。 重力球先不講,主要歡迎輪播簡單實作 首先新建一個類 TextTranslationXGuideView,用于影片展示 文本是類似的,最后會有個圖片箭頭影片,布局很簡單,就是一個 TextVi ......

    uj5u.com 2023-04-20 08:39:36 more
  • 【FAQ】關于華為推送服務因營銷訊息頻次管控導致服務通訊類訊息

    一. 問題描述 使用華為推送服務下發IM訊息時,下發訊息請求成功且code碼為80000000,但是手機總是收不到訊息; 在華為推送自助分析(Beta)平臺查看發現,訊息發送觸發了頻控。 二. 問題原因及背景 2023年1月05日起,華為推送服務對咨詢營銷類訊息做了單個設備每日推送數量上限管理,具體 ......

    uj5u.com 2023-04-20 08:39:13 more
  • iOS從UI記憶體地址到讀取成員變數(oc/swift)

    開發除錯時,我們發現bug時常首先是從UI顯示發現例外,下一步才會去定位UI相關連的資料的。XCode有給我們提供一系列debug工具,但是很多人可能還沒有形成一套穩定的除錯流程,因此本文嘗試解決這個問題,順便提出一個暴論:UI顯示例外問題只需要兩個步驟就能完成定位作業的80%: 定位例外 UI 組 ......

    uj5u.com 2023-04-19 09:16:23 more
  • FIDE重磅更新!性能飛躍!體驗有禮!

    FIDE 開發者工具重構升級啦!實作500%性能提升,誠邀體驗! 一直以來不少開發者朋友在社區反饋,在使用 FIDE 工具的程序中,時常會遇到諸如加載不及時、代碼預覽/渲染性能不如意的情況,十分影響開發體驗。 作為技術團隊,我們深知一件趁手的開發工具對開發者的重要性,因此,在2023年開年,FinC ......

    uj5u.com 2023-04-19 09:16:15 more
  • 游戲內嵌社區服務開放,助力開發者提升玩家互動與留存

    華為 HMS Core 游戲內嵌社區服務提供快速訪問華為游戲中心論壇能力,支持玩家直接在游戲內瀏覽帖子和交流互動,助力開發者擴展內容生產和觸達的場景。 一、為什么要游戲內嵌社區? 二、游戲內嵌社區的典型使用場景 1、游戲內打開論壇 您可以在游戲內繪制論壇入口,為玩家提供沉浸式發帖、瀏覽、點贊、回帖、 ......

    uj5u.com 2023-04-19 09:15:46 more
  • iOS從UI記憶體地址到讀取成員變數(oc/swift)

    開發除錯時,我們發現bug時常首先是從UI顯示發現例外,下一步才會去定位UI相關連的資料的。XCode有給我們提供一系列debug工具,但是很多人可能還沒有形成一套穩定的除錯流程,因此本文嘗試解決這個問題,順便提出一個暴論:UI顯示例外問題只需要兩個步驟就能完成定位作業的80%: 定位例外 UI 組 ......

    uj5u.com 2023-04-19 09:14:53 more
  • FIDE重磅更新!性能飛躍!體驗有禮!

    FIDE 開發者工具重構升級啦!實作500%性能提升,誠邀體驗! 一直以來不少開發者朋友在社區反饋,在使用 FIDE 工具的程序中,時常會遇到諸如加載不及時、代碼預覽/渲染性能不如意的情況,十分影響開發體驗。 作為技術團隊,我們深知一件趁手的開發工具對開發者的重要性,因此,在2023年開年,FinC ......

    uj5u.com 2023-04-19 09:14:08 more
  • 游戲內嵌社區服務開放,助力開發者提升玩家互動與留存

    華為 HMS Core 游戲內嵌社區服務提供快速訪問華為游戲中心論壇能力,支持玩家直接在游戲內瀏覽帖子和交流互動,助力開發者擴展內容生產和觸達的場景。 一、為什么要游戲內嵌社區? 二、游戲內嵌社區的典型使用場景 1、游戲內打開論壇 您可以在游戲內繪制論壇入口,為玩家提供沉浸式發帖、瀏覽、點贊、回帖、 ......

    uj5u.com 2023-04-19 09:08:34 more