Android Weekly Issue #245

February 19th, 2017

Android Weekly Issue #245

本期内容: 写好单元测试的几条原则; 如何mock Kotlin的对象; 如何消除God Object -> Context; 如何用Android来打电话和发短信, 以及相应事件的监听; 一个监控用电情况的应用(Android Things);

用Keystore保存敏感信息; 依赖注入和Dagger 2的使用; Wear应用向Wear 2.0的迁移; 用ViewPager构建无Fragment的应用结构; Android应用的压力测试讨论; RxJava中Subscription注销处理不当引起的内存泄露; 单元测试并不是完全可靠; Trello向离线模式迁移的架构变化.

本周推荐的代码里有一个顶部提示控件, 一个手势检测库, 还有一个loading view的库.

ARTICLES & TUTORIALS

Write awesome unit tests

作者关于写好单元测试提供了三条简单的规则以及每条规则对应的一些建议.

1. 尽快尽早地跑测试.

尽量在每次改动之后都跑跑测试, 及早发现问题. 你的测试跑得越快你就越有可能经常跑它们.

为了让测试跑得很快:

  • 让测试跑在JVM上而不是设备上.
  • 仅测试独立的逻辑模块.
  • 不要包含UI, 数据库, 或者网络测试在你的主测试套件中.
  • 测试中不要使用wait/sleep.

2. 小并且关注点集中的测试

对每一个bug来说, 应该有且只有一个测试挂掉, 并且测试失败的原因应该能从测试方法名上看出来.

这样就迫使你每一个测试只检查一件事情, 导致你的测试小并且简单易懂, 也好维护.

实现tips:

  • 测试中只有一条assert/verify语句.
  • 有更多的小测试, 而不是几个大测试.
  • 测试的名字能清楚地描述失败的原因.

3. 100%的可靠性

你的测试应该是完全值得信赖的, 不应该随机失败, 否则你将会对测试失去信任, 也不再会认真对待测试的失败.

所以你的测试应该是100%可靠的, 只在真的有问题的时候才失败.

建议是:

  • 在JVM上跑测试, 因为到设备的连接可能会中断.
  • 在测试的时候mock网络通信.
  • 把UI/集成测试移出你的单元测试套件.

Helping to Mock Tests in Kotlin

因为Kotlin中的类默认是final的, 要继承的话需要显示地声明open.

如果只是为了在单元测试中mock就要加个open吗? 不.

本篇文章就介绍如何如何mock Kotlin的对象, 而不用该它的声明.

首先, Set up; 然后, 使用这个库mockito-kotlin.

文中详细介绍了使用细节, 以及对any()方法的讨论.

How and Why I Kill God Objects

在面向对象编程中, God Objects是应该被避免的.

在Android开发中, 最常见的一种God对象是Context. 本文介绍如何清除这个God对象, 同样的方法也可以用来处理其他对象.

首先说为什么要干掉Context?

在做TDD的过程中, 我们希望是面向接口的, 而且我们不应该mock非我们拥有的类型.

所以我们不应该直接mock外部的API, 而是应该创建一个自己的接口层.

作者发现很多类其实并不真正需要一个Context, 它们只是需要得到string或者存储的键值对.

之后文中举例介绍了如何通过定义接口摆脱Context.

How to Make Calls and Use SMS in Android Apps

如何拨打电话:

String dial = "tel:" + phoneNo;
startActivity(new Intent(Intent.ACTION_DIAL, Uri.parse(dial)));

(不需要权限).

如果想在app里直接拨出去电话, 需要权限android.permission.CALL_PHON, 并且改用ACTION_CALL.

监控电话事件:

需要权限android.permission.READ_PHONE_STATE.来监控来电, 打出去的电话需要这个权限: android.permission.PROCESS_OUTGOING_CALLS.

具体实现就是在TelephonyManager注册监听器PhoneStateListener. 如果是在Activity中需要在对应的生命周期注销监听器.

如果需要后台监控, 则需要用到BroadcastReceiver, 过滤actions为android.intent.action.PHONE_STATEandroid.intent.action.NEW_OUTGOING_CALL.

除了获取相应的电话号码, 还可以进一步阻止电话的拨出.

发送短信:

发短信也是两种方法, 启动一个短信客户端程序, 或者直接从程序里发.

启动其他程序:

Intent smsIntent = new Intent(Intent.ACTION_SENDTO, Uri.parse("smsto:" + phoneNo));
smsIntent.putExtra("sms_body", message);
startActivity(smsIntent);

自己发: 需要权限android.permission.SEND_SMS.

SmsManager smsManager = SmsManager.getDefault();
smsManager.sendTextMessage(phoneNo, null, message, null, null);

注意Android 6.0以上的设备, 本文提到的这些危险权限都是需要动态请求的.

收短信:

通过BroadcastReceiver, 需要权限android.permission.RECEIVE_SMS.

Android Things - Electricity Monitoring App

作者分享了一个她的Android Things的应用(和Github repo), 可以监控她家的用电情况.

Using Keystore system to store and retrieve sensitive information

利用Android的Keystore来存储一些敏感信息.

The lost droid and the magic Dagger

一篇依赖注入的介绍文章.

先介绍依赖注入是什么, 有什么优点, 接着介绍Dagger 2的使用.

Wear 2.0: Match Timer – Part 1

作者把他的Wear应用升级到了Wear 2.0.

ViewPager without Fragments

一些开发者可能不想选择Fragment, 这篇文章里有相关讨论: Advocating Against Android Fragments.

作者推荐了一些在不用Fragment的情况下构建App的库: Conductor, mosby, flow, mortar.

而本篇文章想要展示另一种方法, 既不用Fragment, 也不用上述的第三方库来构建一个App -> 用ViewPager.

在PagerAdapter里管理了一个Presenter的List, 每一个Presenter管理一个View. 具体实现见原文.

Stress-testing Android apps

之前大神JakeWharton有一个Sample App: JakeWharton/u2020, 里面有一个debug drawer, 可以用来模拟不同的测试情形, 比如网络连接不好, 延迟, 或者网络错误等等.

作者他们的App也有一个类似的debug drawer, 他们讨论出了一个需要测试的情形的checklist:

  • 网络延迟
  • 错误率
  • 离线模式
  • 屏幕旋转
  • 应用在后台被杀死
  • 应用升级
  • Key Bashing
  • 多窗口模式 (Android N)
  • TransactionTooLargeException (Android N)

作者甚至发现其中的一些项目组合起来测试非常有趣.

  • 网络延迟: 可结合方向改变/app后台被杀死测试.
  • 错误率: 可以检查错误是否被正确处理并被报告.
  • 离线模式: 关掉网络或者打开飞行模式, 检测正在执行的网络请求是否会引起崩溃; 是否正确通知了用户连接丢失了; 所有应该被cach的内容是否被正确cach了.
  • 方向改变: 检查: 正在进行的请求会怎么办? app的状态是否被正确恢复了? 是否加载了当前方向对应的正确资源?
  • App在后台被杀死: 可以通过命令: adb shell am kill YOUR_PACKAGNE_NAME或者"Do not keep activities"来模拟这种情形. 相关阅读: Optimizing for Doze and App Standby.
  • App更新: 升级后之前的数据是否被保存了?
  • Key Bashing: 剧烈的滑动和敲击可能产生一些奇怪的错误. 可以跑Monkey来测试一下你的应用: adb shell monkey -p YOUR_PACKAGNE_NAME.
  • 多窗口模式(Android N): 列出了一些多窗口的测试项目, 详情见原文.
  • TransactionTooLargeException (Android N): Bundle中的数据不能太大, 超过限制, 在Android N以上会直接抛异常.

How to leak memory with Subscriptions in RxJava

文中举了一个例子, 用RxJava结合MVP, 做网络请求, 更新UI, 很常见的使用情形.

在生命周期结束的时候调用RxJava的Subscription.unsubscribe()来注销, 以结束还在进行的网络请求.

看上去没有什么问题, 但是程序实际运行, 反复旋转屏幕进行测试, StrictMode报告出了Activity的InstanceCountViolation, dump memory的确看到了多个Activity的实例. 这是为什么呢?

作者深究原因, 发现Subscriber的子类存储的都是final的字段, 比如这个类:

public final class ActionSubscriber<T> extends Subscriber<T> {

    final Action1<? super T> onNext;
final Action1<Throwable> onError;
final Action0 onCompleted; public ActionSubscriber(Action1<? super T> onNext, Action1<Throwable> onError, Action0 onCompleted) {
this.onNext = onNext;
this.onError = onError;
this.onCompleted = onCompleted;
}
...
}

因为它们都是final的, 所以最后即便执行了注销操作, 也是没有办法把它们置为null的.

在生命周期结束的时候注销的操作是这样:

public void destroy() {
subscription.unsubscribe();
view = null;
}

这个subscriptionsubscribe()方法的返回值, 被保存在Presenter的一个字段里, 它实际就是Subscriber对象.

这里的问题就是, 在destroy()之后, 该引用并没有被置为null, 导致了下面的引用链:

Presenter -> subscription字段, 也即匿名的`Subscriber`对象 -> final字段 -> 对view的引用 -> 对Activity的引用.

从而造成了内存泄露.

解决的办法有两个:

  • subscription.unsubscribe();之后把subscription字段置为null.
  • 使用CompositeSubscription, 它可以管理多个Subscription对象, 用它的clear()方法, 它会unsubscribe所有项目并且清除所有的引用.

文后还列了相关的资料, 作者发现问题并寻找原因的思路很值得学习.

Your Unit tests might not be as reliable as you thought

作者举了个例子, 说明即便你的单元测试过了, 也不保证你的产品代码一定没问题.

他的例子是

SimpleDateFormat fmt = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSXXX");

失败的原因是因为XXX格式是在Android 4.3以上才支持的, 它是在java.text包下的. 所以实际在高版本的设备还运行正常, 换个低版本的设备就崩溃了.

所以单元测试并不一定可靠, 因为跑单元测试的JVM和Android设备上的JVM有可能不一样.

Airplane Mode: Enabling Trello Mobile Offline

Trello移动移动现在有离线模式了. 作者介绍了他们的心路历程和架构变化. (比较简单和笼统的介绍).

Self-guided resources to Android development

这是一条Twitter, 作者分享了Android的学习资源. (可惜我打不开里面说的链接, 不知为何.)

LIBRARIES & CODE

Alerter

一个加在Window的Decor View上面的顶部提示栏, 类似于Snackbar和Toast一类的东东. 可定制外观, icon, 加多行字, 可添加click事件.

sensey

一个好用的手势检测库.

mkloader

好看并且平滑的自定义loading view. 目前支持好几种图案.

欢迎关注微信公众号: 圣骑士Wind

Android Weekly Notes Issue #245的更多相关文章

  1. Android Weekly Notes Issue #230

    Android Weekly Notes Issue #230 November 6th, 2016 Android Weekly Issue #230. Android Weekly笔记, 本期内容 ...

  2. Android Weekly Notes Issue #227

    Android Weekly Issue #227 October 16th, 2016 Android Weekly Issue #227. 本期内容包括: Google的Mobile Vision ...

  3. Android Weekly Notes Issue #237

    Android Weekly Issue #237 December 25th, 2016 Android Weekly Issue #237 这是本年的最后一篇issue, 感谢大家. 本期内容包括 ...

  4. Android Weekly Notes Issue #229

    Android Weekly Issue #229 October 30th, 2016 Android Weekly Issue #229 Android Weekly笔记, 本期内容包括: 性能库 ...

  5. Android Weekly Notes Issue #221

    Android Weekly Issue #221 September 4th, 2016 Android Weekly Issue #221 ARTICLES & TUTORIALS And ...

  6. Android Weekly Notes Issue #219

    Android Weekly Issue #219 August 21st, 2016 Android Weekly Issue #219 ARTICLES & TUTORIALS Andro ...

  7. Android Weekly Notes Issue #236

    Android Weekly Issue #236 December 18th, 2016 Android Weekly Issue #236 本期内容包括: Google的物联网平台Android ...

  8. Android Weekly Notes Issue #235

    Android Weekly Issue #235 December 11th, 2016 Android Weekly Issue #235 本期内容包括: 开发一个自定义View并发布为开源库的完 ...

  9. Android Weekly Notes Issue #234

    Android Weekly Issue #234 December 4th, 2016 Android Weekly Issue #234 本期内容包括: ConstraintLayout的使用; ...

随机推荐

  1. STM32-USB那点事

    STM32 USB那点事1 USB那点事2 - Custom HID例子程序解疑 USB那点事3 -使用端口2作为custom HID的传输 USB那点事5之USB通信出错 USB那点事6传输要素 S ...

  2. BZOJ 1455: 罗马游戏 [可并堆]

    1455: 罗马游戏 Time Limit: 5 Sec  Memory Limit: 64 MBSubmit: 1715  Solved: 718[Submit][Status][Discuss] ...

  3. c#第5章 变量的更多内容 隐式和显式转换、枚举、结构、数组、

    1.目标数据 destination 英[ˌdestɪˈneɪʃn] 美[ˌdɛstəˈneʃən] n. 目的,目标; 目的地,终点; [罕用语] 预定,指定; 2.源数据 source 英[sɔ: ...

  4. java系列--过滤器

    在web.xml配置过滤器:过滤器一定要放在所以Servlet前面 过滤器的生命周期: 过滤器的应用: 1.编码格式 2.权限验证 3.数据库关闭

  5. lower_bound和upper_bound算法实现

    lower_bound算法要求在已经按照非递减顺序排序的数组中找到第一个大于等于给定值key的那个数,其基本实现原理是二分查找,如下所示: int lower_bound(vector<int& ...

  6. call的初步理解

    首先说下call的本质是一个函数 模Function.prototype.call = function(context){ // this表示某函数,函数里面的this先被替换成context,然后 ...

  7. 如何解决PHP+MySQL出现乱码的现象

    在使用PHP+MYSQL时,您是否遇到过字符乱码的问题呢?您是如何解决这个问题的呢?这里提供了一种解决之道. 在mysql_connect后面加一句SET NAMES UTF8,即可使得UTF8的数据 ...

  8. Hibernate懒加载的三种解决方案

    Hibernate懒加载的两种解决方案: 1.Hibernate.initialize(代理对象) 2.在*.hbm.xml映射文件中添加lazy="false"属性 3.使用op ...

  9. Jq对象与dom对象的互相转换!

    JQ对象转化成dom对象 var a=$('div'); var b=a[0];//dom对象 转化成dom对象以后就可以使用dom方法了 dom对象转化成jq对象 var a=document.ge ...

  10. 在代码中控制UI界面

    虽然Android推荐使用XML布局文件来控制UI界面,但如果开发者愿意,Android允许开发者完全抛弃XML布局文件,完全在Java代码中控制UI界面. 实例:用编程的方式开发UI界面 packa ...