天天看點

Android Weekly Notes Issue #221

Android Weekly Issue #221, 中文摘要筆記.

Android Weekly Issue #221

September 4th, 2016

ARTICLES & TUTORIALS

Android ImageView ScaleType: A Visual Guide

回想一下, 你是不是總是記不住ImageView的不同ScaleType的差別, 每次都要各種嘗試來找到自己适合的.

這篇文章的作者也有這樣的煩惱, 于是他把各種ScaleType都截了圖:

Android Weekly Notes Issue #221

如果用了CENTER_INSIDE, FIT_CENTER, FIT_START,或者FIT_END, 而實際View的大小比圖像的大, 可以使用

android:adjustViewBounds

屬性為true, 就會調整View的大小.

官方文檔: ImageView.ScaleType

5 steps to creating frustration-free Android test devices

作者講了如何統一管理測試機.

1.根據你要支援的API level來安裝系統.

理想情況下你應該每一個API都有一個對應的機器, 更進一步可以統計一下你的使用者用什麼的最多來進行調整.

作者列舉了他目前的五個機器, 一般來講, 你至少需要高中低API版本的, 也需要Samsung的機器來測試一些可能會被定制的地方, (當然作者是在國外了, 國内估計需要測試的定制機型就更多了), 另外還需要一個大螢幕的, 來檢視UI的适配情況.

幸運的是除了品牌定制機, 其他都可以用模拟器來補救, 在此推薦一下genymotion, 傳說中最快的模拟器.

2. 安裝并配置測試所需的應用

為了測試你的app, 你可能需要一系列的工具app, 是以第二步你需要安裝它們, 登入及設定等等.

原作者常用的工具app有:

  • 1Password: 管理密碼.
  • AZ Screen Recorder: 截屏, 制作gif.
  • Chrome Beta: 因為原作者做WebView相關的工作, 是以需要看這個.
  • Dropbox: 自動上傳截圖, 從電腦可以友善拿, 也可以用來做一些檔案相關的測試.
  • Flesky / Swiftkey / Google Keyboard: 也是作者應用相關, 需要測試各種鍵盤.
  • Keep: 很好用的筆記應用, 可以存一些小notes, url等, 跨裝置同步.
  • Solid Explorer: 檔案管理器, 可以在系統中友善地移動檔案.

3. 在各處都登入

需要登入的賬号都登入.

4. 為了統一體驗裝個Nova Launcher

為了讓每個機器都看起來一樣, 原作者裝了個launcher應用: Nova Launcher.

确實, 因為每個機器的launcher群組織方式不一樣, 是以有時候換個機器就會很難找到你想要的東西.

用了這個Nova Launcher之後, 你可以設定好你的home, dock, drawer, 然後多個機器分享設定, 這樣當你拿起另一個機器的時候, 所有的應用都在同樣的位置.

5. 設定每個機器的系統設定

最後一件事就是一些系統上的設定, 包括:

  • 所有地點的Wi-Fi;
  • DND/total silence: 關聲音;
  • Developer options和USB debugging開關打開;
  • 當插線時仍然保持螢幕喚醒;
  • 亮度設定.

最後作者建議開發者平時生活中可以多玩玩各種Android應用.

Security issues with Android Accessibility

看這篇文章之前, 讓我們了解一下Accessibility是什麼, 搜了一下Android相關文檔:

  • Guides of Accessibility
  • Training for Implementing Accessibility
  • Design Guidelines for Accessibility
  • Material Design Accessibility

Accessibility是為了擴充通路和利用應用的形式, 基本出發點是為了輔助老年人或者是有障礙的人, 增加一些聽覺或觸覺回報, 也可以用來輔助一些特殊場合下的使用者, 比如正在開車或照顧孩子, 或者處于非常嘈雜的環境下的情形.

可以結合Google的TalkBack, 也可以自己開發相關的服務.

好了, 話題收斂回來, 看看作者說的安全問題指的是什麼.

作者一開篇以一個印度很流行的應用Voodoo為例指出, 把螢幕上的文字讀出來這個功能是有安全漏洞的.

首先Voodoo向使用者請求accessibility的權限, 這個權限使得應用可以從螢幕上讀取文字, 但是使用者會認為所有的敏感字段應該不在這個範圍之内, 這就是開發者需要認真對待的了.

最近有一個新的登入設計, 已經被應用開來, 就是使用者可以選擇顯示或者隐藏密碼字段.

當我們把輸入框的input type設定為密碼, 那麼它是不會被讀取到的, 但是有一些應用為了支援顯示密碼的功能, 可能會把input type設定為其他類型, 這樣就會導緻密碼暴露, 有accessibility權限的惡意應用就會借此盜用使用者的敏感資訊.

這樣當然是不好的啦, 使用者開啟權限的時候還認為敏感字段總會受到保護呢, 是以我們開發者應該小心地對待使用者的敏感資訊, 很簡單:

ViewCompat.setImportantForAccessibility(your_view, ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_NO);

新的

TextInputLayout

的API可以實作密碼顯示的toggle功能, 希望這個問題在TextInputLayout中已經解決了, 但是在用這個View之前, 上面的hotfix也算一種解決辦法.

How to fix horizontal scrolling in your Android app

在Android中垂直的滾動很常見, 但是如果在垂直滾動的View裡嵌套一個水準滾動的View, 那滑動的體驗将會非常不好.

Problem: 垂直滾動和内嵌的水準滾動打架了, 滾動體驗不佳.

What's happening inside:

例子裡根view是一個

RecyclerView

加垂直

LinearLayoutManager

, 裡面的child是一個

RecyclerView

加水準

LinearLayoutManager

.

但是當使用者做水準滾動的時候, touch事件首先被外面的父View給攔截了.

RecyclerView

的代碼可知, 在

onInterceptTouchEvent()

方法裡, 在垂直滾動使能的情況下, 隻要垂直移動的距離(dy)大于一定程度(

Math.abs(dy) > mTouchSlop

), 就會被認為是垂直滾動.

是以作者他們的解決方案是繼承了RecyclerView, 覆寫了這個方法, 把條件改成:

if(canScrollVertically && Math.abs(dy) > mTouchSlop && (canScrollHorizontally || Math.abs(dy) > Math.abs(dx))) {...}  
           

這是他們的完整代碼: BetterRecyclerView

Bonus

還有一個跟fling相關的問題:

RecyclerView

在fling之後需要挺長的一段時間來穩定(settle)下來, 當child還在這個穩定過程中時, 如果使用者嘗試豎直滾動, touch事件實際上是被child吃掉的.

還是從

onInterceptTouchEvent()

的代碼可以看出:

if (mScrollState == SCROLL_STATE_SETTLING) {
    getParent().requestDisallowInterceptTouchEvent(true);
    setScrollState(SCROLL_STATE_DRAGGING);
}
           

當child處于SETTLING狀态時, child會要求它的parent不要攔截touch事件.

這在通常情況下是好的.

但是在作者的使用場景裡, 他們的root中沒有其他豎直滾動和拖拽的child, 是以他們又繼承了剛才那個BetterRecyclerView, 寫了一個

requestDisallowInterceptTouchEvent()

為空實作的View作為root.

他們的sample demo在這裡: manidesto/scrolling-demo.

Update Dependencies. Code. Repeat.

每一個Android開發可能都需要對他們的項目進行(更新依賴, 編碼, 重複)這樣的循環工作, 如果你想要你的所有項目都有同樣的版本号, 這樣是很浪費時間的.

原文作者就經曆了這樣的情景, 他想要把他這個目錄Android-Examples下的所有項目都更新一下. 這個目錄裡全是那種很小的簡單例子, 但都是可獨立運作的工程.

每當gradle-plugin, support library或者google play services要更新版本号, 保持這些工程全部都updated是一項很難的工作.

是以原作者想要放棄原先逐個更新的土辦法, 更有效率地來更新依賴版本号.

首先想到的就是在gradle裡定義一個變量, 然後雙引号加$引用這個變量.

為了讓所有的module都采用同一變量, 可以在根項目的build.gradle檔案裡定義變量, 即使用ext塊.

但是到此, 隻能統一管理在同一個project下的各個modules的依賴版本.

如果跨projects呢?

首先, 作者在存放這些projects的根目錄下建了一個gradle檔案, 然後把變量都定義在那裡.

然後如何應用到各個project呢?于是原作者找啊找, 找到了這塊: gradle Subproject configuration

他給每個工程的根build檔案加了個這個:

// This is added to apply the gradle file to each module under the project
subprojects {
    apply from: '../../dependencies.gradle'
}
           

這樣以後在每個工程的module裡面都可以直接引用變量了.

但是, 這對于android-gradle-plugin的版本是不管用的.

這是因為上面應用的配置隻對subproject起作用, 對每一個root project是沒有應用到的.

是以作者在每一個項目的root build.gradle中, 在buildscript塊又加了它的依賴配置檔案:

buildscript {
    // This is added to apply the gradle file to facilitate providing variable values to root build.gradle of the project
    apply from: '../dependencies.gradle'
    ..
    dependencies {
        classpath "com.android.tools.build:gradle:$androidPluginVer"
        ..
    }
}
           

好啦, 至此, 所有的依賴配置問題就解決了, 以後改版本号隻需要改一個地方就可以應用到所有項目.

DI 101 - Part 2

作者上一篇的文章裡介紹了Dagger的基本使用.

這篇還是教程類文章, 講:

多個Modules:

寫了一個Retrofit 2的ApiModule, 和一個Realm的DatabaseModule.

多個對象:

有時候我們需要提供一個類的不同對象, 我們可以用

@Named

注解, 然後用不同的字元串來區分它們.

文中的例子是這樣:

@Provides
@Named(IMAGE_URL)
String provideImageUrl() {
  return ImageApiService.ENDPOINT;
}

@Provides
@Named(URL)
String provideBaseUrl() {
  return RestApiService.ENDPOINT;
}

@Provides @Singleton @Named(REST_API_RETROFIT)
Retrofit provideRetrofit(@Named(URL) String baseUrl, OkHttpClient client) { ... }

@Provides @Singleton @Named(IMAGE_API_RETROFIT)
Retrofit provideImageRetrofit(@Named(IMAGE_URL) String baseUrl, OkHttpClient client) { ... }
           

Inject interfaces without provide methods on Dagger 2

用dagger2注入接口, 傳回實作類的對象, 比較正常的方法是在Module裡面寫一個

@Provides

标注的providesXXX()方法, 傳回值類型是接口, 實際傳回的是實作類的對象, 比如:

@Module
public class HomeModule {

  @Provides
  public HomePresenter providesHomePresenter(){
    return new HomePresenterImp();
  }
}
           

但是如果我們想給實作類加一個依賴UserService呢?

我們當然可以把UserService作為參數傳給這個provide方法, 然後傳到實作類的構造函數中, 在裡面存一個字段.

又或者, 我們可以使用

@Binds

注解, 像這樣:

@Module
public abstract class HomeModule {

  @Binds
  public abstract HomePresenter bindHomePresenter(HomePresenterImp   
    homePresenterImp);
}
           

這是一個抽象類中的抽象方法, 這個方法的簽名意思是告訴dagger, 注入

HomePresenter

接口(傳回值)時, 使用

HomePresenterImpl

(方法參數)實作.

然後, 在

HomePresenterImpl

類的構造函數上加一個

@Inject

就可以了.

這樣我們就不需要在provide方法上加依賴參數了.

Introducing ExpandableRecyclerView

本文介紹expandable-recycler-view, 一個開源的庫, 可以展開和折疊RecyclerView中的組.

RecyclerView

作為

ListView

的更新版, 卻也減少了一些功能比如

OnItemClickListener

,

ChoiseModes

, 還有擴充版的

ExpandableListView

本文作者就介紹這個開源庫,

ExpandableRecyclerView

, 用自定義的

RecyclerView.Adapter

來實作展開關閉分組的功能.

首先明白一下Adapter的功能, 其實adapter就是一個中間人, 将一些資料按照index翻譯給View, 然後顯示.

當顯示的list是單次元的時候, 這樣的翻譯很簡單, 資料的index就直接對應了螢幕上view的index.

當時當你顯示二維資料時, 翻譯就變得有點複雜,資料和view的index可能對應, 也可能不對應.

RecyclerView.Adapter

就隻能處理一維資料的情況, 這就是為什麼要對其進行一些擴充, 才能實作ExpandableRecyclerView.

後來作者簡單講了實作的原理, 用到了

ExpandableListPosition

, 是Android SDK中就有的類, 隻不過有包限制, 是以拷貝到了這個庫裡.

最後附上repo位址: expandable-recycler-view

Creating Custom Annotations in Android

注解是什麼

Annotations are Metadata.

注解是中繼資料, 而中繼資料是一些關于其他資料的資訊.

是以說, 注解是關于代碼的資訊.

比如

@Override

注解, 即便你不在方法上标注它, 程式依然能夠正常工作. 那麼它是用來幹什麼的呢?

@Override

是用來告訴編譯器, 這個方法覆寫了一個方法, 如果父類沒有這個方法, 則會報一個編譯錯誤.

如果你不加這個注解, 有可能你方法名不小心拼錯了卻仍然編譯通過了.

建立自定義注解:

比如, 建立一個:

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@interface Status {
  public enum Priority {LOW, MEDIUM, HIGH}
  Priority priority() default Priority.LOW;
  String author() default “Amit”;
  int completion() default 0;
}
           

其中

@Target

指定了這個注解可以放在哪裡. 如果你不設定, 這個注解可以放在任何地方.

可能的值有:

  • ElementType.TYPE

    (class, interface, enum)
  • ElementType.FIELD

    (instance variable)
  • ElementType.METHOD

  • ElementType.PARAMETER

  • ElementType.CONSTRUCTOR

  • ElementType.LOCAL_VARIABLE

@Retention

定義這個注解可以被儲存多久.

  • RetentionPolicy.SOURCE

    - 到編譯結束, 不會被編進.class, 隻會留在源檔案中.

    @Override

    @SuppressWarnings

    都是這種.
  • RetentionPolicy.CLASS

    - 到類加載丢棄, 注解将存儲在.class檔案中, 這是預設值.
  • RetentionPolicy.RUNTIME

    - 不被丢棄. .class檔案中有, 并且可由VM讀入, 在運作時可以通過反射的方式讀取到.

上面的注解使用時:

// in Foo.java
@Status(priority = STATUS.Priority.MEDIUM, author = “Amit Shekhar”, completion = 0)
public void methodOne() {
 //no code
}

// get the annotation information
 Class foo = Foo.class;
 for(Method method : foo.getMethods()) {
    Status statusAnnotation = (Status)method.getAnnotation(Status.class);
    if(statusAnnotation != null) {
     System.out.println(" Method Name : " + method.getName());
     System.out.println(" Author : " + statusAnnotation.author());
     System.out.println(" Priority : " + statusAnnotation.priority());
     System.out.println(" Completion Status : " + statusAnnotation.completion());
    }
 }
           

如果你的注解中僅有一個屬性, 它應該叫value, 并且使用的時候不用指定屬性名.

@interface Status{
 int value();
}
 @Status(50)
 public void someMethod() {
  //few codes
 }
           

最後作者還附上了另一個他的文章, 推薦他的網絡請求庫: Fast Android Networking

It's parfetti time!

作者介紹他的庫: confetti.

這個庫實作了一個粒子系統, 來發射出随機的紙屑, 并且可以被定制化, 比如發射源的形狀(點或者線), 初始的實體限制(速度, 加速度, 旋轉等), 還可以定義消失或者拖拽行為, 感覺效果還挺好的.

關于性能, 紙屑對象是循環利用的, 每一個bitmap也隻被配置設定一次位址, 動畫參數也做了一些預計算, 是以作者說不用擔心丢幀, 除非你一次性出現的片兒實在是太多了.

Converting callback async calls to RxJava

作者他們在自己Android應用裡開始使用RxJava以後, 經常會遇到由于API沒有follow reactive model, 導緻他們必須做一些轉換工作, 将它們和其他的RxJava Observable鍊連接配接起來.

API對于很重的操作通常提供這兩種方式之一

  • 1.同步阻塞方法調用, 通常需要背景線程調用.
  • 2.異步非阻塞方法調用, 結合callback, listener, 或者broadcast receiver等.

把同步方法變為Observable:

用這個Observable.fromCallable()

比如:

// wrapping synchronous operation in an RxJava Observable
Observable<Boolean> wipeContents(final SharedPreferences sharedPreferences) { 
    return Observable.fromCallable(new Callable<Boolean>() { 
        @Override 
        public Boolean call() throws Exception { 
            return sharedPreferences.edit().clear().commit(); 
        } 
    }); 
}
           

把異步方法變為Observable:

變異步沒那麼簡單了, 之前有一些模式是用工廠方法

Observable.create()

把它們包起來, 比如here, here, here, 但是這種方法存在一些缺點.

作者舉了一個傳感器監聽的例子.

用create()轉換之後, 需要處理一些問題, 比如登出listener, 錯誤處理, 檢查subscriber等, 這幾個都可能辦到, 但是還有一個backpressure的問題, 不好辦.

這個backpressure是什麼捏: backpressure

當生産者發射值的速率比消費者可以處理的速率快的時候, 有一個内置的buffer size, 當超出的時候就會抛出

MissingBackpressureException

幸運的是, RxJava v1.1.7推出了

Observable.fromAsync()

, 在v1.2.0改名為

Observable.fromEmitter()

這個裡面對于backpressure的處理定義了好幾種政策, 你隻需要選一種模式就行.

然後作者給出了采用這個新方法的例子, 這裡不再贅述, 可以看原文.

Sample代碼在: AndroidRxFromAsyncSample

LIBRARIES & CODE

ItemTouchHelper Extension

ItemTouchHelper

的擴充, 加滑動settling和恢複. Sample的效果是給單個item加了滑動後出現删除和refresh兩個按鈕.

Fresco Image Viewer

為Fresco庫加的全屏檢視圖像的工具, 支援雙手指的zoom和滑動關閉手勢.

ABTestGen

一個生成簡單A/B test的庫, 使用注解.

RecyclerViewHelper v24.2.0

一個RecyclerView的輔助類, 提供滑動删除, 拖動, divider, 選中和非選中事件等的支援.

Paginize

一個輕量級的Android應用framework.

SPECIALS

Tips and tricks for Android Development

一個很長的README, 包含了各種快捷鍵, 編碼建議, 工具, 插件, 還有有一些推薦的網站等, 其中有mock api和新聞網站及其他有用的工具等.

作者: 聖騎士Wind

出處: 部落格園: 聖騎士Wind

Github: https://github.com/mengdd

微信公衆号: 聖騎士Wind

Android Weekly Notes Issue #221