主頁 > 移動端開發 > 抖音品質建設 - iOS啟動優化《原理篇》

抖音品質建設 - iOS啟動優化《原理篇》

2021-01-11 17:58:45 移動端開發

作者:位元組跳動技術團隊

 

前言

啟動是 App 給用戶的第一印象,啟動越慢用戶流失的概率就越高,良好的啟動速度是用戶體驗不可缺少的一環,啟動優化涉及到的知識點非常多面也很廣,一篇文章難以包含全部,所以拆分成兩部分:原理和實踐,

本文從基礎知識出發,先回顧一些核心概念,為后續章節做鋪墊;接下來介紹 IPA 構建的基本流程,以及這個流程里可用于啟動優化的點;最后大篇幅講解 dyld3 的啟動 pipeline,因為啟動優化的重點還在運行時,

 

小編推薦一個技術交流圈子會來淺談一下iOS開發中有哪些方向和職業規劃,同時小編也歡迎大家加入小編的可以加QQ群:1001906160! 群里會免費提供相關面試資料,書籍歡迎大家入駐!

基本概念

啟動的定義

啟動有兩種定義:

  • 廣義:點擊圖示到首頁資料加載完畢
  • 狹義:點擊圖示到 Launch Image 完全消失第一幀

不同產品的業務形態不一樣,對于抖音來說,首頁的資料加載完成就是視頻的第一幀播放;對其他首頁是靜態的 App 來說,Launch Image 消失就是首頁資料加載完成,由于標準很難對齊,所以我們一般使用狹義的啟動定義:即啟動終點為啟動圖完全消失的第一幀

以抖音為例,用戶感受到的啟動時間:

Tips:啟動最佳時間是 400ms 以內,因為啟動影片時長是 400ms,

這是從用戶感知維度定義啟動,那么代碼上如何定義啟動呢?Apple 在 MetricKit 中給出了官方計算方式:

  • 起點:行程創建的時間
  • 終點:第一個CA::Transaction::commit()

Tips:CATransaction 是 Core Animation 提供的一種事務機制,把一組 UI 上的修改打包,一起發給 Render Server 渲染,

啟動的種類

根據場景的不同,啟動可以分為三種:冷啟動,熱啟動和回前臺,

  • 冷啟動:系統里沒有任何行程的快取資訊,典型的是重啟手機后直接啟動 App
  • 熱啟動:如果把 App 行程殺了,然后立刻重新啟動,這次啟動就是熱啟動,因為行程快取還在
  • 回前臺:大多數時候不會被定義為啟動,因為此時 App 仍然活著,只不過處于 suspended 狀態

那么,線上用戶的冷啟動多還是熱啟動多呢?

答案是和產品形態有關系,打開頻次越高,熱啟動比例就越高,

Mach-O

Mach-O 是 iOS 可執行檔案的格式,典型的 Mach-O 是主二進制和動態庫,Mach-O 可以分為三部分:

  • Header
  • Load Commands
  • Data

Header 的最開始是 Magic Number,表示這是一個 Mach-O 檔案,除此之外還包含一些 Flags,這些 flags 會影響 Mach-O 的決議,

Load Commands 存盤 Mach-O 的布局資訊,比如 Segment command 和 Data 中的 Segment/Section 是一一對應的,除了布局資訊之外,還包含了依賴的動態庫等啟動 App 需要的資訊,

Data 部分包含了實際的代碼和資料,Data 被分割成很多個 Segment,每個 Segment 又被劃分成很多個 Section,分別存放不同型別的資料,

標準的三個 Segment 是 TEXT,DATA,LINKEDIT,也支持自定義:

  • TEXT,代碼段,只讀可執行,存盤函式的二進制代碼(__text),常量字串(__cstring),Objective C 的類/方法名等資訊
  • DATA,資料段,讀寫,存盤 Objective C 的字串(__cfstring),以及運行時的元資料:class/protocol/method…
  • LINKEDIT,啟動 App 需要的資訊,如 bind & rebase 的地址,代碼簽名,符號表…

dyld

dyld 是啟動的輔助程式,是 in-process 的,即啟動的時候會把 dyld 加載到行程的地址空間里,然后把后續的啟動程序交給 dyld,dyld 主要有兩個版本:dyld2 和 dyld3,

dyld2 是從 iOS 3.1 引入,一直持續到 iOS 12,dyld2 有個比較大的優化是dyld shared cache,什么是 shared cache 呢?

  • shared cache 就是把系統庫(UIKit 等)合成一個大的檔案,提高加載性能的快取檔案,

iOS 13 開始 Apple 對三方 App 啟用了 dyld3,dyld3 的最重要的特性就是啟動閉包,閉包里包含了啟動所需要的快取資訊,從而提高啟動速度,

虛擬記憶體

記憶體可以分為虛擬記憶體和物理記憶體,其中物理記憶體是實際占用的記憶體,虛擬記憶體是在物理記憶體之上建立的一層邏輯地址,保證記憶體訪問安全的同時為應用提供了連續的地址空間,

物理記憶體和虛擬記憶體以頁為單位映射,但這個映射關系不是一一對應的:一頁物理記憶體可能對應多頁虛擬記憶體;一頁虛擬記憶體也可能不占用物理記憶體,

iPhone 6s 開始,物理記憶體的 Page 大小是 16K,6 和之前的設備都是 4K,這是 iPhone 6 相比 6s 啟動速度斷崖式下降的原因之一

mmap

mmap 的全稱是 memory map,是一種記憶體映射技術,可以把檔案映射到虛擬記憶體的地址空間里,這樣就可以像直接操作記憶體那樣來讀寫檔案,當讀取虛擬記憶體,其對應的檔案內容在物理記憶體中不存在的時候,會觸發一個事件:File Backed Page In,把對應的檔案內容讀入物理記憶體

啟動的時候,Mach-O 就是通過 mmap 映射到虛擬記憶體里的(如下圖),下圖中部分頁被標記為 zero fill,是因為全域變數的初始值往往都是 0,那么這些 0 就沒必要存盤在二進制里,增加檔案大小,作業系統會識別出這些頁,在 Page In 之后對其置為 0,這個行為叫做 zero fill,

Page In

啟動的路徑上會觸發很多次 Page In,其實也比較容易理解,因為啟動的會讀寫二進制中的很多內容,Page In 會占去啟動耗時的很大一部分,我們來看看單個 Page In 的程序:

  • MMU 找到空閑的物理記憶體頁面
  • 觸發磁盤 IO,把資料讀入物理記憶體
  • 如果是 TEXT 段的頁,要進行解密
  • 對解密后的頁,進行簽名驗證

其中解密是大頭,IO 其次,

為什么要解密呢?因為 iTunes Connect 會對上傳 Mach-O 的 TEXT 段進行加密,防止 IPA 下載下來就直接可以看到代碼,這也就是為什么逆向里會有個概念叫做“砸殼”,砸的就是這一層 TEXT 段加密,iOS 13 對這個程序進行了優化,Page In 的時候不需要解密了

二進制重排

既然 Page In 耗時,有沒有什么辦法優化呢?啟動具有區域性特征,即只有少部分函式在啟動的時候用到,這些函式在二進制中的分布是零散的,所以 Page In 讀入的資料利用率并不高,如果我們可以把啟動用到的函式排列到二進制的連續區間,那么就可以減少 Page In 的次數,從而優化啟動時間:

以下圖為例,方法 1 和方法 3 是啟動的時候用到的,為了執行對應的代碼,就需要兩次 Page In,假如我們把方法 1 和 3 排列到一起,那么只需要一次 Page In,從而提升啟動速度,

聯結器 ld 有個引數-order_file 支持按照符號的方式排列二進制,獲取啟動時候用到的符號的有很多種方式,感興趣的同學可以看看抖音之前的文章:基于二進制檔案重排的解決方案 APP 啟動速度提升超 15%,

IPA 構建

pipeline

既然要構建,那么必然會有一些地方去定義如何構建,對應 Xcode 中的兩個配置項:

  • Build Phase:以 Target 為維度定義了構建的流程,可以在 Build Phase 中插入腳本,來做一些定制化的構建,比如 CocoaPod 的拷貝資源就是通過腳本的方式完成的,
  • Build Settings:配置編譯和鏈接相關的引數,特別要提到的是 other link flags 和 other c flags,因為編譯和鏈接的引數非常多,有些需要手動在這里配置,很多專案用的 CocoaPod 做的組件化,這時候編譯選項在對應的.xcconfig 檔案里,

以單 Target 為例,我們來看下構建流程:

  • 源檔案(.m/.c/.swift 等)是單獨編譯的,輸出對應的目標檔案(.o)
  • 目標檔案和靜態庫/動態庫一起,鏈接出最后的 Mach-O
  • Mach-O 會被裁剪,去掉一些不必要的資訊
  • 資源檔案如 storyboard,asset 也會編譯,編譯后加載速度會變快
  • Mach-O 和資源檔案一起,打包出最后的.app
  • 對.app 簽名,防篡改

編譯

編譯器可以分為兩大部分:前端和后端,二者以 IR(中間代碼)作為媒介,這樣前后端分離,使得前后端可以獨立的變化,互不影響,C 語言家族的前端是 clang,swift 的前端是 swiftc,二者的后端都是 llvm,

  • 前端負責預處理,詞法語法分析,生成 IR
  • 后端基于 IR 做優化,生成機器碼

那么如何利用編譯優化啟動速度呢?

代碼數量會影響啟動速度,為了提升啟動速度,我們可以把一些無用代碼下掉,那怎么統計哪些代碼沒有用到呢?可以利用 LLVM 插樁來實作,

LLVM 的代碼優化流程是一個一個 Pass,由于 LLVM 是開源的,我們可以添加一個自定義的 Pass,在函式的頭部插入一些代碼,這些代碼會記錄這個函式被呼叫了,然后把統計到的資料上傳分析,就可以知道哪些代碼是用不到的了 ,

Facebook 給 LLVM 提的order_file的 feature 就是實作了類似的插樁,

鏈接

經過編譯后,我們有很多個目標檔案,接著這些目標檔案會和靜態庫,動態庫一起,鏈接出一個 Mach-O,鏈接的程序并不產生新的代碼,只會做一些移動和補丁,

  • tbd 的全稱是 text-based stub library,是因為鏈接的程序中只需要符號就可以了,所以 Xcode 6 開始,像 UIKit 等系統庫就不提供完整的 Mach-O,而是提供一個只包含符號等資訊的 tbd 檔案,

舉一個基于鏈接優化啟動速度的例子:

最開始講解 Page In 的時候,我們提到 TEXT 段的頁解密很耗時,有沒有辦法優化呢?

可以通過 ld 的-rename_section,把 TEXT 段中的內容,比如字串移動到其他的段(啟動路徑上難免會讀很多字串),從而規避這個解密的耗時

抖音的重命名方案:

"-Wl,-rename_section,__TEXT,__cstring,__RODATA,__cstring",
"-Wl,-rename_section,__TEXT,__const,__RODATA,__const", 
"-Wl,-rename_section,__TEXT,__gcc_except_tab,__RODATA,__gcc_except_tab", 
"-Wl,-rename_section,__TEXT,__objc_methname,__RODATA,__objc_methname", 
"-Wl,-rename_section,__TEXT,__objc_classname,__RODATA,__objc_classname",
"-Wl,-rename_section,__TEXT,__objc_methtype,__RODATA,__objc_methtype"

復制代碼

裁剪

編譯完 Mach-O 之后會進行裁剪(strip),是因為里面有些資訊,如除錯符號,是不需要帶到線上去的,裁剪有多種級別,一般的配置如下:

  • All Symbols,主二進制
  • Non-Global Symbols,動態庫
  • Debugging Symbols,二方靜態庫

為什么二方庫在出靜態庫的時候要選擇 Debugging Symbols 呢?是因為像 order_file 等鏈接期間的優化是基于符號的,如果把符號裁剪掉,那么這些優化也就不會生效了

簽名 & 上傳

裁剪完二進制后,會和編譯好的資源檔案一起打包成.app 檔案,接著對這個檔案進行簽名,簽名的作用是保證檔案內容不多不少,沒有被篡改過,接著會把包上傳到 iTunes Connect,上傳后會對__TEXT段加密,加密會減弱 IPA 的壓縮效果,增加包大小,也會降低啟動速度 (iOS 13 優化了加密程序,不會對包大小和啟動耗時有影響)

dyld3 啟動流程

Apple 在 iOS 13 上對第三方 App 啟用了 dyld3,官方資料顯示,過去四年新發布的設備中有 93%的設備是 iOS 13,所以我們重點看下 dyld3 的啟動流程,

Before dyld

用戶點擊圖示之后,會發送一個系統呼叫 execve 到內核,內核創建行程,接著會把主二進制 mmap 進來,讀取 load command 中的 LC_LOAD_DYLINKER,找到 dyld 的的路徑,然后 mmap dyld 到虛擬記憶體,找到 dyld 的入口函式_dyld_start,把 PC 暫存器設定成_dyld_start,接下來啟動流程交給了 dyld,

注意這個程序都是在內核態完成的,這里提到了 PC 暫存器,PC 暫存器存盤了下一條指令的地址,程式的執行就是不斷修改和讀取 PC 暫存器來完成的,

dyld

創建啟動閉包

dyld 會首先創建啟動閉包,閉包是一個快取,用來提升啟動速度的,既然是快取,那么必然不是每次啟動都創建的,只有在重啟手機或者更新/下載 App 的第一次啟動才會創建,閉包存盤在沙盒的 tmp/com.apple.dyld 目錄,清理快取的時候切記不要清理這個目錄

閉包是怎么提升啟動速度的呢?我們先來看一下閉包里都有什么內容:

  • dependends,依賴動態庫串列
  • fixup:bind & rebase 的地址
  • initializer-order:初始化呼叫順序
  • optimizeObjc: Objective C 的元資料
  • 其他:main entry, uuid…

動態庫的依賴是樹狀的結構,初始化的呼叫順序是先呼叫樹的葉子結點,然后一層層向上,最先呼叫的是 libSystem,因為他是所有依賴的源頭,

為什么閉包能提高啟動速度呢?

因為這些資訊是每次啟動都需要的,把資訊存盤到一個快取檔案就能避免每次都決議,尤其是 Objective C 的運行時資料(Class/Method...)決議非常慢

fixup

有了閉包之后,就可以用閉包啟動 App 了,這時候很多動態庫還沒有加載進來,會首先對這些動態庫 mmap 加載到虛擬記憶體里,接著會對每個 Mach-O 做 fixup,包括 Rebase 和 Bind,

  • Rebase:修復內部指標,這是因為 Mach-O 在 mmap 到虛擬記憶體的時候,起始地址會有一個隨機的偏移量 slide,需要把內部的指標指向加上這個 slide,
  • Bind:修復外部指標,這個比較好理解,因為像 printf 等外部函式,只有運行時才知道它的地址是什么,bind 就是把指標指向這個地址,

舉個例子:一個 Objective C 字串@"1234",編譯到最后的二進制的時候是會存盤在兩個 section 里的

  • __TEXT,__cstring,存盤實際的字串"1234"
  • __DATA,__cfstring,存盤 Objective C 字串的元資料,每個元資料占用 32Byte,里面有兩個指標:內部指標,指向__TEXT,__cstring中字串的位置;外部指標 isa,指向類物件的,這就是為什么可以對 Objective C 的字串字面量發訊息的原因,

如下圖,編譯的時候,字串 1234 在__cstring的 0x10 處,所以 DATA 段的指標指向 0x10,但是 mmap 之后有一個偏移量 slide=0x1000,這時候字串在運行時的地址就是 0x1010,那么 DATA 段的指標指向就不對了,Rebase 的程序就是把指標從 0x10,加上 slide 變成 0x1010,運行時類物件的地址已經知道了,bind 就是把 isa 指向實際的記憶體地址

LibSystem Initializer

Bind & Rebase 之后,首先會執行 LibSystem 的 Initializer,做一些最基本的初始化:

  • 初始化 libdispatch
  • 初始化 objc runtime,注冊 sel,加載 category

注意這里沒有初始化 objc 的類方法等資訊,是因為啟動閉包的快取資料已經包含了 optimizeObjc,

Load & Static Initializer

接下來會進行 main 函式之前的一些初始化,主要包括+load 和 static initializer,這兩類初始化函式都有個特點:呼叫順序不確定,和對應檔案的鏈接順序有關系,那么就會存在一個隱藏的坑:有些注冊邏輯在+load 里,對應會有一些地方讀取這些注冊的資料,如果在+load 中讀取,很有可能讀取的時候還沒有注冊,

那么,如何找到代碼里有哪些 load 和 static initializer 呢?

在 Build Settings 里可以配置 write linkmap,這樣在生成的 linkmap 檔案里就可以找到有哪些檔案里包含 load 或者 static initializer:

  • __mod_init_func,static initializer
  • __objc_nlclslist,實作+load 的類
  • __objc_nlcatlist,實作+load 的 Category

load 舉例

如果+load 方法里的內容很簡單,會影響啟動時間么?比如這樣的一個+load 方法?

+ (void)load 
{
    printf("1234");
}
復制代碼

編譯完了之后,這個函式會在二進制中的 TEXT 兩個段存在:__text存函式二進制,cstring存盤字串 1234,為了執行函式,首先要訪問__text觸發一次 Page In 讀入物理記憶體,為了列印字串,要訪問__cstring,還會觸發一次 Page In,

  • 為了執行這個簡單的函式,系統要額外付出兩次 Page In 的代價,所以 load 函式多了,page in 會成為啟動性能的瓶頸,

static initializer 產生的條件

靜態初始化是從哪來的呢?以下幾種代碼會導致靜態初始化

  • __attribute__((constructor))
  • static class object
  • static object in global namespace

注意,并不是所有的 static 變數都會產生靜態初始化,編譯器很智能,對于在編譯期間就能確定的變數是會直接 inline,

//會產生靜態初始化
class Demo{ 
static const std::string var_1; 
};
const std::string var_2 = "1234"; 
static Logger logger;
//不會產生靜態初始化
static const int var_3 = 4; 
static const char * var_4 = "1234";
復制代碼

std::string 會合成 static initializer 是因為初始化的時候必須執行建構式,這時候編譯器就不知道怎么做了,只能延遲到運行時~

UIKit Init

+load 和 static initializer 執行完畢之后,dyld 會把啟動流程交給 App,開始執行 main 函式,main 函式里要做的最重要的事情就是初始化 UIKit,UIKit 主要會做兩個大的初始化:

  • 初始化 UIApplication
  • 啟動主執行緒的 Runloop

由于主執行緒的 dispatch_async 是基于 runloop 的,所以在+load 里如果呼叫了 dispatch_async 會在這個階段執行,

Runloop

執行緒在執行完代碼就會退出,很明顯主執行緒是不能退出的,那么就需要一種機制:事件來的時候執行任務,否則讓執行緒休眠,Runloop 就是實作這個功能的,

Runloop 本質上是一個 While 回圈,在圖中橙色部分的 mach_msg_trap 就是觸發一個系統呼叫,讓執行緒休眠,等待事件到來,喚醒 Runloop,繼續執行這個 while 回圈,

Runloop 主要處理幾種任務:Source0,Source1,Timer,GCD MainQueue,Block,在回圈的合適時機,會以 Observer 的方式通知外部執行到了哪里,

那么,Runloop 與啟動又有什么關系呢?

  • App 的 LifeCycle 方法是基于 Runloop 的 Source0 的
  • 首幀渲染是基于 Runloop Block 的

Runloop 在啟動上主要有幾點應用:

  • 精準統計啟動時間
  • 找到一個時機,在啟動結束去執行一些預熱任務
  • 利用 Runloop 打散耗時的啟動預熱任務

Tips: 會有一些邏輯要在啟動之后 delay 一小段時間再回到主執行緒上執行,對于性能較差的設備,主執行緒 Runloop 可能一直處于忙的狀態,所以這個 delay 的任務并不一定能按時執行,

AppLifeCycle

UIKit 初始化之后,就進入了我們熟悉的 UIApplicationDelegate 回呼了,在這些會調里去做一些業務上的初始化:

  • willFinishLaunch

  • didFinishLaunch

  • didFinishLaunchNotification

要特別提一下 didFinishLaunchNotification,是因為大家在埋點的時候通常會忽略還有這個通知的存在,導致把這部分時間算到 UI 渲染里,

First Frame Render

一般會用 Root Controller 的 viewDidApper 作為渲染的終點,但其實這時候首幀已經渲染完成一小段時間了,Apple 在 MetricsKit 里對啟動終點定義是第一個CA::Transaction::commit()

什么是 CATransaction 呢?我們先來看一下渲染的大致流程

iOS 的渲染是在一個單獨的行程 RenderServer 做的,App 會把 Render Tree 編碼打包給 RenderServer,RenderServer 再呼叫渲染框架(Metal/OpenGL ES)來生成 bitmap,放到幀緩沖區里,硬體根據時鐘信號讀取幀緩沖區內容,完成螢屏重繪,CATransaction 就是把一組 UI 上的修改,合并成一個事務,通過 commit 提交,

渲染可以分為四個步驟

  • Layout(布局),源頭是 Root Layer 呼叫[CALayer layoutSubLayers],這時候 UIViewControllerviewDidLoadLayoutSubViews 會呼叫,autolayout 也是在這一步生效
  • Display(繪制),源頭是 Root Layer 呼叫[CALayer display],如果 View 實作了 drawRect 方法,會在這個階段呼叫
  • Prepare(準備),這個程序中會完成圖片的解碼
  • Commit(提交),打包 Render Tree 通過 XPC 的方式發給 Render Server

啟動 Pipeline

詳細回顧下整個啟動程序,以及各個階段耗時的影響因素:

  1. 點擊圖示,創建行程
  2. mmap 主二進制,找到 dyld 的路徑
  3. mmap dyld,把入口地址設為_dyld_start
  4. 重啟手機/更新/下載 App 的第一次啟動,會創建啟動閉包
  5. 把沒有加載的動態庫 mmap 進來,動態庫的數量會影響這個階段
  6. 對每個二進制做 bind 和 rebase,主要耗時在 Page In,影響 Page In 數量的是 objc 的元資料
  7. 初始化 objc 的 runtime,由于閉包已經初始化了大部分,這里只會注冊 sel 和裝載 category
  8. +load 和靜態初始化被呼叫,除了方法本身耗時,這里還會引起大量 Page In
  9. 初始化 UIApplication,啟動 Main Runloop
  10. 執行 will/didFinishLaunch,這里主要是業務代碼耗時
  11. Layout,viewDidLoad Layoutsubviews 會在這里呼叫,Autolayout 太多會影響這部分時間
  12. Display,drawRect 會呼叫
  13. Prepare,圖片解碼發生在這一步
  14. Commit,首幀渲染資料打包發給 RenderServer,啟動結束

dyld2

dyld2 和 dyld3 的主要區別就是沒有啟動閉包,就導致每次啟動都要:

  • 決議動態庫的依賴關系
  • 決議 LINKEDIT,找到 bind & rebase 的指標地址,找到 bind 符號的地址
  • 注冊 objc 的 Class/Method 等元資料,對大型工程來說,這部分耗時會很長

總結

本文回顧了 Mach-O,虛擬記憶體,mmap,Page In,Runloop 等基礎概念,接下來介紹了 IPA 的構建流程,以及兩個典型的利用編譯器來優化啟動的方案,最后詳細的講解了 dyld3 的啟動 pipeline,

之所以花這么大篇幅講原理,是因為任何優化都一樣,只有深入理解系統運作的原理,才能找到性能的瓶頸,下一篇我們會介紹下如何利用這些原理解決實際問題,




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

標籤:其他

上一篇:抖音品質建設 - iOS啟動優化《原理篇》

下一篇:Android學習目錄(三)

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