Android Weekly Notes Issue #227

Android Weekly Issue #227

October 16th, 2016
Android Weekly Issue #227.html

本期內容包括: Google的Mobile Vision API 人臉檢測; Firebase的Remote Config; 與HashMap有關的優化; 提升RecyclerView幀率的優化; 使用AutoValue生成model代碼; 開源庫中抽象類和接口的使用討論; Bottom Sheet的使用; Android Studio中的版本控制系統; ConstraintLayout的使用; 應用換Bottom Navigation; Nougat的Messaging Style Notification; 自定義字體; Reductor的使用等.java

ARTICLES & TUTORIALS

Face Detection Concepts Overview

這篇文章來自Mobile Vision, 講人臉檢測及相關概念.
API使用Tutorial.
Sample.android

Exploring Firebase on Android & iOS: Remote Config

Remote config是Firebase提供的一個feature, 讓咱們能夠定義參數, 在firebase的console管理, 從而在server端控制應用的UI或者行爲, 而且能夠選擇生效的用戶範圍.ios

以前還有這個文章是關於Firebase Analytics的.git

本篇文章介紹了Firebase的Remote Config能夠幹什麼, 以及怎麼作, 解說很詳細.github

參數
咱們用Remote Config定義的鍵值對叫參數(parameters). 它提供了這個參數相關的what信息(key, the identifier), 和how信息(value, the configuration).編程

條件
條件值(conditional value)也是一個鍵值對, 其中condition指定了須要知足的條件, value指定了知足條件時須要返回的值.redux

優先級
若是單個條件被知足, 那麼返回對應的值; 若是多個條件都被知足, 那麼返回主導條件(list上方的條件)對應的值; 若是沒有條件知足, 則返回默認值; 若是沒有定義默認值, 則什麼也不返回.c#

文中還詳細介紹了Android和iOS端的實現, 以及console的配置.設計模式

Android App Optimization Using ArrayMap and SparseArray

當咱們須要存儲鍵值對的時候, 咱們老是首先想到用HashMap, 然而IDE(Android Studio)有時候會警告提醒你, 應該用ArrayMapSparseArray.

HashMap vs ArrayMap

ArrayMap比傳統的HashMap更節省內存, 由於它把本身的映射放在數組結構中: 一個整型數組放每個item的hash code, 一個Object數組放key/value對. 這樣避免了爲每個entry建立額外的對象, 並且數組增加也好控制.

注意ArrayMap並非爲很大的數據集設計的, 而且它會比HashMap慢一些, 覺得查找須要二分查找, 增刪須要在數組中操做.

HashMap

HashMap是一個HashMap.Entry的數組, 其組成是key, value, HashCode, 還有一個指針.

當進行插入時: 首先計算出key的HashCode, 而後用這個hashCode找到對應的bucket, 若是已經存了元素, 則把舊元素的指針指向新元素, 即把bucket變爲一個LinkedList.

當進行查詢時: 複雜度爲O(1), 可是這樣是犧牲了更多的空間複雜度獲得的.

HashMap的缺點:

  • 由於key和value都不能是原生類型, 因此插入時可能會有自動裝箱, 致使建立額外的對象.
  • HashMap.Entry自己就是一層額外的對象.
  • 每次HashMap的收縮或者擴張, Buckets都要從新排列, 隨着對象變多, 這個操做變得愈加昂貴.

ArrayMap

ArrayMap使用兩個數組:
int[] mHashes用來存哈希值; Object[] mArray來存對象.

當插入鍵值對時: Key/Value被自動裝箱, Key被插入到mArray[]數組的下一個位置, Value也被插入到mArray[], 在Key的下一個位置.
計算出的哈希值被放在mHashes[]的下一個位置.

當查詢一個Key時: 首先計算出Key的哈希值, 在mHashes中二分查找這個hashCode(時間複雜度(logN)), 當獲得hash的index以後, 咱們就知道mArray2*index2*index+1的位置對應的是查找的key和value.

雖然時間複雜度提高了, 可是這樣卻更省空間.

推薦的數據結構:

  • ArrayMap<K,V> in place of HashMap<K,V>
  • ArraySet<K,V> in place of HashSet<K,V>
  • SparseArray<V> in place of HashMap<Integer,V>
  • SparseBooleanArray in place of HashMap<Integer,Boolean>
  • SparseIntArray in place of HashMap<Integer,Integer>
  • SparseLongArray in place of HashMap<Integer,Long>
  • LongSparseArray<V> in place of HashMap<Long,V>

RecyclerView: How we achieved 60 FPS in Workable’s Android App

咱們常常會用RecyclerView來顯示一個list的數據.
做者他們作的是一個招聘應用: Workable, 其中會用list來顯示candidates.
他們還使用了DataBinding.
本文是做者他們關於RecyclerView的幀率所作的一些優化.

首先他們使用了Android Studio的Allocation Tracking, 而後上下滾動, 從報告發現, 他們佈局中使用的TableLayout花費了不少資源, 因而後來他們改成LinearLayout加權重的方式來解決, 擺脫了耗費資源的TableLayout.

另外一個引發不少資源分配的問題是, 對於須要大寫的文字, xml中的:

<TextView
          ...
  android:textAllCaps="true"
          ...
/>

TextView的代碼中會爲今生成一個對象:

if (allCaps) {
      setTransformationMethod(new AllCapsTransformationMethod(getContext()));
  }

這個在靜態的佈局中可能沒有問題, 可是在一個滾動的list中可能會有些影響.

改進方法是改成用java String的.toUpperCase().

而後他們使用了RecyclerView的.onViewRecycled()方法. 這個方法讓咱們知道了RecyclerView中的一行什麼時候被回收, 這樣咱們就能夠釋放一些不須要的資源.
他們使用了DataBinding, 因此這是一個合適的時機來刪除ViewModel中的OnPropertyChangedCallbacks, 而後清理ViewModel自身, 咱們還能夠清理以前用Glide load到ImageView中的圖片.

@Override
public void onViewRecycled(Candidates holder) {
    if(holder != null) {
        holder.binding.getCandidateVM().removePropertyChangedCallback();
        holder.binding.setCandidateVM(null);
        holder.binding.setHighlightTerm(null);
        holder.binding.setShowJobTitle(false);
        holder.binding.setShowStage(false);
        holder.binding.executePendingBindings();
        Glide.clear(holder.binding.candidateBrowserAvatar);
        holder.binding.candidateBrowserAvatar.setImageDrawable(null);
    }

    super.onViewRecycled(holder);
}

做者他們的應用還有一些cache設置:

binding.fragmentCandidateBrowseList.setItemViewCacheSize(30);
binding.fragmentCandidateBrowseList.setDrawingCacheEnabled(true);
binding.fragmentCandidateBrowseList.setDrawingCacheQuality(View.DRAWING_CACHE_QUALITY_HIGH);

以後做者測量了他們的FPS, 顯示是60 FPS, 而且發現去掉這些cache設置仍然是60.

測量幀率FPS的工具: TinyDancer.

No more value classes boilerplate — The power of AutoValue

在Java/Android編程中常常須要寫model對象來存放一些數據, 使用Google的庫AutoValue能夠幫你自動生成這些類, 你須要作的就是定義你的字段, 而後給類加上註解.

Setup

在project的build.gradle中:

buildscript {
    [...]
    dependencies {
        [...]
        classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8'
    }
}

在app的build.gradle中:

apply plugin: 'com.neenbedankt.android-apt' // At the beginning
[...]
dependencies {
    [...]
    provided "com.google.auto.value:auto-value:1.2"
    apt "com.google.auto.value:auto-value:1.2"
}

基本使用

好比要建立Film類, 你能夠寫一個這樣的抽象類:

@AutoValue
abstract class Film {
    static Film create(String name, int year) {
        return new AutoValue_Film(name, year);
    }

    abstract String name();
    abstract int year();
}

每個字段都對應一個抽象方法.
build一下, AutoValue_Film類就會自動生成, 加上靜態工廠方法(上面的create()方法) 而後就可使用工廠方法來獲得model:

Film matrix = Film.create("The Matrix", 1999);

點進自動生成的類AutoValue_Film裏能夠看到, 連hashCode()equals()方法都生成了.

用builder模式

上面的例子隨着字段的增多, create()方法的參數會變得不少, 用起來不方便, 那麼此時就須要用Builder模式:

@AutoValue
abstract class Film {

    static Builder builder() {
        return new AutoValue_Film.Builder();
    }

    @AutoValue.Builder
    abstract static class Builder {
        abstract Builder setName(String value);
        abstract Builder setYear(int value);
        abstract Film build();
    }

    abstract String name();
    abstract int year();
}

這樣就能夠很方便地加參數了:

Film matrix = Film.builder()
  .setName("The Matrix")
  .setYear(1999)
  .setCategory(Category.FANTASY)
  .setRating(8.7f)
  .setDuration(136)
  .setReleaseDate(releasedDate)
  .setDirectors(directorsList)
  .setCast(castList)
  .build();

AutoValue擴展 Parcelable

有時候你須要在Activity之間傳數據, 須要你的model是Parcelable的, 此時你就能夠用這個auto-value-parcel, 在代碼裏也只須要實現這個接口:

@AutoValue
abstract class Film implements Parcelable {
   [...]
}

還有不少的擴展庫: extensions for AutoValue, 好比AutoValue-Gson, AutoValue-Cursor, AutoValue-With, AutoValue-Redacted等.

Consider abstract class instead of interface

這篇文章的做者說, 在library開發中, 應該考慮用抽象類而不是接口. 他的庫是AdapterDelegates.

做者先介紹了通用的概念比較:

  • class vs. interface
    接口更解耦, 更靈活, 只是定義了一個協議, 不限制實現.
  • interface vs. abstract class
    抽象類會有繼承的問題, 基類和子類會共享一些實現, 因此子類的編寫者最好能清楚基類的實現, 這樣纔不會在寫子類實現抽象方法的時候打破了基類做者的意圖. 另外就是基類做者仍然可能會更新基類, 因此得時刻檢查子類是否仍是符合基類的設計意圖.

可是爲何做者仍是要把本身庫中的接口改成抽象類呢? 這是由於做者的庫依賴於Android的庫, Android的庫中相關代碼改了, 做者就得改本身的public接口, 加一個方法, 致使全部新版的使用者也都必須實現這個方法.

還有一個狀況就是好比一個開發者使用了2.1版本, 可是他項目裏依賴的另外一個第三方庫使用了2.0版本. 編譯不會出錯, 最終的apk中打包的是2.1版本. 而後在這個第三方庫的組件裏調用2.1纔有的新方法時就會拋出錯誤.

爲了解決這個問題, Jake Wharton建議在庫的主要更新(major update)中更改發佈的package name和group id: http://jakewharton.com/java-interoperability-policy-for-major-version-updates/

做者以爲那每次Android RecyclerView的Adapter更新都會致使本身的庫major update, 因此他決定把本身的AdapterDelegate接口改成抽象類. 這樣他就能夠對新增的方法提供默認空實現.

這樣定義的抽象類只有抽象方法和一些空實現的方法, 並無狀態和行爲的共享可能會傳播給子類, 其實和接口是同樣的.

Android BottomSheetDialog

實現bottom sheet的時候, 有三種選擇: container view + BottomSheetBehavior, BottomSheetDialogFragment, BottomSheetDialog. 前兩種的例子比較多, 做者要介紹的是第三種.

如何選擇取決你的用途, container view + BottomSheetBehavior 適用於persistent bottom sheet, 而BottomSheetDialogFragmentBottomSheetDialog適用於Modal bottom sheets.

以後做者提供了實現代碼, 附有theme定製和狀態callback的設置.

The VCS client of Android Studio

這篇文章介紹Android Studio的版本控制系統.

在Android Studio 2.2開始, 加入了一個Create command line launcher, 這樣你就能夠在命令行或者第三方的版本控制客戶端使用Android Studio的diff/merge tool了.
做者使用的客戶端是SourceTree.

cmd + shift + A能夠用來find action, 而後就能夠找到Compare with branch: 能夠比較當前文件和某個分支上的文件的diff;
另外還能夠Compare with..., 來比較和以前某一個特定提交的diff; 以及Compare with Clipboard來和剪貼板作比較.

還有一些其餘有用的快捷鍵, 請看原文吧.

Constraint Layout: Icon Label Text

做者想作這樣一個UI, 左邊是一個icon, 右邊是兩行字, icon的top和bottom分別和第一行字的top和bottom對齊.
ConstraintLayout: Icon Label Text

怎麼作呢? 她想到了用ConstraintLayout.
代碼在這裏: iconlabeltext

Bye, Bye Burger

做者他們的應用從burger menu改成bottom navigation, 此篇爲心得分享和他們改版時設計中的一些細節討論.

其中狀態保存是一個最主要的技術問題.

改版以後, 做者他們的應用數據代表有如下幾個好處:

  • 用戶參與度提高了;
  • 在底部導航有入口的功能使用率提升了;
  • 並無用戶反饋說新的導航很差.

Nougat – Messaging Style Notifications

Messaging Style Notifications是爲信息應用特殊設計的, 提供了一個像對話同樣的view.

Messaging-style notifications和Bundled notifications的主要區別是, Bundled notifications中咱們持續建立新的notification, 而後它們被grouped together. 可是用Messaging-style notifications的時候, 咱們只有一個notification, 而後咱們把全部的信息添加進去.

做者展現了實現代碼和效果, 注意這個Messaging style並非後項兼容的, 只在Nougat及之後的版本才支持.

Bottom sheet everything

做者介紹了他的應用中對於Bottom sheet的使用.

Deep linking with bottom sheet Activity
做者用它處理Deep linking, 這樣用戶就不用每次都全屏打開, 只先提供一個peek, 若是真的感興趣再打開.

實現是用一個透明的Activity, 還有狀態欄處理的細節.

Bottom sheet settings menu
關於Settings, 爲了節省用戶的trip, 做者它們的應用用了options menu的彈出菜單. 後來他們改用bottom sheet來實現, 而且結合了PreferenceFragmentCompat, 省去了一些SharedPreferences的讀寫操做.

Supporting tablet users
bottom sheet在平板上使用, 尤爲是橫屏的時候, 看起來不太好.
因此做者定製了Bottom sheet的寬度, 在平板上時是一個指定寬度, 在手機上維持原狀.

Machine Learning for with the Mobile Vision API— Part 1

基於Google的Mobile Vision APIs如今Android開發者能夠在應用裏用上機器學習了. 如今這個Mobile Vision API包括三種類型Face Detection API, Barcode Detection API和Text API.

本文主要講人臉檢測部分, 後面會講二維碼檢測和文字的API.

做者的demo展現瞭如何從一個靜態照片中檢測出人臉區域, 而且標記出landmark(眼睛, 鼻子, 嘴巴等), 以後能夠根據這些特徵位置加上一些覆蓋標記.

sample code.

Custom fonts formatting, the simple way

在Android中自定義字體的一個庫: Calligraphy.

若是你的輸入是html文字, 你想自動處理裏面的tag(好比), 用另外一種字體, 怎麼處理呢, 做者給出了代碼.
custom font in one textview

完整的例子代碼見: sample code.

Reductor - Redux for Android. Part 1: Introduction

以前這個文章介紹過Reductor, 在Android Weekly以前也出現過, 個人筆記: Android Weekly Notes Issue 224.

Reductor是一個狀態管理的庫, 用Java從新實現了JavaScript的庫Redux.
它的中心思想:
redux idea

以前的一篇文章作了一個TODO app, 而後做者發現這種mutable的數據會致使失控的數據改變, 而後可能會出現沒法預測的行爲. 作了一些改動以後, 咱們發現能夠經過只保存一個immutable的對象和mutable的reference來避免這個問題.

這篇文章用Reductor來從新實現應用, 文中詳細說明了代碼實現.

LIBRARIES & CODE

ImageTransition

一個很小的庫, Activity直接的shared element transition動畫, 把一個圓形的ImageView變換到下一個Activity的方形ImageView.

Design-Patterns-In-Kotlin

用Kotlin實現的設計模式.

相關文章
相關標籤/搜索