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实现的设计模式.

相关文章
相关标签/搜索