October 2nd, 2016
Android Weekly Issue #225
本期内容包括: Android 7.0的Quick Settings; Firebase; 兼容旧版本的shared element transition; Wear; ORM: 用ActiveAndroid做数据库存储; 崩溃报告工具对比; Google Cast API介绍; Google的播放器库ExoPlayer 2.x发布; 项目的包结构整理; Task API的使用等等.
从Android 7.0 (API 24)开始, 任何app都可以创建一个quick settings tile, 快速访问关键功能.
它除了是一个展示最新信息的UI, 点击一个片还可以trigger后台任务, 打开dialog或activity.
一个好的quick settings tile:
决定是否要建立这样一个tile时, 主要考虑紧急性和频繁性两个方面.
每一个tile和一个TileService关联. 和其他service一样, 它需要在manifest中注册, 它的label和icon就是显示在quick settings上的文字和图片.
TileService的生命周期:
TileService是一个bound service, 它的生命周期主要由系统控制. 主要有三个阶段: being added, listening, being removed.
onTileAdded()
: 当用户添加这个tile到quick settings.onStartListening()
: tile变为可见.onStopListening()
: tile变为不可见.onTileRemoved()
: 用户移除这个tile.以上这是默认模式, 如果你准确地知道何时更新, 你可以使用active mode.
此时更新的回调onStartListening()
是通过静态方法主动触发的.
更新UI:
UI是Tile, 主要包含icon, label, description和state. 最后必须调用updateTile()
方法.
处理点击:
在onClick()
回调触发的时候, 我们可以启动一些后台工作, 或者showDialog()
, 或者startActivityAndCollapse()
.
对于锁屏的机器有一些限制, 不能打开dialog, 并且activity需要有一个特定的flag, 有一个unlockAndRun()
方法可以让用户先解锁后做一些工作.
长按tile默认会打开app的app info屏, 当然这个行为也可以override. 只要给你想打开的activity加上ACTION_QS_TILE_PREFERENCES
.
关于性能:
Best Practices for Performance;
Performance and Optimization
关于架构:
android-architecture
写单元测试和UI测试.
使用Proguard, Stetho.
复用布局, 使用 标签.
reusing-layouts.
把launcher icons放在mipmap文件夹下.
多用shape和selector而不是图片.
避免深层次的布局.
向Intent或Bundler传数据时, 使用Parcelable
而不是Serializable
. 因为后者使用反射而比较慢.
不要在UI线程进行文件操作.
明白Bitmaps. 因为它们占用很多memory. Displaying Bitmaps
使用style来避免重复的属性设置.
需要时使用Fragment.
明白Activity的生命周期.
使用得到公认的libraries而不是自己的实现.
在各种机器上测试.
作者参加了一个叫Google Launchpad Build的会议, 这篇文章是总结, 全部是关于Firebase的.
在Lollipop+的设备上, shared element的transition动画很好实现, 但是在旧的版本上该怎么办呢? 作者展示了他的方法:
几个实现细节:
需要知道View在B中的位置, 时机是layout之后, 但是draw之前, 即onPreDraw()
.
返回时只需要把这个动画反向播放即可.
(这个上一期刚讲过, 不知道为什么重复了. )
就是关于RecyclerView的Adapter, 作者认为多种View类型时, Adapter中太多的instance of和强制类型转换不是一种好做法, 于是提出了他的做法.
Data Layer API是Google Play services的一部分, 用于不同设备(手机和手表)间的数据交换.
作者先提供了代码, 发送和存储数据, 监听数据变化.
问题是, 如果Wear第二次向mobile请求数据, mobile发送了和上一次一样的数据, Wear并不会进入onDataChanged()
, 因为数据并没有变化.
所以作者想知道如何从Data Layer API来获取数据, 并展示了他的方法在不同情形下的应用.
作者想给TextSwitcher写Espresso测试.
从Android Studio 2.2开始, 你可以录制你的操作, IDE将会自动为你生成Espresso测试代码. 但是作者录了一个有关TextSwitcher的测试之后, 跑失败了.
这是因为TextSwitcher
继承了ViewSwitcher
, 其实现其实是把两个TextView加到了布局里.
所以Espresso抛出了AmbiguousViewMatcherException
.
所以作者根据可见性区分了它俩, 修复了测试.
还可以根据child view的index来区分.
作者展示了如何给Activity和View加上左右滑动的动画.
这是一个系列教程, 相关的代码在: ActiveAndroid-Tutorial
什么是ORM(Object-Relational Mapping)呢?
a technique to convert between incompatible type-systems in an object-oriented programming language.
在面向对象的语言中, 转换不兼容的类型的技术.
ActiveAndroid是一个ORM(object relational mapper), 让你不用写SQL语句, 就可以读写数据库.
其他类似的工具还有Realm和OrmLite.
作者对比了几种崩溃报告工具, 并介绍了如何使用.
包括: Firebase, Crashlytics, Apteligent, Bugsnag.
Google Cast是一个让用户把网上的内容发送到设备上的技术. 通常用来和TV交换内容.
作者详细地介绍了如何使用Google Cast SDK来创建应用.
注: 要建造客户端程序, 首先需要注册: https://cast.google.com/publish/.
这是收费的.
Google的库google/ExoPlayer升级到v2.x了.
(它是一个Media Player, YouTube用的就是它.)
这次是个重大更新, 添加了很多新功能, 推荐大家以后用新版.
作者他们重新整理了项目的包结构, 总结了整个过程还有从中学到的东东.
作者他们之前的包结构是按类型的, 有activities, fragments, adapters等包. 因为类名以类型终结, 所以索性就按整个分组.
当app变得越来越大, 这种组织方式发现就不太好, 感觉很难找东西, 并且感觉没什么结构.
经过改变之后, 作者他们采用了一种更加整洁并且易于导航的结构.
新结构中, 当添加一个新的feature, 就保持在同一个目录中, 这样就不用来回切换目录.
作者他们的新结构有四个总目录:
data中包含网络请求及相关的models, preferences, database, data models, 还有其他和数据直接关联的东西.
其中和不同API关联的models又分别组织在子目录下.
ui目录中包含所有和UI相关的组件, 在这个包中按照功能又拆分了子目录. 其中有base包, 用来盛放Fragment, Activity和MVP的基类, 接口等; 还有common包, 用来盛放公共控件.
injection中包含所有依赖注入的类, 分component, module和scope的子目录.
util中含有Helper和Utility类.
这是系列文章的第三篇, 这个系列是关于Play services的Task API.
如果项目里已经依赖了Firebase, 变自动包含了Task API, 如果不想用Firebase, 可以单独添加依赖:
compile 'com.google.android.gms:play-services-tasks:9.6.1'
创建新的Task可以用下面这两个方法:
Task<TResult> call(Callable<TResult> callable)
Task<TResult> call(Executor executor, Callable<TResult> callable)
第一个call()
方法在主线程执行任务, 第二个call()
方法可以把工作提交给一个Executor
.
Callable有点类似于Runnable:
public class CarlyCallable implements Callable<String> {
@Override
public String call() throws Exception {
return "Call me maybe";
}
}
参数制定了方法的返回值的类型, 进而也是创建出Task的类型.
Task<String> task = Tasks.call(new CarlyCallable());
想要链式执行, 进行后续操作, 可以用Continuation.
public class SeparateWays implements Continuation<String, List<String>> {
@Override
public List<String> then(Task<String> task) throws Exception {
return Arrays.asList(task.getResult().split(" +"));
}
}
它继承接口时指定了输入和输出的类型, 它的输入来自于Task的输出.
可以多写几个Continuation类然后连起来:
Task<String> playlist = Tasks.call(new CarlyCallable())
.continueWith(new SeparateWays())
.continueWith(new AllShookUp())
.continueWith(new ComeTogether());
playlist.addOnSuccessListener(new OnSuccessListener<String>() {
@Override
public void onSuccess(String message) {
// The final String with all the words randomized is here
}
});
显示和管理复杂的RecyclerView布局, 把你的items按照逻辑分组管理.
Gradle插件, 用JUnit5做Android的单元测试.
用来构建复杂的RecyclerView屏.