主頁 > 移動端開發 > iOS之性能優化·UITableView深度優化

iOS之性能優化·UITableView深度優化

2021-10-24 09:50:15 移動端開發

一、前言

  • UITableView 的優化主要從四個方面入手:
    • 提前計算并快取好高度(布局),因為 tableView:heightForRowAtIndexPath: 是呼叫最頻繁的方法;
    • 滑動時按需加載,防止卡頓,這個在大量圖片展示,網路加載的時候很管用,配合 SDWebImage;
    • 異步繪制,遇到復雜界面,遇到性能瓶頸時,可能就是突破口;
    • 快取一切可以快取的,這個在開發的時候,往往是性能優化最多的方向,
  • 大概需要關注的:
    • cell 復用;
    • cell 高度的計算;
    • 渲染(混合問題);
    • 減少視圖的數目(重寫 drawRect:);
    • 減少多余的繪制操作;
    • 不要給 cell 動態添加 subView;
    • 異步化 UI,不要阻塞主執行緒;
    • 滑動時按需加載對應的內容,

二、Cell 復用

  • UITableView 最核心的思想就是 UITableViewCell 的重用機制,簡單的理解就是:UITableView 只會創建一螢屏(或一螢屏多一點)的 UITableViewCell,其它都是從中取出來重用的,每當 Cell 滑出螢屏時,就會放入到一個集合(或陣列)中(相當于一個重用池),當要顯示某一位置的 Cell 時,會先去集合(或陣列)中取,如果有,就直接拿來顯示;如果沒有,才會創建,這樣做的好處可想而知,極大的減少了記憶體的開銷,
  • 了解了 UITableViewCell 的重用原理后,來看看 UITableView 的回呼方法,UITableView 最主要的兩個回呼方法:
tableView:cellForRowAtIndexPath: 
tableView:heightForRowAtIndexPath:
  • 理想上我們是會認為 UITableView 會先呼叫前者,再呼叫后者,因為這和創建控制元件的思路是一樣的,先創建它,再設定它的布局,但實際上卻并非如此,
  • 我們都知道,UITableView 是繼承自 UIScrollView 的,需要先確定它的 contentSize 及每個 Cell 的位置,然后才會把重用的 Cell 放置到對應的位置,所以事實上,UITableView 的回呼順序是先多次呼叫 tableView:heightForRowAtIndexPath: 以確定 contentSize 及 Cell 的位置,然后才會呼叫 tableView:cellForRowAtIndexPath:,從而來顯示在當前螢屏的 Cell,
  • 因此,在可見的頁面會重復繪制頁面,每次重繪顯示都會去創建新的 Cell,非常耗費性能, 解決方案就是創建一個靜態變數 reuseID,防止重復創建(提高性能),使用系統的快取池功能,
 // 呼叫次數太多,static 保證只創建一次 reuseID,提高性能
static NSString *kCELL_RUID = @"Cell"; 

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    // 快取池中取已創建的 cell
    UITableViewCell * cell = [tableView dequeueReusableCellWithIdentifier:kCELL_RUID
                                                             forIndexPath:indexPath];
    return cell;
}
  • 通過 identifier 標識不同型別的 cell,快取池中只會保存已經被移出螢屏的不同型別的 cell,復用 Cell 時 不會呼叫 awakeFromNib:
 - (nullable __kindof UITableViewCell *)dequeueReusableCellWithIdentifier:(NSString *)identifier;  // Used by the delegate to acquire an already allocated cell, in lieu of allocating a new one.
 - (__kindof UITableViewCell *)dequeueReusableCellWithIdentifier:(NSString *)identifier forIndexPath:(NSIndexPath *)indexPath NS_AVAILABLE_IOS(6_0); // newer dequeue method guarantees a cell is returned and resized properly, assuming identifier is registered
  • 兩個獲取方法的區別:
    • dequeueReusableCellWithIdentifier:forIndexPath 如果沒有注冊復用 identifier,執行這句時會崩潰,提示:
reason: 'unable to dequeue a cell with identifier CELL - must register a nib or a class for the identifier or connect a prototype cell in a storyboard'
    • dequeueReusableCellWithIdentifier 如果沒有注冊復用 identifier,陳述句回傳 nil,繼續執行會崩潰,提示:
failed to obtain a cell from its dataSource
    • 判斷 nil 后可以自己創建 cell,如下:
MyCell * cell = [tableView dequeueReusableCellWithIdentifier:@"Cell"];
if (cell == nil) {
	cell = [[MyCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:@"Cell"];
}
  • 為什么需要 forIndexPath:?
    • 因為在回傳 cell 之前,會呼叫委托 tableView:heightForRowAtIndexPath:來確定 cell 尺寸(如果已經定義該函式),
    • 我們經常在 tableView:cellForRowAtIndexPath: 中為每一個 cell 系結資料,實際上在呼叫 cellForRowAtIndexPath: 的時候 cell 還沒有被顯示出來,為了提高效率應該把資料系結的操作放在 cell 顯示出來后再執行,可以在 tableView:willDisplayCell:forRowAtIndexPath: 方法中系結資料,
    • 注意 willDisplayCell 中 cell 在 tableview 展示之前就會呼叫,此時 cell 實體已經生成,所以不能更改 cell 的結構,只能是改動 cell 上的 UI 的一些屬性,如 label 的內容、控制元件的隱藏等,

三、定義一種(盡量少)型別的 Cell 及善用 hidden 隱藏(顯示)subviews

  • 分析 Cell 結構,盡可能的將相同內容的抽取到一種樣式 Cell 中,UITableView 真正創建出的 Cell 可能只比螢屏顯示的多一點,雖然 Cell 的“體積”可能會大點,但是因為 Cell 的數量不會很多,完全可以接受的,
  • 這樣的好處就是:
    • 減少代碼量,減少 Nib 檔案的數量,在一個 Nib 檔案定義 Cell,容易修改、維護;
    • 基于復用機制,真正運行時鋪滿螢屏所需的 Cell 數量大致是固定的,設為 N 個,如果只有一種 cell,那就是只有 N + c 個 cell 的實體;但是如果有 M 種 cell,那么運行時最多可能會是 M * (N + c) 個 cell 的實體,雖然這可能并不會占用太多記憶體,但能少一些更好,
  • 既然只定義一種 Cell,那么需要把所有不同型別的 view 都定義好,放在 Cell 里面,通過 hidden 屬性控制,來顯示不同型別的內容,畢竟,在用戶快速滑動中,只是單純的顯示/隱藏 subview 比實時創建要快得多,
  • 盡量少用 [cell addSubview:] 動態添加 View,可以初始化時就添加,然后通過 hidden 屬性來控制,

四、提前計算并快取 Cell 的高度

① rowHeight

  • UITableView 詢問 cell 高度有兩種方式:
    • 一種是針對所有 Cell 具有固定高度的情況,通過:
self.tableView.rowHeight = 88;
    • 直接采用上面方式給定高度,不需要實作 tableView:heightForRowAtIndexPath: 以節省不必要的計算和開銷,指定一個所有 cell 都是 88 高度的 UITableView,對于定高需求的表格,強烈建議使用這種(而非下面的)方式保證不必要的高度計算和呼叫,rowHeight 屬性的默認值是 44,
    • 另一種方式就是實作 UITableViewDelegate 中的:
 - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
    // return xxx
}
    • 實作了這個方法后,rowHeight 的設定將無效,因此這個方法適用于具有多種 cell 高度的 UITableView,

② estimatedRowHeight

  • iOS7 就出現這個屬性, 檔案是這么描述它的作用:
If the table contains variable height rows, it might be expensive to calculate all their heights when the table loads. Using estimation allows you to defer some of the cost of geometry calculation from load time to scrolling time.
  • UITableView 是個 UIScrollView,就像平時使用 UIScrollView 一樣,加載時指定 contentSize 后它才能根據自己的 bounds、contentInset、contentOffset 等屬性共同決定是否可以滑動以及滾動條的長度,而 UITableView 在一開始并不知道自己會被填充多少內容,于是詢問 data source 個數和創建 cell,同時詢問 delegate 這些 cell 應該顯示的高度,這就造成它在加載的時候浪費了多余的計算在螢屏外邊的 cell 上,
  • 和上面的 rowHeight 很類似,設定這個估算高度有兩種方法:
self.tableView.estimatedRowHeight = 88;
// or
 - (CGFloat)tableView:(UITableView *)tableView estimatedHeightForRowAtIndexPath:(NSIndexPath *)indexPath {
    // return xxx
}
  • 有所不同的是,即使面對種類不同的 cell,依然可以使用簡單的 estimatedRowHeight 屬性賦值,只要整體估算值接近就可以,比如大概有一半 cell 高度是 44, 一半 cell 高度是 88, 那就可以估算一個 66,基本符合預期,
  • 說完了估算高度的基本使用,會有以下問題:
    • 設定估算高度后,contentSize.height 根據“cell估算值 x cell個數”計算,這就導致滾動條的大小處于不穩定的狀態,contentSize 會隨著滾動從估算高度慢慢替換成真實高度,肉眼可見滾動條突然變化甚至“跳躍”;
    • 若是有設計不好的下拉重繪或上拉加載控制元件,或是 KVO 了 contentSize 或 contentOffset 屬性,有可能使表格滑動時跳動;
    • 估算高度設計初衷是好的,讓加載速度更快,那憑啥要去侵害滑動的流暢性呢,用戶可能對進入頁面時多零點幾秒加載時間感覺不大,但是滑動時實時計算高度帶來的卡頓是明顯能體驗到的,個人覺得還不如一開始都算好(iOS8 之后更過分,即使都算好了也會邊劃邊計算),
  • 因此,tableView:estimatedHeightForRowAtIndexPath: -> tableView:heightForRowAtIndexPath: 獲取每個 Cell 即將顯示的高度,從而確定表格視圖的布局,實際是要獲取滾動視圖的 contentSize,然后呼叫 tableView:cellForRowAtIndexPath:,獲取每個 Cell,進行賦值,如果有很多個 Cell 要顯示,那么方法會執行很多次,
  • 解決方案:在 Model(Entity)中計算并保存 Cell 的高度,其實 Model 中保存 UI 的引數是很奇怪的,最好放在 MVVM 模式的 ViewModel(視圖模型)中,讓 Model(資料模型)只負責處理資料,
@interface Model : NSObject

@property (nonatomic, assign) CGFloat cellHeight;  // Cell 高度

/**
 * @brief  計算高度
 */ 
- (void)calculateCellHeight;

@end
  • 在 tableView:heightForRowAtIndexPath: 中盡量不使用 cellForRowAtIndexPath: 方法來獲取 cell,如果需要用到它,只用一次然后快取結果,
  • 還可以繼續進行優化,提前創建真正顯示的、需要加工的資料并快取,如:介面回傳 NSString 而展示 NSAttributeString,

③ iOS8 self-sizing cell

    • 具有動態高度內容的 cell 一直是個頭疼的問題,比如聊天氣泡的 cell, frame 布局時代通常是用資料內容反算高度:
CGFloat height = textHeightWithFont() + imageHeight + topMargin + bottomMargin + ...;
  • 供 UITableViewDelegate 呼叫時很可能是個 cell 的類方法:
@interface BubbleCell : UITableViewCell
 + (CGFloat)heightWithEntity:(id)entity;
@end
  • AutoLayout 時代好了不少,提供了 -systemLayoutSizeFittingSize: 的 API,在 contentView 中設定約束后,就能計算出準確的值;缺點是計算速度肯定沒有手算快,而且這是個實體方法,需要維護專門為計算高度而生的 template layout cell,它還要求使用者對約束設定的比較熟練,要保證 contentView 內部上下左右所有方向都有約束支撐,設定不合理的話計算的高度就成了0,
  • 這里不得不提到一個 UILabel 的蛋疼問題,當 UILabel 行數大于 0 時,需要指定 preferredMaxLayoutWidth 后它才知道自己什么時候該換行,因為 UILabel 需要知道 superview 的寬度才能換,而 superview 的寬度還依仗著子 view 寬度的累加才能確定,
  • 自從 iOS8 之后,有了 self-sizing Cell 的概念,Cell 可以自己算出高度,使用 self-sizing cell 需要滿足以下三個條件:
    • 使用 AutoLayout 進行 UI 布局約束,要求 cell.contentView 的四條邊都與內部元素有約束關系;
    • 指定 TableView 的 estimatedRowHeight 屬性的默認值;
    • 指定 TableView 的 rowHeight 屬性為 UITableViewAutomaticDimension;
	self.tableView.estimatedRowHeight = 44.0;
    self.tableView.rowHeight = UITableViewAutomaticDimension;
  • 這里又不得不吐槽,自動計算 rowHeight 跟 estimatedRowHeight 到底是有什么仇,如果不加上估算高度的設定,自動算高就失效了,iOS8 系統中 rowHeight 的默認值已經設定成了 UITableViewAutomaticDimension,所以第二行代碼可以省略,

④ UITableView+FDTemplateLayoutCell

  • 使用 UITableView+FDTemplateLayoutCell 無疑是解決算高問題的最佳實踐之一,既有 iOS8 以后的 self-sizing 功能簡單的 API,又可以達到 iOS7 流暢的滑動效果,還保持了最低支持 iOS6,這個開源的擴展,請參考:UITableView-FDTemplateLayoutCell,
  • 使用 Cocoapods 可以直接安裝:
pod search UITableView+FDTemplateLayoutCell
  • 使用起來大概是這樣:
#import <UITableView+FDTemplateLayoutCell.h>
 - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
    return [tableView fd_heightForCellWithIdentifier:@"identifer" cacheByIndexPath:indexPath configuration:^(id cell) {
        // 配置 cell 的資料源,和 "cellForRow" 干的事一致,比如:
        cell.entity = self.feedEntities[indexPath.row];
    }];
}
  • 以上代碼,產生的優化收益:
    • 和每個 UITableViewCell ReuseID 一一對應的 template layout cell,這個 cell 只為參加高度計算,不會真的顯示到螢屏上;它通過 UITableView 的 -dequeueCellForReuseIdentifier: 方法 lazy 創建并保存,所以要求這個 ReuseID 必須已經被注冊到了 UITableView 中,也就是說,要么是 Storyboard 中的原型 cell,要么就是使用了 UITableView 的 -registerClass:forCellReuseIdentifier: 或 -registerNib:forCellReuseIdentifier:其中之一的注冊方法;
    • 根據 autolayout 約束自動計算高度:使用系統提供的 API:-systemLayoutSizeFittingSize:;
    • 根據 index path 的一套高度快取機制:計算出的高度會自動進行快取,所以滑動時每個 cell 真正的高度計算只會發生一次,后面的高度詢問都會命中快取,減少了非常可觀的多余計算;
    • 自動的快取失效機制:無須擔心資料源的變化引起的快取失效,當呼叫如 -reloadData,-deleteRowsAtIndexPaths:withRowAnimation: 等任何一個觸發 UITableView 重繪機制的方法時,已有的高度快取將以最小的代價執行失效,如洗掉一個 indexPath 為 [0:5] 的 cell 時,[0:0] ~ [0:4] 的高度快取不受影響,而 [0:5] 后面所有的快取值都向前移動一個位置;自動快取失效機制對 UITableView 的 9 個公有 API 都進行了分別的處理,以保證沒有一次多余的高度計算;
    • 預快取機制:預快取機制將在 UITableView 沒有滑動的空閑時刻執行,計算和快取那些還沒有顯示到螢屏中的 cell,整個快取程序完全沒有感知,這使得完整串列的高度計算既沒有發生在加載時,又沒有發生在滑動時,同時保證了加載速度和滑動流暢性,

⑤ 在 Model(Entity)中計算并保存 Cell 的高度

  • 在 Model(Entity)中保存 UI 的引數是很奇怪的,最好放在 ViewModel 中,就是 MVVM 模式的,那么 Entity 可能就是如下樣子:
@interface DataEntity : NSObject

// 原始資料
@property(copy, nonatomic) NSString *content;
@property(copy, nonatomic) NSString *title;

// Cell 高度
@property(assign, nonatomic) CGFloat cellHeight;

// 計算高度
- (void)calculateCellHeight;

@end
  • 這樣,就不用在 tableView:heightForRowAtIndexPath: 中每次都計算 cell 的高度,

五、異步繪制(自定義 Cell 繪制)

  • 遇到比較復雜的界面時(復雜點的圖文混排),上面快取行高的方式可能就不能滿足要求,繪制的各個資訊都是根據之前算好的布局進行繪制的,那么就需要異步繪制:
/**
 *  @brief  cell 添加 draw 方法
 */
- (void)draw {
    // 異步繪制
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        
    });
}

/** 
 *  @brief  重寫 drawRect: 方法 
 */
- (void)drawRect:(CGRect)rect {
    // 不需要用 GCD 異步執行緒,因為 drawRect: 本來就是異步繪制的
}
  • 具體分析可以參考:詳細整理UITableView優化技巧,

六、滑動時,按需加載

  • 自定義 Cell 的種類千奇百怪,但它本來就是用來顯示資料的,幾乎百分之百的時候都帶有圖片,這個時候就要考慮,下滑的程序中可能會有點卡頓,尤其網路不好的時候,異步加載圖片是個程式員都會想到,但是如果給每個回圈物件都加上異步加載,開啟的執行緒太多,一樣會卡頓,
  • 這個時候,利用 UIScrollViewDelegate 兩個代理方法就能很好地解決這個問題,如下所示:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { 
    if (needLoadArr.count > 0 && [needLoadArr indexOfObject:indexPath] == NSNotFound) {
         [cell clear];  // 清掉內容
    } 
    return cell;
}

// 按需加載 - 如果目標行與當前行相差超過指定行數,只在目標滾動范圍的前后指定 3 行加載
 - (void)scrollViewWillEndDragging:(UIScrollView *)scrollView withVelocity:(CGPoint)velocity targetContentOffset:(inout CGPoint *)targetContentOffset {
    NSIndexPath * ip  = [self.tableView indexPathForRowAtPoint:CGPointMake(0, targetContentOffset->y)];
    NSIndexPath * cip = [[self.tableView indexPathsForVisibleRows] firstObject];
    
    NSInteger skipCount = 8;
    
    // -8 < 當前位置 - 目標位置 < 8
    if (labs(cip.row - ip.row) > skipCount) {
        // 目標區域的 cell 的 indexPaths
        NSArray * temp = [self.tableView indexPathsForRowsInRect:CGRectMake(0, targetContentOffset->y, self.tableView.frame.size.width, self.tableView.frame.size.height)];
        
        NSMutableArray *arr = [NSMutableArray arrayWithArray:temp];
        if (velocity.y < 0) {
            NSIndexPath *indexPath = [temp lastObject];
            if (indexPath.row + 33) {
                [arr addObject:[NSIndexPath indexPathForRow:indexPath.row - 3 inSection:0]];
                [arr addObject:[NSIndexPath indexPathForRow:indexPath.row - 2 inSection:0]];
                [arr addObject:[NSIndexPath indexPathForRow:indexPath.row - 1 inSection:0]];
            }
        }
        [needLoadArr addObjectsFromArray:arr];
    }
}
  • 識別 UITableView 拖拽即將結束的時候,進行異步加載圖片,快滑動程序中,只加載目標范圍內的 Cell,這樣按需加載,極大的提高流暢度,而 SDWebImage 可以實作異步加載,與這條性能配合就完美了,尤其是大量圖片展示的時候,而且也不用擔心圖片快取會造成記憶體警告的問題,

七、快取 View

  • 當 Cell 中的部分 View 是非常獨立且不便于重用的,"體積"非常小,在記憶體可控的前提下,完全可以將這些 view 快取起來,
  • 方法當然也是將快取的 view 放在 Entity 中,

八、盡量顯示大小剛好合適的圖片資源

  • 避免大量的圖片縮放、顏色漸變等,

九、避免同步的從網路、檔案獲取資料

  • Cell 內實作的內容來自 web,使用異步加載,快取請求結果,

十、渲染

  • 減少 subviews 的個數和層級:子控制元件的層級越深,渲染到螢屏上所需要的計算量就越大;如多用 drawRect 繪制元素,替代用 view 顯示,
  • 少用 subviews 的透明圖層:渲染最耗時的操作之一就是混合(blending)了,對于不透明的 View,設定 opaque = YES,這樣在繪制該 View 時,避免 GPU 對 View 覆寫的其他內容也進行繪制,
  • 背景色不要使用 clearColor;
  • 避免 CALayer 特效(shadowPath):給 Cell 中 View 加陰影會引起性能問題,如下面代碼會導致滾動時有明顯的卡頓:
view.layer.shadowColor   = color.CGColor;
view.layer.shadowOffset  = offset;

view.layer.shadowOpacity = 1;

view.layer.shadowRadius  = radius;
  • 當有影像時,預渲染影像,在 bitmap context 先將其畫一遍,匯出成 UIImage 物件,然后再繪制到螢屏,這會大大提高渲染速度,

十一、異步加載影像

  • 快取影像可以幫助我們在應用程式中快速實體化 tableView,并快速回應滾動,圖片不是資產目錄的一部分,而是應用包的一部分,用來模擬通過 URL 異步加載每個圖片,這確保了用戶界面保持回應性,
  • 當用戶在視圖中滾動時,應用程式會反復請求相同的影像,保存相關的完成模塊直到影像加載,然后將影像傳遞給所有請求塊,因此 API 只需要呼叫一次就可以為給定 URL 獲取影像,
  • 如下所示,展示了專案如何構造一個基本的快取和加載方法:
final func load(url: NSURL, item: Item, completion: @escaping (Item, UIImage?) -> Swift.Void) {
    // Check for a cached image.
    if let cachedImage = image(url: url) {
        DispatchQueue.main.async {
            completion(item, cachedImage)
        }
        return
    }
    // In case there are more than one requestor for the image, we append their completion block.
    if loadingResponses[url] != nil {
        loadingResponses[url]?.append(completion)
        return
    } else {
        loadingResponses[url] = [completion]
    }
    // Go fetch the image.
    ImageURLProtocol.urlSession().dataTask(with: url as URL) { (data, response, error) in
        // Check for the error, then data and try to create the image.
        guard let responseData = data, let image = UIImage(data: responseData),
            let blocks = self.loadingResponses[url], error == nil else {
            DispatchQueue.main.async {
                completion(item, nil)
            }
            return
        }
        // Cache the image.
        self.cachedImages.setObject(image, forKey: url, cost: responseData.count)
        // Iterate over each requestor for the image and pass it back.
        for block in blocks {
            DispatchQueue.main.async {
                block(item, image)
            }
            return
        }
    }.resume()
}
  • 在啟動時加載所有資料的應用有耗盡記憶體或因耗時太長而終止的風險,除非應用程式需要在操作前加載所有資料,否則在 UI 請求時加載影像,
  • 通常,應用程式應該等到資料源請求一個單元格來獲取和設定一個影像,如下,演示在可重用視圖中獲取和顯示影像的一種方法:
var content = cell.defaultContentConfiguration()
content.image = item.image
ImageCache.publicCache.load(url: item.url as NSURL, item: item) { (fetchedItem, image) in
    if let img = image, img != fetchedItem.image {
        var updatedSnapshot = self.dataSource.snapshot()
        if let datasourceIndex = updatedSnapshot.indexOfItem(fetchedItem) {
            let item = self.imageObjects[datasourceIndex]
            item.image = img
            updatedSnapshot.reloadItems([item])
            self.dataSource.apply(updatedSnapshot, animatingDifferences: true)
        }
    }
}
cell.contentConfiguration = content

十二、參考資料

  • VVeboTableViewDemo;
  • UITableView-FDTemplateLayoutCell;
  • LazyTableImages,

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

標籤:其他

上一篇:RxJava 的基本概念和基本實作

下一篇:Android 12 第一次運行就報錯

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