主頁 > 後端開發 > 讓 Flutter 在鴻蒙系統上跑起來

讓 Flutter 在鴻蒙系統上跑起來

2021-01-24 21:46:57 後端開發

鴻蒙系統 (HarmonyOS)是華為推出的一款分布式作業系統,那么如何在保證開發迭代效率的前提下,以相對低的成本將移動應用快速移植到鴻蒙平臺上呢?美團外賣 MTFlutter 團隊近期做了一次技術探索,成功地實作了 Flutter 對于鴻蒙系統的原生支持,

前言

鴻蒙系統 (HarmonyOS)是華為推出的一款面向未來、面向全場景的分布式作業系統,在傳統單設備系統能力的基礎上,鴻蒙提出了基于同一套系統能力、適配多種終端形態的分布式理念,自 2020 年 9 月 HarmonyOS 2.0 發布以來,華為加快了鴻蒙系統大規模落地的步伐,預計 2021 年底,鴻蒙系統會覆寫包括手機、平板、智能穿戴、智慧屏、車機在內的數億臺終端設備,對移動應用而言,新的系統理念、新的互動形式,也意味著新的機遇,如果能夠利用好鴻蒙的開發生態及其特性能力,可以讓應用覆寫更多的互動場景和設備型別,從而帶來新的增長點,

與面臨的機遇相比,適配鴻蒙系統帶來的挑戰同樣巨大,當前手機端,盡管鴻蒙系統仍然支持安卓 APK 安裝及運行,但長期來看,華為勢必會拋棄 AOSP,逐步發展出自己的生態,這意味著現有安卓應用在鴻蒙設備上將會逐漸變成“二等公民”,然而,如果在 iOS 及 Android 之外再重新開發和維護一套鴻蒙應用,在如今業界越來越注重開發迭代效率的環境下,所帶來的開發成本也是難以估量的,因此,通過打造一套合適的跨端框架,以相對低的成本移植應用到鴻蒙平臺,并利用好該系統的特性能力,就成為了一個非常重要的選項,

在現有的眾多跨端框架當中,Flutter 以其自渲染能力帶來的多端高度一致性,在新系統的適配上有著突出的優勢,雖然Flutter 官方并沒有適配鴻蒙的計劃,但經過一段時間的探索和實踐,美團外賣 MTFlutter 團隊成功實作了 Flutter 對于鴻蒙系統的原生支持,

這里也要提前說明一下,因為鴻蒙系統目前還處于Beta版本,所以這套適配方案還沒有在實際業務中上線,屬于技術層面比較前期的探索,接下來本文會通過原理和部分實作細節的介紹,分享我們在移植和開發程序中的一些經驗,希望能對大家有所啟發或者幫助,

背景知識和基礎概念介紹

在適配開始之前,我們要明確好先做哪些事情,先來回顧一下 Flutter 的三層結構:

在 Flutter 的架構設計中,最上層為框架層,使用 Dart 語言開發,面向 Flutter 業務的開發者;中間層為引擎層,使用 C/C++ 開發,實作了 Flutter 的渲染管線和 Dart 運行時等基礎能力;最下層為嵌入層,負責與平臺相關的能力實作,顯然我們要做的是將嵌入層移植到鴻蒙上,確切地說,我們要通過鴻蒙原生提供的平臺能力,重新實作一遍 Flutter 嵌入層

對于 Flutter 嵌入層的適配,Flutter 官方有一份不算詳細的指南,實際操作起來成本很高,由于鴻蒙的業務開發語言仍然可用 Java,在很多基礎概念上與 Android 也有相似之處(如下表所示),我們可以從 Android 的實作入手,完成對鴻蒙的移植,

Flutter 在鴻蒙上的適配

如前文所述,要完成 Flutter 在新系統上的移植,我們需要完整實作 Flutter 嵌入層要求的所有子模塊,而從能力支持角度,渲染互動以及其他必要的原生平臺能力是保證 Flutter 應用能夠運行起來的最基本的要素,需要優先支持,接下來會依次進行介紹,

1. 渲染流程打通

我們再來回顧一下 Flutter 的影像渲染流程,如圖所示,設備發起垂直同步(VSync)信號之后,先經過 UI 執行緒的渲染管線(Animate/Build/Layout/Paint),再經過 Raster 執行緒的組合和柵格化,最終通過 OpenGL 或 Vulkan 將影像上屏,這個流程的大部分作業都由框架層和引擎層完成,對于鴻蒙的適配,我們主要關注的是與設備自身能力相關的問題,即:

(1)如何監聽設備的 VSync 信號并通知 Flutter 引擎?
(2)OpenGL/Vulkan 用于上屏的視窗物件從何而來?

VSync 信號的監聽及傳遞

在 Flutter 引擎的 Android 實作中,設備的 VSync 信號通過 Choreographer 觸發,其產生及消費流程如下圖所示:

Flutter VSync

Flutter 框架注冊 VSync 回呼之后,通過 C++ 側的 VsyncWaiter 類等待 VSync 信號,后者通過 JNI 等一系列呼叫,最終 Java 側的 VsyncWaiter 類呼叫 Android SDK 的 Choreographer.postFrameCallback 方法,再通過 JNI 一層層傳回 Flutter 引擎消費掉此回呼,Java 側的 VsyncWaiter 核心代碼如下:

@Override
public void asyncWaitForVsync(long cookie) {
  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(
              frameTimeNanos, frameTimeNanos + refreshPeriodNanos, cookie);
          }
        });
}

在整個流程中,除了來自 Android SDK 的 Choreographer 以外,大多數邏輯幾乎都由 C++ 和 Java 的基礎 SDK 實作,可以直接在鴻蒙上復用,問題是鴻蒙目前的 API 檔案中尚沒有開放類似 Choreographer 的能力,所以現階段我們可以借用鴻蒙提供的類似 iOS Grand Central Dispatch 的執行緒 API,模擬出 VSync 的信號觸發與回呼:

@Override
public void asyncWaitForVsync(long cookie) {
  // 模擬每秒 60 幀的螢屏重繪間隔:向主執行緒發送一個異步任務, 16ms 后呼叫
  applicationContext.getUITaskDispatcher().delayDispatch(() -> {
    float fps = 60; // 設備重繪幀率,HarmonyOS 未暴露獲取幀率 API,先寫死 60 幀
    long refreshPeriodNanos = (long) (1000000000.0 / fps);
    long frameTimeNanos = System.nanoTime();
    FlutterJNI.nativeOnVsync(frameTimeNanos, frameTimeNanos + refreshPeriodNanos, cookie);
  }, 16);
};

渲染視窗的構建及傳遞

在這一部分,我們需要在鴻蒙系統上構建平臺容器,為 Flutter 引擎的圖形渲染提供用于上屏的視窗物件,同樣,我們參考 Flutter for Android 的實作,看一下 Android 系統是怎么做的:

Flutter 在 Android 上支持 Vulkan 和 OpenGL 兩種渲染引擎,篇幅原因我們只關注 OpenGL,拋開復雜的注冊及呼叫細節,本質上整個流程主要做了三件事:

  1. 創建了一個視圖物件,提供可用于直接繪制的 Surface,將它通過 JNI 傳遞給原生側;

  2. 在原生側獲取 Surface 關聯的本地視窗物件,并交給 Flutter 的平臺容器;

  3. 將本地視窗物件轉換為 OpenGL ES 可識別的繪圖表面(EGLSurface,用于 Flutter 引擎的渲染上屏,

接下來我們用鴻蒙提供的平臺能力來實作這三點,

a. 可用于直接繪制的視圖物件

鴻蒙系統的 UI 框架提供了很多常用視圖組件(Component),比如按鈕、文字、圖片、串列等,但我們需要拋開這些上層組件,獲得直接繪制的能力,借助官方媒體播放器開發指導檔案,可以發現鴻蒙提供了 SurfaceProvider 類,它管理的 Surface 物件可以用于視頻解碼后的展示,而 Flutter 渲染與視頻上屏從原理上是類似的,因此我們可以借用 SurfaceProvider 實作 Surface 的管理和創建:

// 創建一個用于管理 Surface 的容器組件
SurfaceProvider surfaceProvider = new SurfaceProvider(context);
// 注冊視圖創建回呼
surfaceProvider.getSurfaceOps().get().addCallback(surfaceCallback);

// ... 在 surfaceCallback 中
@Override
public void surfaceCreated(SurfaceOps surfaceOps) {
  Surface surface = surfaceOps.getSurface();
  // ...將 surface 通過 JNI 交給 Native 側
  FlutterJNI.onSurfaceCreated(surface);
}

b. 與 Surface 關聯的本地視窗物件

鴻蒙目前開放的 Native API 并不多,在官方檔案中,我們可以比較容易地找到 Native_layer API,根據檔案的說明,Native API 中的 NativeLayer 物件剛好對應了 Java 側的 Surface 類,借助 GetNativeLayer 方法,我們實作了兩者之間的轉化:

// platform_view_android_jni_impl.cc
static void SurfaceCreated(JNIEnv* env, jobject jcaller, jlong shell_holder, jobject jsurface) {
  fml::jni::ScopedJavaLocalFrame scoped_local_reference_frame(env);
  // 通過鴻蒙 Native API 獲取本地視窗物件 NativeLayer
  auto window = fml::MakeRefCounted<AndroidNativeWindow>(
      GetNativeLayer(env, jsurface));
  ANDROID_SHELL_HOLDER->GetPlatformView()->NotifyCreated(std::move(window));
}

c. 與本地視窗物件關聯的 EGLSurface

在 Android 的 AOSP 實作中,EGLSurface 可通過 EGL 庫的 eglCreateWindowSurface 方法從本地視窗物件 ANativeWindow 創建而來,對于鴻蒙而言,雖然我們沒有從公開檔案找到類似的說明,但是鴻蒙標準庫默認支持了 OpenGL ES,而且鴻蒙 SDK 中也附帶了 EGL 相關的庫及頭檔案,我們有理由相信在鴻蒙系統上,EGLSurface 也可以通過此方法從前一步生成的 NativeLayer 轉化而來,在之后的驗證中我們也確認了這一點:

// window->handle() 即為之前得到的 NativeLayer
EGLSurface surface = eglCreateWindowSurface(
      display, config_, reinterpret_cast<EGLNativeWindowType>(window->handle()),
      attribs);
//...交給 Flutter 渲染管線

2. 互動能力實作

互動能力是支撐 Flutter 應用能夠正常運行的另一個基本要求,在 Flutter 中,互動包含了各種觸摸事件、滑鼠事件、鍵盤錄入事件的傳遞及消費,以觸摸事件為例,Flutter 事件傳遞的整個流程如下圖所示:

Flutter 事件分發

iOS/Android 的原生容器通過觸摸事件的回呼 API 接收到事件之后,會將其打包傳遞至引擎層,后者將事件傳發給 Flutter 框架層,并完成事件的消費、分發和邏輯處理,同樣,整個流程的大部分作業已經由 Flutter 統一,我們要做的僅僅是在原生容器上監聽用戶的輸入,并封裝成指定格式交給引擎層而已,

在鴻蒙系統上,我們可以借助平臺提供的多模輸入 API,實作多種型別事件的監聽:

flutterComponent.setTouchEventListener(touchEventListener); // 觸摸及滑鼠事件
flutterComponent.setKeyEventListener(keyEventListener); // 鍵盤錄入事件
flutterComponent.setSpeechEventListener(speechEventListener); // 語音錄入事件

對于事件的封裝處理,可以復用 Android 已有的邏輯,只需要關注鴻蒙與 Android 在事件處理上的對應關系即可,比如觸摸事件的部分對應關系:

3. 其他必要的平臺能力

為了保證 Flutter 應用能夠正常運行,除了最基本的渲染和互動外,我們的嵌入層還要提供資源管理、事件回圈、生命周期同步等平臺能力,對于這些能力 Flutter 大多都在嵌入層的公共部分有抽象類宣告,只需要使用鴻蒙 API 重新實作一遍即可,

比如資源管理,引擎提供了 AssetResolver 宣告,我們可以使用鴻蒙 Rawfile API 來實作:

class HAPAssetMapping : public fml::Mapping {
 public:
  HAPAssetMapping(RawFile* asset) : asset_(asset) {}
  ~HAPAssetMapping() override { CloseRawFile(asset_); }

  size_t GetSize() const override { return GetRawFileSize(asset_); }

  const uint8_t* GetMapping() const override {
    return reinterpret_cast<const uint8_t*>(GetRawFileBuffer(asset_));
  }

 private:
  RawFile* const asset_;

  FML_DISALLOW_COPY_AND_ASSIGN(HAPAssetMapping);
};

對于事件回圈,引擎提供了 MessageLoopImpl 抽象類,我們可以使用鴻蒙 Native_EventHandler API 實作:

// runner_ 為鴻蒙 EventRunnerNativeImplement 的實體
void MessageLoopHarmony::Run() {
  FML_DCHECK(runner_ == GetEventRunnerNativeObjForThread());
  int result = ::EventRunnerRun(runner_);
  FML_DCHECK(result == 0);
}

void MessageLoopHarmony::Terminate() {
  int result = ::EventRunnerStop(runner_);
  FML_DCHECK(result == 0);
}

對于生命周期的同步,鴻蒙的 Page Ability 提供了完整的生命周期回呼(如下圖所示),我們只需要在對應的時機將狀態上報給引擎即可,

Page Ability Lifecycle

當以上這些能力都準備好之后,我們就可以成功把 Flutter 應用跑起來了,以下是通過 DevEco Studio 運行官方 Flutter Gallery 應用的截圖,截圖中 Flutter 引擎已經使用鴻蒙系統的平臺能力進行了重寫:

DevEco Running Flutter

借由鴻蒙的多設備支持能力,此應用甚至可在 TV、車機、手表、平板等設備上運行:

Flutter Multiple Devices

總結和展望

通過上述的構建和適配作業,我們以極小的開發成本實作了 Flutter 在鴻蒙系統上的移植,基于 Flutter 開發的上層業務幾乎不做任何修改就可以在鴻蒙系統上原生運行,為迎接鴻蒙系統后續的大規模推廣也提前做好了技術儲備,

當然,故事到這里并沒有結束,在最基本的運行和互動能力之上,我們更需要關注 Flutter 與鴻蒙自身生態的結合:如何優雅地適配鴻蒙的分布式技術?如何用 Flutter 實作設備之間的快速連接、資源共享?現有的眾多 Flutter 插件如何應用到鴻蒙系統上?未來 MTFlutter 團隊將在這些方面做更深入的探索,因為解決好這些問題,才是真正能讓應用覆寫用戶生活的全場景的關鍵,

參考文獻

  • https://developer.huawei.com/consumer/cn/events/hdc2020/

  • https://developer.harmonyos.com/cn/documentation

  • https://flutter.dev/docs/resources/architectural-overview

  • https://github.com/flutter/flutter/wiki/Custom-Flutter-Engine-Embedders

作者簡介

楊超,2016 年加入美團外賣技術團隊,目前主要負責 MTFlutter 相關的基礎建設作業,

---------- END ----------

招聘資訊

美團外賣長期招聘Android、iOS、FE 高級/資深工程師和技術專家,歡迎感興趣的同學加入我們,可投遞簡歷至:tech@meituan.com(郵件主題請注明:外賣前端)

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

標籤:java

上一篇:小紅書面試題:如何分析用戶行為?

下一篇:你想被開除嗎?來看看程式員【離職小技巧】吧

標籤雲
其他(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)

熱門瀏覽
  • 【C++】Microsoft C++、C 和匯編程式檔案

    ......

    uj5u.com 2020-09-10 00:57:23 more
  • 例外宣告

    相比于斷言適用于排除邏輯上不可能存在的狀態,例外通常是用于邏輯上可能發生的錯誤。 例外宣告 Item 1:當函式不可能拋出例外或不能接受拋出例外時,使用noexcept 理由 如果不打算拋出例外的話,程式就會認為無法處理這種錯誤,并且應當盡早終止,如此可以有效地阻止例外的傳播與擴散。 示例 //不可 ......

    uj5u.com 2020-09-10 00:57:27 more
  • Codeforces 1400E Clear the Multiset(貪心 + 分治)

    鏈接:https://codeforces.com/problemset/problem/1400/E 來源:Codeforces 思路:給你一個陣列,現在你可以進行兩種操作,操作1:將一段沒有 0 的區間進行減一的操作,操作2:將 i 位置上的元素歸零。最終問:將這個陣列的全部元素歸零后操作的最少 ......

    uj5u.com 2020-09-10 00:57:30 more
  • UVA11610 【Reverse Prime】

    本人看到此題沒有翻譯,就附帶了一個自己的翻譯版本 思考 這一題,它的第一個要求是找出所有 $7$ 位反向質數及其質因數的個數。 我們應該需要質數篩篩選1~$10^{7}$的所有數,這里就不慢慢介紹了。但是,重讀題,我們突然發現反向質數都是 $7$ 位,而將它反過來后的數字卻是 $6$ 位數,這就說明 ......

    uj5u.com 2020-09-10 00:57:36 more
  • 統計區間素數數量

    1 #pragma GCC optimize(2) 2 #include <bits/stdc++.h> 3 using namespace std; 4 bool isprime[1000000010]; 5 vector<int> prime; 6 inline int getlist(int ......

    uj5u.com 2020-09-10 00:57:47 more
  • C/C++編程筆記:C++中的 const 變數詳解,教你正確認識const用法

    1、C中的const 1、區域const變數存放在堆疊區中,會分配記憶體(也就是說可以通過地址間接修改變數的值)。測驗代碼如下: 運行結果: 2、全域const變數存放在只讀資料段(不能通過地址修改,會發生寫入錯誤), 默認為外部聯編,可以給其他源檔案使用(需要用extern關鍵字修飾) 運行結果: ......

    uj5u.com 2020-09-10 00:58:04 more
  • 【C++犯錯記錄】VS2019 MFC添加資源不懂如何修改資源宏ID

    1. 首先在資源視圖中,添加資源 2. 點擊新添加的資源,復制自動生成的ID 3. 在解決方案資源管理器中找到Resource.h檔案,編輯,使用整個專案搜索和替換的方式快速替換 宏宣告 4. Ctrl+Shift+F 全域搜索,點擊查找全部,然后逐個替換 5. 為什么使用搜索替換而不使用屬性視窗直 ......

    uj5u.com 2020-09-10 00:59:11 more
  • 【C++犯錯記錄】VS2019 MFC不懂的批量添加資源

    1. 打開資源頭檔案Resource.h,在其中預先定義好宏 ID(不清楚其實ID值應該設定多少,可以先新建一個相同的資源項,再在這個資源的ID值的基礎上遞增即可) 2. 在資源視圖中選中專案資源,按F7編輯資源檔案,按 ID 型別 相對路徑的形式添加 資源。(別忘了先把檔案拷貝到專案中的res檔案 ......

    uj5u.com 2020-09-10 01:00:19 more
  • C/C++編程筆記:關于C++的參考型別,專供新手入門使用

    今天要講的是C++中我最喜歡的一個用法——參考,也叫別名。 參考就是給一個變數名取一個變數名,方便我們間接地使用這個變數。我們可以給一個變數創建N個參考,這N + 1個變數共享了同一塊記憶體區域。(參考型別的變數會占用記憶體空間,占用的記憶體空間的大小和指標型別的大小是相同的。雖然參考是一個物件的別名,但 ......

    uj5u.com 2020-09-10 01:00:22 more
  • 【C/C++編程筆記】從頭開始學習C ++:初學者完整指南

    眾所周知,C ++的學習曲線陡峭,但是花時間學習這種語言將為您的職業帶來奇跡,并使您與其他開發人員區分開。您會更輕松地學習新語言,形成真正的解決問題的技能,并在編程的基礎上打下堅實的基礎。 C ++將幫助您養成良好的編程習慣(即清晰一致的編碼風格,在撰寫代碼時注釋代碼,并限制類內部的可見性),并且由 ......

    uj5u.com 2020-09-10 01:00:41 more
最新发布
  • Rust中的智能指標:Box<T> Rc<T> Arc<T> Cell<T> RefCell<T> Weak

    Rust中的智能指標是什么 智能指標(smart pointers)是一類資料結構,是擁有資料所有權和額外功能的指標。是指標的進一步發展 指標(pointer)是一個包含記憶體地址的變數的通用概念。這個地址參考,或 ” 指向”(points at)一些其 他資料 。參考以 & 符號為標志并借用了他們所 ......

    uj5u.com 2023-04-20 07:24:10 more
  • Java的值傳遞和參考傳遞

    值傳遞不會改變本身,參考傳遞(如果傳遞的值需要實體化到堆里)如果發生修改了會改變本身。 1.基本資料型別都是值傳遞 package com.example.basic; public class Test { public static void main(String[] args) { int ......

    uj5u.com 2023-04-20 07:24:04 more
  • [2]SpinalHDL教程——Scala簡單入門

    第一個 Scala 程式 shell里面輸入 $ scala scala> 1 + 1 res0: Int = 2 scala> println("Hello World!") Hello World! 檔案形式 object HelloWorld { /* 這是我的第一個 Scala 程式 * 以 ......

    uj5u.com 2023-04-20 07:23:58 more
  • 理解函式指標和回呼函式

    理解 函式指標 指向函式的指標。比如: 理解函式指標的偽代碼 void (*p)(int type, char *data); // 定義一個函式指標p void func(int type, char *data); // 宣告一個函式func p = func; // 將指標p指向函式func ......

    uj5u.com 2023-04-20 07:23:52 more
  • Django筆記二十五之資料庫函式之日期函式

    本文首發于公眾號:Hunter后端 原文鏈接:Django筆記二十五之資料庫函式之日期函式 日期函式主要介紹兩個大類,Extract() 和 Trunc() Extract() 函式作用是提取日期,比如我們可以提取一個日期欄位的年份,月份,日等資料 Trunc() 的作用則是截取,比如 2022-0 ......

    uj5u.com 2023-04-20 07:23:45 more
  • 一天吃透JVM面試八股文

    什么是JVM? JVM,全稱Java Virtual Machine(Java虛擬機),是通過在實際的計算機上仿真模擬各種計算機功能來實作的。由一套位元組碼指令集、一組暫存器、一個堆疊、一個垃圾回收堆和一個存盤方法域等組成。JVM屏蔽了與作業系統平臺相關的資訊,使得Java程式只需要生成在Java虛擬機 ......

    uj5u.com 2023-04-20 07:23:31 more
  • 使用Java接入小程式訂閱訊息!

    更新完微信服務號的模板訊息之后,我又趕緊把微信小程式的訂閱訊息給實作了!之前我一直以為微信小程式也是要企業才能申請,沒想到小程式個人就能申請。 訊息推送平臺🔥推送下發【郵件】【短信】【微信服務號】【微信小程式】【企業微信】【釘釘】等訊息型別。 https://gitee.com/zhongfuch ......

    uj5u.com 2023-04-20 07:22:59 more
  • java -- 緩沖流、轉換流、序列化流

    緩沖流 緩沖流, 也叫高效流, 按照資料型別分類: 位元組緩沖流:BufferedInputStream,BufferedOutputStream 字符緩沖流:BufferedReader,BufferedWriter 緩沖流的基本原理,是在創建流物件時,會創建一個內置的默認大小的緩沖區陣列,通過緩沖 ......

    uj5u.com 2023-04-20 07:22:49 more
  • Java-SpringBoot-Range請求頭設定實作視頻分段傳輸

    老實說,人太懶了,現在基本都不喜歡寫筆記了,但是網上有關Range請求頭的文章都太水了 下面是抄的一段StackOverflow的代碼...自己大修改過的,寫的注釋挺全的,應該直接看得懂,就不解釋了 寫的不好...只是希望能給視頻網站開發的新手一點點幫助吧. 業務場景:視頻分段傳輸、視頻多段傳輸(理 ......

    uj5u.com 2023-04-20 07:22:42 more
  • Windows 10開發教程_編程入門自學教程_菜鳥教程-免費教程分享

    教程簡介 Windows 10開發入門教程 - 從簡單的步驟了解Windows 10開發,從基本到高級概念,包括簡介,UWP,第一個應用程式,商店,XAML控制元件,資料系結,XAML性能,自適應設計,自適應UI,自適應代碼,檔案管理,SQLite資料庫,應用程式到應用程式通信,應用程式本地化,應用程式 ......

    uj5u.com 2023-04-20 07:22:35 more