Effective Java 笔记

Java 语言中存在四种类型:

  • 接口 interface
  • 类 class
  • 数组 array
  • 基本类型 primitive type

前三种是引用类型,类实例和数组是对象,基本类型不是对象。

在Java中一共有8种基本数据类型,其中有4种整型,2种浮点类型,1种用于表示Unicode编码的字符单元的字符类型和1种用于表示真值的boolean类型。(一个字节等于8个bit)

1.整型

类型 存储需求 bit数 取值范围 备注
byte 1字节 1*8 -128~127  
short 2字节 2*8 -32768~32767  
int 4字节 4*8 -2^31 ~ 2^31-1  
long 8字节 8*8 -2^63 ~ 2^63-1  

2.浮点型

类型 存储需求 bit数 取值范围 备注
float 4字节 4*8 2^-149 ~ (2-2^-23)·2^127 float类型的数值有一个后缀F(例如:3.14F)
double 8字节 8*8 2^-1074 ~ (2-2^-52)·2^1023 没有后缀F的浮点数值(如3.14)默认为double类型, Double 有静态变量 MIN_VALUEMAX_VALUE

3.char类型

类型 存储需求 bit数 取值范围 备注
char 2字节 2*8 0 ~ 65,535  

4.boolean类型

类型 存储需求 bit数 取值范围 备注
boolean 1字节 1*8 false、true  

补充:Java有一个能够表示任意精度的算术包,通常称为“大数值”(big number)。虽然被称为大数值,但它并不是一种Java类型,而是一个Java对象。 如果基本的整数和浮点数精度不能够满足需求,那么可以使用 java.math 包中的两个很有用的类:BigIntegerBigDecimal (Android SDK中也包含了java.math包以及这两个类)这两个类可以处理包含任意长度数字序列的数值。BigInteger 类实现了任意精度的整数运算,BigDecimal 实现了任意精度的浮点数运算。具体的用法可以参见Java API。

BigInteger 和 BigDecimal 都是不可变 immutable ,类似于 String, 在使用

BigInteger sum = new BigInteger.valueOf(0);
sum.add(BigInteger.valueOf(10));    // wrong way, sum is still 0
sum = sum.add(BigInteger.valueOf(10));   // right way, add() return a BigInteger Object.

创建和销毁对象

1. 静态工厂方法代替构造器

优点:

  1. 静态工厂方法有名字;
  2. 不必要每次调用的时候都创建一个新的对象。
  3. 返回原返回类型的任何子类型对象。
  4. 创建参数化类型实例的时候使代码变得更加简洁。

2. 遇到多个构造器参数时要考虑使用构造器

Builder 模式,代码易读,模拟了具名的可选参数,build 方法可检验约束条件。 builder 可以自动填充某些域,比如每次创建对象的时自动增加序列号。

类的构造器或者静态工厂中具有多个参数时,设计这种类, Builder 模式可考虑,特别是当大多数参数都是可选的时候。

当类的参数初始化时有相互关联时使用 Builder 模式。

3. 用私有构造器或者枚举类型强化 Singleton 属性

共有静态成员 final 域

public class Elvis {
    public static final Elvis INSTANCE = new Elvis();
    private Elvis() { … }
    public void leaveTheBuilding() {…}
}

静态方法所有调用,都会返回同一个对象引用,不会创建其他 Elvis 实例。

公有的成员是静态工厂方法:

public class Elvis {
    private static final Elvis INSTANCE = new Elvis();
    private Elvis() { … }
    public static Elvis getInstance() { return INSTANCE; }
    
    public void leaveTheBuilding() {…}
}

4. 通过私有构造器强化不可实例化的能力

希望一个类不能被实例化,则私有化构造方法

5. 避免创建不必要的对象

String s1 = "string";

String s2 = new String("string");  // don‘t do this

当心无意识的基本类型自动装箱。

6. 清除过期的对象引用

Java 内存泄露可能发生在:

  • 类自我管理内存

    比如在实现 Stack 栈时,弹出栈时,消除对象引用,结束变量的生命周期。

  • 缓存
  • 监听器或者其他回调。

7. 避免使用终结方法

提供显式的终止方法,要求类客户端在每个实例不再有用的时候调用这个方法。 Java 中 FileInputStream, FileOutputStream,Timer 和 Connection 都具有终结方法。

第3章

所有对象都通用的方法, 非 final 方法(equals、hashCode、toString、clone 和 finalize)都有明确的通用约定,被设计成被覆盖。

8. 覆盖 equals 时请遵守通用约定

类的每个实例都只与它自身相等

  • 类的每个实例本质都是 唯一
  • 不关心类是否提供“逻辑相等”的测试功能
  • 超类已经覆盖 equals,从超类继承过来的行为对于子类也合适
  • 类是私有的或者包级私有,可以确定 equals 方法永远不会被调用

什么时候覆盖 Object.equals ?

  • 类具有特定“逻辑相等”概念,不等同于对象等同的概念。
  • 并且超类没有覆盖 equals 实现。

equals 等价关系:自反,对称,传递,一致

里氏替换原则 Liskov substitution principle ,类型的任何重要属性也将适用于它的子类型。

9. 覆盖 equals() 时一定要覆盖 hashCode()

10. 始终覆盖 toString

将类的基本信息放入。IDE 中一般可以自动生成该方法。

11. 谨慎地覆盖 clone

12. 考虑实现 Comparable 接口

实现对象实例之间的比较。

第4章

13. 类和成员的可访问性最小化

encapsulation 封装,模块化

原因:有效地解除组成系统各个模块之间的耦合关系,使得模块之间可以独立开发、测试、优化、使用、理解和修改。

  • 尽可能使每个类或者成员不被外界访问
  • 实例域决不能是拥有的
  • 类具有公有的静态final数组域,或者返回这种域的访问方法,总是错误的,客户端能够轻易的修改数组中的内容

类成员的公开级别:

  • 私有 private,只有声明该类的内部才能访问
  • 包级私有 package-private 缺省访问级别,声明该成员的包内部任何类都能访问
  • 受保护 protected,声明该类的子类可以访问,包内部其他类可以访问
  • 公有 public,任何地方都可以访问,如果做成公有接口,则需要考虑并有责任永远支持该方法,以保持兼容性

公有的静态 final 域来暴露常量,惯例,使用 大写字母 加 下划线,这些域要么包含基本类型,要么包含指向不可变对象的引用。

14. 公有类中使用访问方法而非公有域

未来改变类的内部表示时非常有效,如果类是包级私有,或者私有嵌套类,直接暴露数据域并没有本质的错误。

15. 使可变性最小化

不可变类 实例不能被修改,实例包含的信息必须在创建时提供,并且在对象生命周期内固定不变。 String 基本类型包装类, BigInteger 和 BigDecimal 是不可变类。

原因:不可变类易于设计、实现和使用,不容易出错,更加安全

类不可变类要遵循一下规则:

  • 不要提供任何会修改对象状态的方法
  • 保证类不会被扩展, 将类定义成 final 类
  • 使所有域都是 final
  • 使所有域都是私有的
  • 确保任何可变组件互斥访问,

优点:1. 不可变对象本质上是线程安全的,他们不要求同步。2. 不可变对象可以被自由地共享,将频繁使用的值提供公有的静态final常量,将频繁被请求的实例缓存起来,当现有实例可以符合请求时,不用创建新的实例。基本类型的包装类和 BigInteger 都有这样的静态工厂。3. 不可变对象为其他对象提供了大量的构建。

缺点:不可变类唯一的缺点就是,对于每个不同的值都需要一个单独的对象。

16. 复合优先于继承

不扩展现有的类,而是在新的类中增加一个私有域引用一个类的实例,这种设计叫做“复合” Composition。

优点:与方法调用不用,继承打破了封装性,子类依赖于父类中特定功能的实现细节,因此复合被提出,不扩展现有的类,而是在新的类中增加一个私有域来组成新的类。

缺点:包装类没什么缺点,但需要注意,包装类不适合用在回调框架 callback framework。

17. 为继承而设计,提供文档说明,否则就禁止继承

类必须有文档说明它可覆盖的方法的自用性。在发n布类之前编写子类进行测试。

为了允许继承,类需要遵守的约束条件:

  • 构造函数绝不能调用可被覆盖的方法
  • clone 和 readObject 都不可以调用可覆盖的方法,不管是以直接还是间接的方式。
  • 如果决定在一个为了继承而设计的类中实现 Serializable ,并且该类有 readResolve 或者 writeReplace 方法,则必须使 readResolve 或者 writeReplace 成为受保护的方法,而不是私有方法。

两种方法禁止子类化:

  • 声明 类 final
  • 所有构造函数变成私有,或者包级私有

18. 接口优于抽象类

Java 提供两种机制,来允许多实现,接口与抽象类。重要区别在于:

  • 抽象类 允许包含某些方法的实现 ,接口则不允许
  • 为了实现抽象类定义的类型,类必须成为抽象类的子类,Java只允许单继承,抽象类受到一些限制

优点:

  • 现有的类可以很容易被更新,实现新的接口
  • 接口是定义 mixin (混合类型)的理想选择
  • 接口允许我们构造非层次结构的类型框架

19. 接口只用于定义类型

常量接口 constant interface ,不包含 任何方法,只包含静态final域,每个域都是一个常量。反面,不值得使用。

导出常量可选合理方案,将常量添加到 相关类 或者接口中,尽量使用枚举类型 enum type,否则应该使用不可实例化的工具类 utility class 来导出常量。

接口只应该被用来定义类型,不应该被用来导出常量。

20. 类层次优于标签类

标签类指在类中用标签区别类的行为模式,比如使用枚举变量,更加枚举不同显示不同内容。尽量不使用标签类,使用子类抽象,将标签类中每个方法都定义成一个包含抽象方法的抽象类。

21. 用函数对象表示策略

允许程序把“调用特殊函数的能力”存储起来并且传递这种能力,这种机制允许函数调用者通过传递第二个函数,来指定行为。

执行对象上的某项操作,能够实现函数指针。

Comparator

22. 优先考虑静态成员类

嵌套类 nested class 指被定义在另一个类内部的类。

嵌套类有四种:

  • 静态成员类 static member class
  • 非静态成员类 nonstatic member class
  • 匿名类 anonymous class
  • 局部类 local class

除去第一种之外,其他三类都被称为内部类 inner class。

静态成员类,最简单的嵌套类,普通类,静态成员类可以访问外围类的所有成员,包括私有。访问性原则同类中其他成员。

静态成员类,常见用法作为公有辅助类,与外部类一起使用时才有意义。

非静态成员类,语法层面只有一个 static 修饰符的不同,但是两者区别很大。非静态成员类的每一个实例都隐含着与外围类的一个外围实例 enclosing instance。嵌套的实例需要外围类的实例。

非静态成员类的一种常见用法是定义 adapter,它允许外部类的实例被看做是另一个不相关的类的实例。

私有静态成员类 常见用法是代表外围类所代表的对象的组件。

匿名类没有名字,在使用的同时被声明和实例化。匿名类可以出现在代码中任何允许存在表达式的地方。

匿名类的一种常见用法是动态地创建函数对象 function object。 另一种常见用法是创建过程对象 process object ,比如 Runnable , Thread 或者 TimeTask 实例。第三种常见用法是在静态工厂方法的内部。

局部类是嵌套类中用得最少的类。任何可以声明局部变量的地方都可以声明局部类。

成员类的每个实例都需要一个指向外围实例的引用,就要把成员类做成非静态的,否则做成静态的。

嵌套类属于方法内部,只需要一个地方创建实例,匿名类,否则就是局部类

23. 不在新代码中使用原生态类型

尽早发现错误,最好是编译时就发现。 在 Java 1.5 之前,运行时,集合,才会发现错误。

24. 消除非受检警告

非受检强制转化警告 unchecked cast warnings

非受检方法调用警告

非受检普通数组创建警告

非受检转换警告 unchecked conversation warnings

尽可能小的使用 SuppressWarnings(“unchecked”) 注解

25. 列表优先 于数组

数组是协变 covariant 的,泛型是不可变的 invariant。

数组在运行时才知道并检查元素类型。泛型在编译时就能检查类型。

26. 优先考虑泛型

27. 优先考虑泛型方法

编写泛型方法和编写泛型类相似

泛型方法的特性是,无需明确指定类型参数的值,编译器通过检查方法参数的类型来计算类型参数的值。类型推导。

28. 利用有限制通配符来提升API的灵活性

参数化类型是不可变的

Interface Iterable<T> 接口,实现这个接口可以让对象使用 foreach 循环,或者使用

// If you have Iterable<String> , you can do:
for (String str : myIterable) {
    ...
}

大部分的容器类都实现了这个接口。

http://docs.oracle.com/javase/6/docs/api/java/lang/Iterable.html

Java 提供了特殊的参数化类型,有限制的通配符类型 bounded wildcard type

29. 优先考虑类型安全的异构容器

第六章

枚举和注解, Java 1.5 版本中增加两个引用类型家族:1. 新的类枚举类型(enum type) 2. 新的接口注解类型(annotation type)。

30. 用枚举类型 enum 代替 int 常量

枚举类型是指由一组固定常量组成合法值的类型。

优点:

  • 采用 int 枚举模式,程序非常脆弱
  • 将int枚举打印成字符串没有便利方法
  • 使用 String 常量无效

31. 用实例域代替序数

所有枚举都有 ordinal 方法,返回枚举常量在类型中的位置,不要使用该方法。永远不要根据枚举的序数导出与它关联的值,而是应该将它保存到实例域中。

32. 用 EnumSet 代替位域

33. 用 EnumMap代替序数索引

34. 用接口模拟可伸缩的枚举

35. 注解优先于命名模式

不必定义注解类型,但是都应该使用 java 平台所提供的预定义注解类型。

36. 坚持使用 Override 注解

37. 用标记接口定义类型

标记接口 marker interface 没有包含任何方法声明的接口,指明一个类实现的某种属性的接口。

第七章

设计方法的参数,返回值

38. 检查参数的有效性

39. 必要时进行保护性拷贝

在传入可变参数时需要特别注意,外部可能会通过改变传入参数而间接的影响到类内部的实现。因此需要在初始化的时候进行保护性拷贝。

40. 谨慎设计方法签名

  • 谨慎地选择方法名称,遵循命名习惯

  • 不过于追求提供便利的方法

  • 避免过长的参数列表

有三种方法可以缩短长参数列表

  • 将方法拆解成多个方法

  • 创建辅助类 helper class , 保存参数分组,一般作为静态成员类

  • 从对象构建到方法调用都采用 Builder 模式,多次 setter

对于参数类型,要优先使用接口而非类, Map接口作为参数,可以传入 Hashtable, HashMap,TreeMap,TreeMap 子映射表submap 等等

对于 boolean 参数,优先使用两个元素的枚举类型

41. 慎用重载

永远不要导出两个具有相同参数数目的重载方法。

42. 慎用可变参数

Java 1.5 版本中增加了 可变参数 varargs 方法,一般称为 variable arity method 。可匹配不同长度的变量的方法。

可变参数方法接受0个或者多个指定类型的参数,先创建一个数组,数组大小为在调用位置所传递的参数数量,然后将参数值传到数组中,最后将数组传递给方法。

43. 返回零长度的数组或者集合,而不是 null

44. 为所有导出的API元素编写文档注释

方法的文档应该简洁地描述出它和客户端之间的约定。

第八章

45. 将局部变量的作用域最小化

几乎每一个局部变量的声明都应该包含一个初始化表达式。

46. for-each 循环优于传统 for 循环

有三种情况无法使用 for-each 循环

  • 过滤—-需要遍历集合,使用 remove方法
  • 转换 —- 需要低缓其中部分或者全部数据
  • 平行迭代—- 并行遍历多个集合

47. 了解使用类库

使用标准类库,可以充分利用这些编写标准类库的专家知识,以及在你之前其他人的使用经验。

将时间花在应用程序上,而不是底层细节。

都应该熟悉 java.lang java.util java.io 中的内容。

java.util.concurrent 并发工具

48. 需要精确的答案,避免使用 float 和 double

BigDecimal

使用 int 或者 long 数值范围没有超过9位十进制数,可以使用int,没有超过18位,可用 long ,超过18位,就必须使用 BigDecimal

49. 基本类型优先于装箱基本类型

基本类型 primitive int double boolean

对应的引用类型 reference type ,称为装箱基本类型 boxed primitive ,对应为 Integer、Double、Boolean

何时使用装箱基本类型:

  • 作为集合中的元素、键、值

50. 如果其他类型更适合,则尽量避免使用字符串

  • 字符串不适合代替其他的值类型

  • 字符串不适合代替枚举类型

  • 字符串不适合代替聚集类型,如果需要用 String 来描述实体,通常不建议这样做,通常应该用一个私有静态成员类来描述。

  • 字符串不适合代替能力表 capabilities

如果可以使用更加合适的数据类型,或者可以编写更加合适的数据类型,就应该避免使用字符串来表示对象。

51. 小心字符串连接的性能

字符串连接操作符 “+”

获得可接受的性能,使用 StringBuilder 代替 String append 方法

52. 通过接口引用对象

使用接口作为类型,程序会更加灵活

53. 接口优先于反射机制

核心反射机制 core reflection facility java.lang.reflect 通过程序来访问关于已装载的类信息的能力。给定 Class 实例,可以获得 Constructor Method 和 Field 实例。

丧失了编译时类型检查的好处

执行放射访问所需要的代码笨拙冗长

性能损失

反射功能只是在设计时被用到,通常普通应用程序在运行时不应该以反射方式访问对象。

54. 谨慎使用本地方法

JNI 方法调用本地 Native method

55. 谨慎地进行优化

  • 规则1:不要进行优化
  • 规则2:(仅针对专家)还是不要进行优化,再没有绝对清晰的未优化方案之前不要进行优化。

不要试图去编写快速的程序—-应该努力编写好的程序,速度随之而来。再设计系统时,设计API、线路层协议和永久数据格式时候,一定要考虑性能因素。

56. 遵守普遍接受的命名惯例

包名,层次,句号分割,小写字母和数字(少使用) 不以 java 和 javax 开头

包名其余部分通常不超过8个字符

第九章 异常

57. 只针对异常情况才使用异常

58. 对可恢复的情况使用受检异常,对编程错误使用运行异常

Java 提供三种可抛出结构:

  • 受检的异常 checked exception

  • 运行时异常 run-time exception

  • 错误 error

期望调用者能够适当地恢复,使用受检异常。

用运行时异常来表明编程错误。

runtimeException 子类

1、 java.lang.ArrayIndexOutOfBoundsException
数组索引越界异常。当对数组的索引值为负数或大于等于数组大小时抛出。
2、java.lang.ArithmeticException
算术条件异常。譬如:整数除零等。
3、java.lang.NullPointerException
空指针异常。当应用试图在要求使用对象的地方使用了null时,抛出该异常。譬如:调用null对象的实例方法、访问null对象的属性、计算null对象的长度、使用throw语句抛出null等等
4、java.lang.ClassNotFoundException
找不到类异常。当应用试图根据字符串形式的类名构造类,而在遍历CLASSPAH之后找不到对应名称的class文件时,抛出该异常。

5、java.lang.NegativeArraySizeException 数组长度为负异常 6、java.lang.ArrayStoreException 数组中包含不兼容的值抛出的异常 7、java.lang.SecurityException 安全性异常 8、java.lang.IllegalArgumentException 非法参数异常

2.IOException IOException:操作输入流和输出流时可能出现的异常。 EOFException 文件已结束异常 FileNotFoundException 文件未找到异常

  1. 其他 ClassCastException 类型转换异常类 ArrayStoreException 数组中包含不兼容的值抛出的异常 SQLException 操作数据库异常类 NoSuchFieldException 字段未找到异常 NoSuchMethodException 方法未找到抛出的异常 NumberFormatException 字符串转换为数字抛出的异常 StringIndexOutOfBoundsException 字符串索引超出范围抛出的异常 IllegalAccessException 不允许访问某类异常 InstantiationException 当应用程序试图使用Class类中的newInstance()方法创建一个类的实例,而指定的类对象无法被实例化时,抛出该异常

59. 避免不必要地使用受检的异常

60. 优先使用标准异常

61. 抛出与抽象相对应的异常

62. 每个方法抛出的异常都要有文档

63. 在细节消息中包含能捕获失败的信息

64. 努力使失败保持原子性

65. 不要忽略异常

第十章

并发

66. 同步访问共享的可变数据

synchronized 保证同一个时刻只有一个线程执行某一段代码块

当多个线程共享可变数据时,每个读或者写数据的线程都必须执行同步。

67. 避免过度同步

68. executor 和 task 优先于线程

69. 并发工具优先于wait 和 notify

70. 线程安全性的文档化

文档说明类是否可以被多个线程安全使用

  • 不可变 immutable 类实例不可变,不需要外部同步,String Long BigInteger

  • 无条件的线程安全 unconditionally thread-safe 实例可变,但是有足够的内部同步 ,实例可被并发使用,无需外部同步, Random ConcurrentHashMap

  • 有条件的线程安全 conditionally thread-safe Collections.synchronized

  • 非线程安全 not thread-safe 类实例可变,需要外部同步 ArrayList HashMap

  • 线程对立 thread-hostile 类不能安全地被多个线程并发使用

71. 慎用延迟初始化

72. 不要依赖于线程调度器

依赖于线程调度器的程序,很有可能都是不可移植的。

73. 避免使用线程组

第11章

序列化

将对象编码成字节流,对象序列化

74. 谨慎地实现 Serializable 接口

序列化成功需要:

  • 实现 java.io.Serializable
  • 类的属性必须是可序列化的

Externalizable 接口继承了 java 的序列化接口,并增加了

void writeExternal(ObjectOutput out) throws IOException;
void readExternal(ObjectInput in) throws IOException, ClassNotFoundException;

为继承而设计的类,尽量避免去实现 Serializable 接口。

内部类不应该实现 Serializable, 静态成员类可以

75. 考虑使用自定义的序列化形式

76. 保护性编写 readObject 方法

77. 对于实例控制,枚举类型优先于 readResolve

78. 考虑用序列化代理代替序列化实例


2016-09-02 Android , Java

国行 Moto 360 2代一周使用感受

在拿到 moto 360 2代国行之前就已经做过调研, 国行并不支持国际版 Android Wear 程序同步,需要用国内阉割版 Android Wear 连接,并且不能使用 Play Store 中各种定制表盘及Android wear 应用。虽然可以通过使用 国际版 Android Wear 1.3 版本连接并同步 moto 360 来激活并使用各种表盘及应用,但是拿到手使用一个星期之后,我依然还是对国行 moto 360 2代表示失望。

在没有了 Google Now 的情况下,语音几乎是不可用状态,国行所带的出门问问,在国际版 Android wear 激活的情况下几乎不可用,真实使用一次都没成功。当然在国内版 Android wear激活的情况下,可能会有一些用处,但是Android wear 的精华就是 Google Now,没有了核心的支持,几乎就沦为只能看时间,只能收通知的尴尬境地。

不过值得赞扬的是续航能力,在一周的使用情况下,12h的待机下来基本还能保证40%以上的电量,这一点值得称赞,但是几乎是一天一冲的情况下,如果能够将续航能力再提高,或许更能吸引用户。单独的无线充电技术,在使用过程也并没有出现太大的问题,至于网上曾经说过的位置放置稍有偏差就无法充电的问题也并没有遇到。

在目前给手表适配的app还不多的情况下,要给微信给个赞,在国际版 Andorid wear 匹配链接下不仅能够收通知,并且在回复时提供表情,语音,语音转文字三种方式,并就使用过程来说,中文识别并没有出现太大问题,语音消息声音大小也合适,至于表情目前似乎只能有内置的一些可选,但是也足够使用。

另一个不错的feature就是这个通知静音的设置,表盘上,从上网下可以调出设置。这个功能本来也是我在原生 Android 上非常喜欢的,可以设置自动睡眠静音,自动在Google日历上出现Event时静音。我希望在我规划好的时间里不被打扰。当然手表作为通知的扩展他的提醒也应该遵循手机的设置。

Moto 360 2nd notification settings

另外说到二代 6.0.1 的升级,在和国际版相比不支持 WIFI,不支持手势的差距下,在更新了 6.0.1 之后手势的功能被补了回来。

期望

在购买 Moto 360 2代之前,抱有的期望有以下几点:

  1. 体验 Android Wear,感受一下新系统
  2. Google Fit 的扩充,记录心率等等
  3. 期望可以通过 GPS 记录路线

基于以上的期望,到现在来看

体验

新的 Android Wear 基本就是 Android 通知的外延,Google 在对 Android Wear 的设计中就将其操作固定成卡片式的左右滑动,不同应用间的通知使用上下滑动切换,相同通知的内容,通过向左滑可以进行更多的操作。

在表盘界面,向左滑可以调出所有应用;先下滑可以调出通知设置,静音设置,还有影院模式,进入设置等操作;向上滑动可以调出系统通知,如果不存在未读通知则不显示任何通知。

长按表盘可以更换表盘设计,当然这一点非常值得称赞,我不希望生产商在手表出厂就固定了一套表盘界面,而后就再无法更改,而这一点正是 Moto 360 ,以及 Android Wear 值得称赞的地方,并且将表盘设计开放给第三方能够带来无穷的想象力结果,喜欢指针手表,拿就可以选择指针的表盘设计,如果喜欢电子表盘,也可以选择电子表盘。

在来通知时,通过向右滑动可以忽略消息,向左滑动可以进行更多的操作,比如Gmail,可以选择存档,回复,删除等等;微信可以选择回复,忽略等等。

Google Fit && GPS

在早先手机上,Google Fit曾经出现没有再后台自动启动服务而暂停记录的情况,通过 Watch 带了了更多的数据,并且也希望能够对 Google Fit 的数据做更多的补充。如今市面上的“智能手表”,“智能手环”如果不能记步,不能记录心率都快称不上智能设备了。在调研的过程中获知2代 Moto 360 其实并不带 GPS 功能,如果想要使用记录路径的话,其实使用的依然是手机的 GPS,虽然这一点有点不便,但是鉴于记录路径这个功能其实日常并不常用,并且带来的电量消耗也会随之增加,所以在运动版和普通版之间还是选择了普通版,不过有机会还是要尝试一下运动版的。

Moto 360 2nd

Reference


2016-08-13 产品体验 , Moto360 , Android , Android Wear , AndroidDev

Android 人脸检测

最近项目使用,总结了目前 Android 中使用到的人脸检测技术,主要分成三部分来介绍:

  1. Android 原生支持人脸检测,从 API 1 开始就提供原生的人脸检测,但是该方法识别率相对较低
  2. 在后来的发展中 Google 将人脸识别技术放到了 Google Play Services 中,从 Google Play Service 7.8 之后提供的 Mobile Vision API 中我们可以使用新的人脸检测API。这个方法不仅能够识别有没有人脸,还能够识别人脸的区域,甚至在视频流中追踪相同的人脸,并且提供了一系列方法来获取眼睛、嘴巴、鼻子、脸颊的位置,利用这个 API 就能够在动态的视频中给人脸增加一些有趣的效果,加个胡子,带个帽子,等等,市面上有一些动态增加表情的 App 利用的似乎就是这个技术。
  3. Face++, 国内一家做人脸检测的提供商,不过其免费版服务需经过网络,也就意味着需要联网将照片上传到其服务器,然后他返回识别的结果回来。
  4. 其他服务,经过搜索 OpenCV 也能提供类似的服务,但是目前我还没有详细了解。 OpenCV 做计算机视觉有一定积累,相信识别准确率应该有保障。

接下来就依次介绍前三种人脸检测的方法。

Android 原生人脸检测API

android.media 包中的人脸检测API 有如下两个限制:

  1. Bitmap 必须以 Config.RGB_565 解码
  2. 输入的 Bitmap 宽度需要为偶数

只要注意这两个限制,另图片眼睛的距离不要太小,其他的代码核心的没几句话。

/**
 * There are some limitation in this 用android.media 包中识别人脸package.
 * 使用使用使Face Detection API's input Bitmap must :
 * <p/>
 * 1. config with Config.RGB_565<br/>
 * 2. Bitmap width must be even<br/>
 * <p/>
 * more details can be checked
 * http://stackoverflow.com/q/17640206/1820217
 *
 * @param bitmap Bitmap
 */
private void detectUsingNative(final Bitmap bitmap) {
    if (null == bitmap || isRunning) {
        if (listener != null) {
            listener.onFail();
        }
        return;
    }
    facesCount = 0;
    final android.media.FaceDetector faceDetector = new android.media.FaceDetector(bitmap.getWidth(), bitmap.getHeight(), MEDIA_MAX_DETECT_FACE_NUMBER);
    androidNativeFacesResults = new android.media.FaceDetector.Face[MEDIA_MAX_DETECT_FACE_NUMBER];
    final Handler handler = new Handler();
    thread = new Thread() {
        @Override
        public void run() {
            facesCount = faceDetector.findFaces(bitmap, androidNativeFacesResults);

            handler.post(new Runnable() {
                @Override
                public void run() {
                    if (listener != null) {
                        listener.onSuccess();
                    }
                }
            });

            isRunning = false;
        }
    };
    thread.start();
    isRunning = true;
}

Play Service 中人脸检测

Play Service 中的人脸检测是随着 Mobile Vision API 一同出现的,这个库中还有一些其他的API,比如识别二维码,识别文字等等,并且 Play Service 中的人脸识别更准确的说应该叫人脸追踪,在官方实现的 Demo 中,直接调用手机摄像,能够一直追踪镜头中的同一人头像。

以下是部分实现,详细代码可参考文末给出的 GitHub 代码。

/**
 * 使用 Play Service 中人脸检测
 *
 * @param bitmap Bitmap
 */
private void detectUsingGms(Bitmap bitmap) {
    if (null == bitmap) {
        if (listener != null) {
            listener.onFail();
        }
        return;
    }
    facesCount = 0;

    detector = new FaceDetector.Builder(context)
            .setTrackingEnabled(false)
            .setLandmarkType(FaceDetector.ALL_LANDMARKS)
            .setClassificationType(FaceDetector.ALL_CLASSIFICATIONS)
            .build();

    // This is a temporary workaround for a bug in the face detector with respect to operating
    // on very small images.  This will be fixed in a future release.  But in the near term, use
    // of the SafeFaceDetector class will patch the issue.
    Detector<Face> safeDetector = new SafeFaceDetector(detector);

    // Create a frame from the bitmap and run face detection on the frame.
    Frame frame = new Frame.Builder().setBitmap(bitmap).build();

    faces = safeDetector.detect(frame);

    if (!safeDetector.isOperational()) {
        // Note: The first time that an app using face API is installed on a device, GMS will
        // download a native library to the device in order to do detection.  Usually this
        // completes before the app is run for the first time.  But if that download has not yet
        // completed, then the above call will not detect any faces.
        //
        // isOperational() can be used to check if the required native library is currently
        // available.  The detector will automatically become operational once the library
        // download completes on device.

        if (listener != null) {
            listener.onFail();
        }
        return;
    }
    if (listener != null) {
        listener.onSuccess();
    }
}

Face++ 人脸检测服务

Face++ 提供了联网的人脸检测服务,需要到其网站上注册开发者账号获取API使用权限。其大概检测代码如下:

    /**
     * 使用 Face++ 人脸检测
     *
     * @param file File
     */
    private void detectUsingFacePlus(File file) {
        if (!file.exists() || isRunning) {
            if (listener != null) {
                listener.onFail();
            }
            return;
        }
        final PostParameters parameters = new PostParameters();
        parameters.setImg(file);
        final Handler handler = new Handler();
        facesCount = 0;
        thread = new Thread() {
            @Override
            public void run() {
                boolean hasFace = false;
                boolean detectSucceed = false;
                Log.d("FacePlusDetect", "Detect Request :" + parameters.toString());
                HttpRequests httpRequests = new HttpRequests(FACEPLUSPLUS_APIKEY, FACEPLUSPLUS_APISECRET, false, true);
                JSONObject result;
                try {
                    result = httpRequests.detectionDetect(parameters);
                    if (result != null) {
                        detectSucceed = true;
                        JSONArray faces = result.getJSONArray("face");
                        double imgWidth = result.getDouble("img_width");
                        double imgHeight = result.getDouble("img_height");
                        if (faces != null && faces.length() > 0 && null != listener) {
                            // Has face!!
                            facesCount = faces.length();
                            facePlusResults = new RectF[facesCount];
                            hasFace = true;
                            for (int i = 0; i < facesCount; i++ ){
                                float x, y, w, h;
                                facePlusResults[i] = new RectF();
                                // 需注意返回结果的center,width,height 都为0~100,百分比
                                x = (float) faces.getJSONObject(i).getJSONObject("position").getJSONObject("center").getDouble("x");
                                y = (float) faces.getJSONObject(i).getJSONObject("position").getJSONObject("center").getDouble("y");
                                w = (float) faces.getJSONObject(i).getJSONObject("position").getDouble("width");
                                h = (float) faces.getJSONObject(i).getJSONObject("position").getDouble("height");
                                float realx = (float) (x * imgWidth / 100);
                                float realy = (float) (y * imgHeight / 100);
                                float realw = (float) (w * imgWidth / 100);
                                float realh = (float) (h * imgHeight / 100);
                                facePlusResults[i].set(realx - realw /2,
                                        realy - realh / 2,
                                        realx + realw / 2,
                                        realy + realh / 2);
                            }
//                            String genderStr = playServiceFaces.getJSONObject(0).getJSONObject("attribute").getJSONObject("gender").getString("value");
//                            gender = Gender.getValueOf(genderStr);
                        } else {
                            hasFace = false;
//                            detectSucceed = true;
//                            gender = Gender.OTHER;
                        }
//                        Log.d("FacePlusDetect", "Detect Result : hasFace = " + hasFace + "; gender = " + gender.toString());
                    }
                } catch (FaceppParseException e) {
                    detectSucceed = false;
                    Log.d(TAG, "Detect FaceppParseException !");
                    e.printStackTrace();
                } catch (JSONException e) {
//                    if (hasFace) {
//                        gender = Gender.OTHER;
//                    }
                    Log.d(TAG, "Detect JSONException !");
                    e.printStackTrace();
                }

                if (detectSucceed) {
                    handler.post(new Runnable() {
                        @Override
                        public void run() {
                            if (listener != null) {
                                listener.onSuccess();
                            }
                        }
                    });
                } else {
                    handler.post(new Runnable() {
                        @Override
                        public void run() {
                            if (listener != null) {
                                listener.onFail();
                            }
                        }
                    });
                }

                isRunning = false;
            }
        };
        thread.start();
        isRunning = true;
    }

Detect Result

Face++ Detect

Detect result

FaceDetectDemo 代码可参考:https://github.com/einverne/AndroidFaceDetectDemo

Reference

全部代码可参考 : https://github.com/einverne/Android-Face-Recognition


2016-08-06 Android , AndroidDev

Clonezilla 克隆系统

之前遇到的一个问题,安装 Linux Mint 的系统分区快要满了,但是我又不想重装系统,于是就提出来这样的一个问题。当时整天得想着如何解决这样的一个问题比较好,于是有了这篇文章。当然也借由这篇文章讲述一个复杂问题的提出到解答的整个过程。其他类似问题的解决过程也是类似的。

一个问题的提出到解决

问题:整体备份 Linux 系统,免去重装系统,进行各种配置,以及安装各种应用的麻烦

问题相关:Windows 下有 Ghost 类似的工具可以协助完成 Windows 系统镜像的制作,并且可以完整恢复系统,而 Mac 下有 Time Machine 类似的工具,似乎可以还原整个系统。我想 Linux 下应该也存在类似的工具。

问题解决:经过Google,Linux 下备份系统的方式可以有很多,我不想使用命令行,如果有现成工具最好,最后锁定关键词 “Clonezilla”,一款非常强大的备份工具,可以用来备份硬盘,分区到镜像,或者直接写入其他硬盘或者分区。

之后搜索 Clonezilla 相关教程以及使用,借助 YouTube 熟悉使用过程,下载iso镜像,安装硬盘,制作启动U盘,从U盘启动电脑,熟悉电脑硬盘的名称,sda1,sdb1,sdb2,sdc1,类似的名称,基本上 sda 就是一块硬盘,后面接的数字是分区,在使用 Clonezilla 的过程中一定要小心数据,目标一定要指定空分区,或者空磁盘,否则目标磁盘的数据会全部被清除。

在我的真实例子中,我的 Linux Mint 安装在一块硬盘的一个分区中,利用命令或者GUI,查看该分区的名称,然后我的解决方法是给电脑新安装了一块 SSD,将光驱位换了。然后给该硬盘分区,并查看该分区的名称,然后利用学习到的 Clonezilla 来完成系统从一个分区到另一个分区的克隆。

clonezilla clone linux partition

从上图就可以看出,我是将 /dev/sdb8 分区拷贝到 /dev/sda1 分区。

如下使用 sudo blkid 查看 UUID。

/dev/sda1: UUID="a7a98d76-5dab-4272-8b9a-b82042b279c5" TYPE="ext4" 
/dev/sdb1: LABEL="Program" UUID="000E3FDB00097ED7" TYPE="ntfs" 
/dev/sdb5: LABEL="Document" UUID="000C3A300002F285" TYPE="ntfs" 
/dev/sdb6: LABEL="Media" UUID="0005653100096CB5" TYPE="ntfs" 
/dev/sdb7: UUID="ad6f91df-ba08-4fad-8efc-ac1254320e2d" TYPE="swap" 
/dev/sdb8: UUID="5f920149-5676-46ef-b545-e50be77c65e2" TYPE="ext4" 
/dev/sdc1: LABEL="System" UUID="EEBACEF9BACEBCF9" TYPE="ntfs" 
/dev/sdc2: UUID="8A0005F80005EBCF" TYPE="ntfs" 

在完成从分区到分区的克隆之后,修复系统磁盘 UUID 以及启动引导 grub。这里花费了一些时间去了解 grub 的启动过程。学习了一些命令。

sudo blkid   #查看磁盘UUID
sudo fdisk -l  #查看磁盘

vim /etc/fstab  #磁盘的信息
vim /boot/grub/grub.cfg   #启动引导的信息
# 大部分的情况下不需要直接修改这两个文件,查看一下内容即可,使用其他命令更新,无需手动的修改里面的值。

update-grub2   #更新启动引导

由于我在使用 Clonezilla 备份恢复的时候使用的是从硬盘一块分区拷贝到另一块硬盘分区,所以在完成备份成功之后两款分区的 UUID 是一样的,这样就导致开机引导的时候总是回到原来的系统,需要给新的分区重新指定一个 UUID,然后更新 grub 才能完成启动到新的硬盘分区。可以通过下面的教程完成 UUID 的修改。

至于修改 Linux 分区的 UUID 其实是另一个问题,当完成从问题提出到解决的过程就能解决。

修改 Linux 分区 UUID

以下为翻译,原文见参考1

UUID 介绍

Linux 分区的 UUID 的全称是 Universally Unique IDentifier。 这个 ID 会被用在不同的地方用来标识硬盘分区。最常见的就是在 /etc/fstab 文件中, 这个文件用来再系统启动时挂载分区。以下是一个小例子:

# <file system> <mount point>   <type>  <options>       <dump>  <pass>
proc            /proc           proc    nodev,noexec,nosuid 0       0
# / was on /dev/sdc3 during installation
UUID=9467f4de-4231-401f-bcaa-fee718d49e85 /               ext4    errors=remount-ro 0       1
# swap was on /dev/sdb1 during installation
UUID=aabe7e48-2d11-421f-8609-7ea9d75e7f9b none            swap    sw              0       0

UUID 的理由

理论上来讲创建两个 UUID 相同的几率是非常小的,可以参考 Random UUID , 但是如果使用 DD 或者 Clonezilla 在同一台设备克隆并恢复了分区,那么就有可能导致完全一样的分区 ID。

使用以上两个命令将会创建两个一模一样的分区,包括 UUID。在这样的情况下,就需要手动更改 UUID。

修改 UUID

第一步,首先利用 sudo blkid 来获取分区标示。

/dev/sdb1: UUID="aabe7e48-2d11-421f-8609-7ea9d75e7f9b" TYPE="swap" 
/dev/sdc1: UUID="9467f4de-4231-401f-bcaa-fee718d49e85" TYPE="ext4" 
/dev/sdc3: UUID="93a54a4a-e0f5-4152-ae59-2245e8d16ee4" TYPE="ext4"
/dev/sde5: UUID="9467f4de-4231-401f-bcaa-fee718d49e85" TYPE="ext4" 
/dev/sde6: LABEL="var" UUID="30433f28-1b79-4b4d-9985-fef5b1c886b5" TYPE="ext4"

如上图可以看出 /dev/sdc1/dev/sde5 的 UUID 完全一致。第二步,使用命令 uuidgen 产生新的 UUID

uuidgen
f0acce91-a416-474c-8a8c-43f3ed3768f9

最后将新的 UUID 应用到新的分区中,

sudo tune2fs /dev/sde5 -U f0acce91-a416-474c-8a8c-43f3ed3768f9

最后,更新 grub 来使用新的 UUID,以免导致系统的混乱。

reference


2016-08-01 clonezilla , linux

我使用的 Xposed module

什么都不多说,这个神器就如 Chrome 下的 Tampermonkey,有很多神奇的待发现。

官网地址:http://repo.xposed.info/

更新及 change log: http://forum.xda-developers.com/showthread.php?t=3034811

Xposed Framwork

安装之前

查看设备CPU架构

例如:Nexus 6 CPU芯片是 armv7,选择 arm 即可。 Play Store 上有一个 Hardware Info 的 APP,可以查看 CPU 架构。

然后需要确认手机的 SELinux 设置成 Permissive , 可以使用 SELinuxModeChanger 一款修改 SELinux 的应用。不过 N6 默认是 Permissive 的,也就不用修改了。

下载必要内容

准备好apk,和 zip 包,最好也下载好 xposed-uninstaller*.zip ,当刷入 zip 包之后无限启动的时候可以恢复。不过最好的方式还是通过 recovery 整体备份原来的数据 Nandroid backup 。数据是最重要的!!切记。

Android 6.0 的 framework zip 包在 官网 可以下到。 而 APK 在 Xda 论坛可以找到。

刷入

通过 recovery 刷入 flash zip 之后进入系统会很长时间,之后启动 Xposed Installer ,enable module 之后需要 soft reboot 。 切记需要点击 Xposed Installer 中的 reboot ,不能自己手工 reboot ,否则无法识别 module,掉进这个坑,重启无数次无效。

保留 modules

经过反复尝试保留下来的一些modules,

AdBlocker

屏蔽广告,另外一个可选择的屏蔽广告的 module 叫做 MinMinGuard,还没尝试,效果应该也很好。很多 ROM 集成的 Adaway 似乎也不错。

AppOpsXposed

权限管理,一张图解释所有

AppOpsXposed

Xposed Pokemon

模拟地址,但是 XPrivacy 也是可以有相同的功能的。但是最后还是另一个 module 叫做 Xposed Pokemon 好用。

GravityBox MM

可以定制很多部分,包括状态栏,导航栏,感觉最有用的就是可以定制长按 recent 按钮弹出小的 Launcher。然后自定义快速点击 recent 两次切换最近使用应用也是很有用的。还可以给状态栏加上网速监测。

自用上这个功能感觉已经不在需要刷其他ROM来支持一些特殊功能了,完全原生+Xposed 就已经让我很舒服了。

Greenify

休眠后台服务,很好的App。

No Lock Home

原生 Android 的 Smart Lock 只有根据蓝牙,GPS和侦测随身携带,这个 module 增加了可以根据 WIFI,或者 mac 地址或者 LAC CID 来增加信任的地点,感觉这个更加实用。日常实用 WIFI 的地方一般都是自己熟悉的地方,将那些地方的 WIFI AP name 或者 WIFI Mac 地址加入信任列表,这样就不用总在熟悉的地方解锁解锁解锁了。自己熟悉的地方也总不至于丢手机的吧。

WechatForwarder

很强大的转发,原先一直想要的转发功能都能搞定了。

WechatUnrecalled

微信不撤回

XInsta

下载 Instagram 的图片,自从 IFTTT 不让我自动下载图片之后就诞生了这种需求。也正是因为这个需求让我发现并使用了 Xposed,然后又间接的找到了很多好玩的 module ,不过后来又找到了自动下载 ins 照片到 Google Drive 的方法

运动修改器

修改运动记步频率,还挺神奇的。

在导航栏或者状态栏显示音乐波形,太赞了。

不太需要的 module

使用之后感觉不太需要的 module ,但是很强大的 module

XPrivacy

很复杂的但是很强大的权限管理,非常细节,但是对新手不好,使用非常复杂。对权限要求比较高的可以尝试一下。

链接: http://repo.xposed.info/module/biz.bokhorst.xprivacy

Flat Style Bar Indicators

高度自定义状态栏

Flat Style Colored Bars

和之前使用的 tint status bar 效果差不多,根据应用颜色改变状态栏和导航栏的颜色

Flat Style Colored Keyboard

自动改变键盘的颜色,我使用 TouchPal 所以也用不到。

Ifont

修改系统字体

Amplify

查看那些服务,禁用服务,省电

所有的 Xposed Module 都可以在 http://repo.xposed.info/module-overview 这里找到。当然 Xda 也有很多更新内容。


2016-07-15 Android , AndroidDev , Xposed , 整理合集

我使用的 Xposed module

什么都不多说,这个神器就如 Chrome 下的 Tampermonkey,有很多神奇的待发现。一句话概括 Xposed 就是 Android 上一款可以修改任意系统进程模式的框架,包括系统进程。下面介绍的 module 基本上都是 Xposed 框架下的插件,实现了各种神奇的功能,有些功能有被官方收录到应用官方功能中的,比如 Instagram 的图片放大功能,有些功能至今还在和官方捉迷藏的,比如微信抢红包插件,还有些功能增强了系统的扩展性,让整个手机能够高效的完成日常的工作,比如在信任WIFI下自动解锁屏幕等等功能。总之 Xposed 框架让整个 Android 系统上升了一个层次。

用 Xposed 的自我介绍来说,就是能够在无感知不接触任何 APK 的情况下修改系统或者应用的行为

Xposed is a framework for modules that can change the behavior of the system and apps without touching any APKs.

Xposed 是由 rovo89 开发和维护的一个项目, 官网地址:http://repo.xposed.info/, 源代码地址在 Github: https://github.com/rovo89/Xposed

更新及 change log 在 xda 论坛: http://forum.xda-developers.com/showthread.php?t=3034811

Xposed Framwork

安装之前

查看设备CPU架构

例如:Nexus 6 CPU芯片是 armv7,选择 arm 即可。 Play Store 上有一个 Hardware Info 的 APP,可以查看 CPU 架构。

然后需要确认手机的 SELinux 设置成 Permissive , 可以使用 SELinuxModeChanger 一款修改 SELinux 的应用。不过 N6 默认是 Permissive 的,也就不用修改了。

下载必要内容

准备好apk,和 zip 包,最好也下载好 xposed-uninstaller*.zip ,当刷入 zip 包之后无限启动的时候可以恢复。不过最好的方式还是通过 recovery 整体备份原来的数据 Nandroid backup 。数据是最重要的!!切记。

Android 6.0 的 framework zip 包在 官网 可以下到。 而 APK 在 Xda 论坛可以找到。

刷入

通过 recovery 刷入 flash zip 之后进入系统会很长时间,之后启动 Xposed Installer ,enable module 之后需要 soft reboot 。 切记需要点击 Xposed Installer 中的 reboot ,不能自己手工 reboot ,否则无法识别 module,掉进这个坑,重启无数次无效。

保留 modules

经过反复尝试保留下来的一些modules,

AdBlocker

屏蔽广告,另外一个可选择的屏蔽广告的 module 叫做 MinMinGuard,还没尝试,效果应该也很好。很多 ROM 集成的 Adaway 似乎也不错。

AppOpsXposed

权限管理,一张图解释所有

AppOpsXposed

Xposed Pokemon

模拟地址,但是 XPrivacy 也是可以有相同的功能的。但是最后还是另一个 module 叫做 Xposed Pokemon 好用。

GravityBox MM

可以定制很多部分,包括状态栏,导航栏,感觉最有用的就是可以定制长按 recent 按钮弹出小的 Launcher。然后自定义快速点击 recent 两次切换最近使用应用也是很有用的。还可以给状态栏加上网速监测。

自用上这个功能感觉已经不在需要刷其他ROM来支持一些特殊功能了,完全原生+Xposed 就已经让我很舒服了。

Greenify

休眠后台服务,很好的App。

No Lock Home

原生 Android 的 Smart Lock 只有根据蓝牙,GPS和侦测随身携带,这个 module 增加了可以根据 WIFI,或者 mac 地址或者 LAC CID 来增加信任的地点,感觉这个更加实用。日常实用 WIFI 的地方一般都是自己熟悉的地方,将那些地方的 WIFI AP name 或者 WIFI Mac 地址加入信任列表,这样就不用总在熟悉的地方解锁解锁解锁了。自己熟悉的地方也总不至于丢手机的吧。

20170805 更新 WeXposed

作者将原本的多个module合并为一个,现在集成了抢红包(自定义关键词过滤),阻止撤回,防朋友圈删除,扩展表情限制,筛子随机等等等等功能,非常强大

WechatForwarder
很强大的转发,原先一直想要的转发功能都能搞定了。

WechatUnrecalled
微信不撤回

XInsta

下载 Instagram 的图片,自从 IFTTT 不让我自动下载图片之后就诞生了这种需求。也正是因为这个需求让我发现并使用了 Xposed,然后又间接的找到了很多好玩的 module ,不过后来又找到了自动下载 ins 照片到 Google Drive 的方法

运动修改器

修改运动记步频率,还挺神奇的。

在导航栏或者状态栏显示音乐波形,太赞了。

No Lock Home

通过信任连接的 WIFI,或者 WIFI MAC 地址来自动解锁屏幕,感觉比Android自带的 SmartLock 要好用很多。Smart Lock 中的 Body Detect,还算有用,其他根据 GPS 来自动解锁必须得开着高精度GPS, 不仅耗电也不精确。

LocationReportEnabler

开启 Google 位置报告

网易云音乐插件

解锁网易云音乐的版权锁定。

地址在: https://github.com/bin456789/Unblock163MusicClient-Xposed/releases

不太需要的 module

使用之后感觉不太需要的 module ,但是很强大的 module

XPrivacy

很复杂的但是很强大的权限管理,非常细节,但是对新手不好,使用非常复杂。对权限要求比较高的可以尝试一下。

链接: http://repo.xposed.info/module/biz.bokhorst.xprivacy

Flat Style Bar Indicators

高度自定义状态栏

Flat Style Colored Bars

和之前使用的 tint status bar 效果差不多,根据应用颜色改变状态栏和导航栏的颜色

Flat Style Colored Keyboard

自动改变键盘的颜色,我使用 TouchPal 所以也用不到。

Ifont

修改系统字体

Amplify

查看那些服务,禁用服务,省电

所有的 Xposed Module 都可以在 http://repo.xposed.info/module-overview 这里找到。当然 Xda 也有很多更新内容。


2016-07-15 Android , AndroidDev , Xposed , 整理合集

Telegram 技巧

感觉是时候写一篇 Telegram 的安利文了。Telegram 简单介绍就是一款 IM, 及时聊天工具。当然其实他远远的超越了一个IM,却依然保持了作为一个IM应有的速度和快捷。

对于基本功能,電腦玩物 Telegram 10技 已经将 Telegram 的主要功能及使用技巧说得非常明白了,就不再多说了。其中我最喜欢的几点:

  • 跨平台,这也正是我一直坚持 Hangout 的原因之一,我不喜欢整天抱着手机,我在电脑前的时间可比拿手机的时间长,我也不希望我坐下之后需要花很长的时间,打开一个客户端输入密码,然后还要花一段时间同步消息,关键有些客户端之间还不能同步消息记录。而 Telegram 给我的体验就和 Hangout 一致,多个平台,多个设备几乎能够在同一时间受到消息,我也能够在任何一个客户端回复,并且所有客户端的消息都是同步的。
  • Bot ,聊天机器人,很久之前的 Gtalk 也是支持机器人的,对那个自动翻译的机器人还是略有记忆,只是后来就没有后来了。而 Telegram 正是将聊天机器人这个命题重新书写了。开放的Bot系统,让 Telegram 成为了一个全能的平台,他是一个 IM ,他也可以是一个翻译工具,只需要一个翻译bot,他也可以是一个RSS阅读器,只需要一个RSS订阅bot,他也可以是一个Tinder,只需要一个约会bot…… Telegram 可以变成想要的任何工具,更不说官方集成的 @gif, @bold,@sticker 等等 bot
  • 开放,作为一个 IM,应该能够包容万千也能够开放的分享,这就提到了 Telegram 支持的媒体内容,文字,链接,音频,视频,gif,表情贴图,能够想到的几乎所有内容都可以兼容,甚至有人直接 Telegram 来当音乐播放器。并且 Telegram 在图片以及 Gif 分享的时候做了很多的优化处理,我在日常使用中几乎没有感受到任何卡顿,甚至流量的消耗也在我可接受的范围。

以下就是几个非常吸引人的Point:

Sticker

说了这么多,其实重点想要谈谈他的贴图以及 Bot 系统。首先是贴图,在刚上手 Telegram 的时候,我就被他丰富的表情震撼到了,就像他在 Blog 中所说, Telegram 觉得现存任何一个 IM 的表情系统设置都不是很理想,封闭,收费,并且糟糕透顶,于是 Telegram 大笔一挥自己做了一套系统,这是我迄今为止使用过最赞的表情贴图系统。尤其是在最近迷上 Pokemon Go 之后,更是找到了很多萌萌的小精灵。

pokemon sticker

从下面两个网站能够找到你想要的绝大多数表情,多到无法想象:

如果这两个网站都无法让你满足,Google,以及官方的 @sticker 机器人都可以帮你找到喜欢的表情。当然如果参与到更多的对话中,就能找到更多的表情。

Bots

几个神奇的内置 bot。在使用过程中竟然遇到了“向聊天群中添加 bot 这样的问题”,不过答案也很简单,拥有群管理权限时,直接添加成员,输入 @ + bot 的名字即可。

@gif

寻找 gif bot,使用最简单了,聊天时直接输入 @gif whatever I like , Telegram 会帮你搜索 whatever I like 字段的内容,点击分享即可。

@bold

Markdown bot 格式化输入

输入: @bold this is *bold* , this is _Italic_ , and this is `some code with *bold*` 。即可得到。

Telegram bold bot

@vote

@vote 用来创建投票

@like

用来创建 emoji based Like 投票。

@Stickerdownloadbot

发送给这个 bot 表情,他就自动将表情转换成 png 。

@sticker

inline bot,可以利用这个 bot 将普通的 emoji 转成其他表情贴图。

然后是一些其他好玩的第三方 bot:

  • @GroupButler_bot 用来管理群组的bot,可以设置 rules , 管理垃圾消息等等
  • @storebot 用来发现其他 bot 的 bot
  • @utubebot 下载 YouTube 视频
  • @Instasave_bot 用来下载 instagram 的 bot

SuperGroup

最为人熟知的就是 Telegram 的聊天群了,在一个没有社交关系基础的 Message App 上,最快吸引大众的就是这个 Group 了,通过群组来建立最初的社交基础。而最初的 Group 功能没有那么强大,管理员也只能是创建群的人,后来 Telegram 升级了一次 supergroup ,于是乎 supergroup 就已经拥有了 Message App 该有的功能,管理员权限,置顶消息,消息管理,搜索等等功能,甚至可以再群中添加 bot 来实现一些自定义的功能。

特别值得称道的便是,所有在 Telegram 中分享的内容都保存历史记录,虽然目前中文搜索有些问题,但是跨平台,消息同步已经让我无法离开这个应用了。

最后来记录一些有爱的群组:

Channel

频道的功能类似于微信的订阅号了,Channel 能实现一对多的通知,当然可以拿他当广播,但是和微信订阅号不同的时,Channel 是不能接收回复的。

一些值得关注一下的频道:

Client

最后推荐一个第三方 Android Client : Plus Messenger Link。 虽然官方的客户端已经足够完美了,但是因为加入的群比较多,经常无法找到对应的目的地,这个第三方客户端完美的解决了这个问题。


2016-07-14 Telegram , IM

Pokemon Go 简介

期待这款产品久矣,从开始玩 Ingress 开始,就一直想着这样一款基于地理现实的游戏将如何发展,至少从今天的火热程度看来相信 Niantic 之后的路不会那么坎坷。其实从逻辑上 Pokemon Go 只是在 Ingress 的逻辑上增加了一层皮肤,原先的 Portal 变成了 Pokestop,原先的 8 塔变成了道馆的等级,原先可以 Hack 的到装备,到 Pokemon Go 中依然可以利用 Spin Pokestop 来获取装备。区别在于 Ingress 一切都是从头开始,什么是 XM,什么是欧洲核子中心,这些都是 Niantic 自己的定义,而如今一切都变得熟悉,皮卡丘,精灵球,甚至野外遇见的每一只小精灵,卖萌的同时,一边也唤醒了很多人儿时的记忆。

这些天浸泡在 Telegram 学习了不少,写下作为笔记。今天刚刚知道原来皮卡丘是分雌雄的,尾巴心形开叉的是雌性,尾巴不分叉的是雄性。这让我自愧不如,当年其实也并没有很深入的追宠物小精灵,而如今一些记忆早就风干,唯一剩下的一点也就是主人公的名字—-小智,还有那只非常可爱的皮卡丘了。

以下是参考官方 Help 写下的一点笔记,不是全部内容的翻译,只是有些疑问我自己并不明白,于是就看了一眼 help。

写在开始之前

  • Respect the community.
  • Be respectful.
  • Respect privacy.
    • 尊重隐私
  • Adhere to the rules of the human world.
    • 不要进入未经允许不可进入的区域,即使那里出现了珍奇神奇宝贝
  • Avoid inappropriate content.
  • No cheating.
    • Methods of cheating, unfortunately, are limited only by cheaters’ imaginations, but include at a minimum the following: using modified or unofficial software; playing with multiple accounts (one account per player, please); sharing accounts; using tools or techniques to alter or falsify your location; or selling/trading accounts.
    • 不要使用修改过或者非官方的App;多账户;共享账户;使用工具伪造位置;售卖账户

Pokemon Go guidelines

说实话其实并不在乎Fake的玩家,游戏的目的是娱乐自己,而不影响别人,飞机确实影响了游戏性,官方也肯定会采取措施,只是长远来看,那些玩家并不会长期的玩这个游戏,只要我们坚持的更远我们就赢了。正如官方所说,作弊的方法 are limited only by imaginations ,自律自觉吧。

Getting Started

在开始的时候,有些内容其实完全不用讲,怎么移动小人(Avatar)?怎么获取物品?在游戏过程中有着非常详细的引导。也就不值得一说了。下面是一些游戏中会出现的术语(不包括全部,显而易见的就不列了):

Glossary

  • Candy 可以在捕获 Pokemon ,孵化宝宝,或者将 Pokemon 送给教授时获得。Candy 可以用来进化或者增强 Pokemon。
    • 抓怪获取 3 个
    • 送给教授 1 个
    • 孵蛋 10-20 个
  • Incense 蚊香,熏香?用来吸引 Pokemon。
  • Lure Module 短时间内可以吸引 Pokemon 到 Pokestop。
  • Defender Bonus 防御奖励,防御道馆可以按时领金币和 Stardust。
  • Potions 药水,恢复 Pokemon 的 HP。
  • Stardust 可以在抓取 Pokemon ,孵化宝宝 (Egg) ,或者在 Defender Bonus 的时候获取。
    • 抓怪 100
    • 孵蛋 600-800
    • 占领道馆 500/个 * 20h
  • Razz Berry 在遭遇野外 Pokemon 时,喂它们吃可以更容易抓取。

From: help

根据 Telegram 红队群@girigirigiri Eye 的详细介绍。

背包中物品

如下物品可能会出现:

  • Poke Ball, 精灵球,用来抓捕 Pokemon ,随着等级提升,有更加厉害的精灵球
  • Potions,药水,用来恢复受伤的 Pokemon
  • Berries,浆果,等级提升之后可能获得

背包有500容量。

pokeball

道馆与战斗

5级以上玩家可以解锁队伍,来大家一定要选红队。

Team Red

道馆

不多说了,道馆的等级由声望决定,等级越高能够容纳的 Pokemon 也越多。当同阵营玩家将Pokemon 放置道馆时,声望提升。地图上显示如下:

占有道馆

每个训练师只能在一个道馆放置一个 Pokemon ,并且这个 Pokemon 不能撤回自己的收集仓,直到它被踢出道馆,所以谨慎选择。

从对方阵营夺取道馆

通过打败对方阵营在道馆中的 Pokemon,在战斗中可以使用至多 6 个Pokemon。每一个被打败的 Pokemon 会导致对方阵营声望下降或者等级降低(reduce the Gym’s Prestige and potentially lowers the Gym’s level)。击败对方阵营的首领(Leader) 会显著降低对方道馆的声望(Prestige)。 当对方阵营的道馆声望为0时,可以从对方阵营夺取。道馆可以被多名训练师同时挑战。

在对战中可以有如下三种操作:

  • Fast Attack 快速攻击
    • 点击自己的 Pokemon 来进行快速攻击
    • 快速攻击补充特殊攻击条读数
  • Dodge attacks 闪躲攻击
    • 左右滑动来闪躲
  • Special Attack 特殊攻击
    • 当特殊攻击条读数充满时,长按 Pokemon 来进行特殊攻击
    • 特殊攻击可以导致较大伤害

每一只 Pokemon 都有 Hit Points (HP) ,在战斗中会消耗 HP,当 HP 消耗殆尽时 Pokemon 会晕倒,当所有的 Pokemon 都晕倒时,战斗结束。

From: pokemon help

增援队友道馆

每个道馆根据声望可以容纳1至10个 Pokemon。每个玩家只能放置一个 Pokemon。

从友方道馆训练 Pokemon

通过训练 Pokemon 来获取 XP 并且增加友方道馆的声望。通过友方道馆训练,训练越多,友方道馆声望越高。 友方道馆训练Pokemon 和战斗类似,只是在战斗失败时,Pokemon 不会晕倒(faint)。而是以 1 HP 返回到收集仓。

CP and HP

CP 是 Combat Power 缩写, HP 是 Hit Points 缩写。
所有 Pokemon 在捕获时有满的 HP, 但是在战斗中会消耗 HP。
每一个 Pokemon 在捕获时都有不同的 CP,CP 值决定神奇宝贝在战斗中的表现。
当训练师获取更多 XP,等级提升时,遇到的 Pokemon 能够捕获的 Pokemon CP 值也会相应增高。但是有一些 Pokemon 天然的低 CP。

From:Help

Q & A

什么是 Pokemon Go

Pokemon Go 是一款基于地理现实的手机游戏,由 Niantic,任天堂,The Pokemon Company合作开发发行。

参考:官网

等级的上限?

目前为止并没有看见官方文档有任何描述,不过有如下图:

level

如何快速的定位周围的 Pokemon?

附近的Pokemon 有图鉴可以看到,爪子的数量表示远近。

Pokemon nearby

关于 Pokemon 的稀有程度?

有一幅图完美的解决:

Pokemon where can see

能够孵化的小精灵?

如图:

孵化的小精灵

More

如果当你真的看到这里的时候,想要了解更多可以加入 Pokemon Go 红队的 Telegram

当然如果依然有不知道的内容,官方文档以及Google都会给你完整的答案。如果真的互联网上不存在你所讨论的问题,那去群里确实也只能是最后一个方法了。

更新一点声明,文中所用图片,部分来自 reddit , 部分来自 Telegram , 有一些来源我确实已经无法找到,如果有任何侵权行为,请联系我。


2016-07-10 产品体验 , Ingress , AR , Game , Niantic , Pokemon , PokemonGo

Evernote 代替品

Evernote 最近一次的 Policy 更新1 真的太人人失望啦。如果说增加功能,增加收费,我完全不反对,我反对的是将现有的免费功能加入到收费功能中。这是对自己曾经的承诺公然的放弃。

一些代替品:

OneNote, Google Keep, WizNote, Simplenote, youdao, Laverna

http://alternativeto.net/software/evernote/

我的简单需求:

  1. 多平台同步(Mac, Windows, Linux) + web + Android online and offline
  2. 好用的 clip Chrome 插件
  3. 最好支持多人编辑
  4. 支持笔记分享
  5. 支持代码高亮,其实这一点 Evernote 也做不到

2017-02-13

WizNote 推出来一个比较好玩的功能,新建一个可以分享的群组之后可以想公众号那样分享内容,但是编辑可简单多了,只要编辑 WizNote 中的内容,就自动同步分享。可以参看如下:

https://note.wiz.cn/pages/manage/biz/payRead.html?kb=8b40bf53-6ce7-4f5e-bbfc-99b2628340f3

2017-01-05

在近一个月的寻觅中,依然没有找到好的代替品,于是在年末的时候买了一年的 WizNote 会员,看一年的使用再行选择。

2016-12-09 重要更新

WizNote 发布了服务规则调整, 有如晴天霹雳。

  • 免费个人用户只提供 100 天试用,到期之后新建,修改笔记无法上传服务器
  • 个人 VIP 下调为 6元/月, 60元/年

这则消息和 Evernote 反悔,任意更改服务条款的性质是一样的。我在此也更新,不在推荐 WizNote 服务,我会把文末的邀请链接删除,对于这样的更改其实 WizNote 一点优势也没有了。

我觉得一家公司重的信誉,也就是说到能做到的信誉,服务可靠,提供稳定的功能更新,而不是出尔反尔的随意更改之前制定的策略。这一点 Evernote 和 WizNote 给我的感觉已经差不多了。虽然现在各种云服务各种收紧,不仅是国内的网盘业务,而且国外的Dropbox, Evernote 等等服务都面临着盈利困境,多年的市场经营虽然圈住不少用户,但却仍然无法变现。

现在回头想一想,一款产品在最初期制定规划的时候是需要很慎重的。说着那些永久免费的,多少坚持到现在了,说着无限空间的,大多已经消失。经历了 Evernote , WizNote 事件,还有之前 网易云音乐歌单丢失事件,多少让我对云服务产生了一些畏惧。目前正准备将云存储的内容,本地备份一份。

发生了这些事情之后,在我的需求列表中可能还需要添加一项:

  • 服务的数据本地可见,并且能够提供导出服务,比如 Dropbox 至少有一个本地备份,比如 WizNote 本地是文件存储的

其实倒不是我不愿意付费,WizNote 的这次变更也挺有诚意,在知道消息的第一时间我是准备购买一年两年的,只是现在想想始终觉得不对,你曾经承诺过可以每个月提供那么多流量,对于我这样的用户这本已足够,而仅仅因条款的强行改变而要求我付费,这和一开始就要求付费是完全不同的两个概念。我不反对付费,Play Store 上面我也买了差不多 100 美元的应用了,好用的应用自然有购买支持的理由。 WizNote 不从功能更新上吸引用户,而仅仅是改变原来的规则这是我要谴责的地方,如果不能够提供之前承诺的服务,在推广或者宣传的时候就不应该叫唤得那么响亮,到头来扇了自己一个巴掌。

或许在调整我的使用习惯之后,会支持下 WizNote,毕竟无法离开他 Linux 下的客户端。而如果真如他所宣称的那样能够提高检索效率,说不定买个两三年也说不定。

Wiznote 唯一使用他的理由就是他的跨平台,Linux 支持比 Evernote 好, Evernote 干脆就不支持 Linux,当然他们客服在也官网中直接说明“不支持Linux,在可见未来也不会支持”。因此在使用过一段时间之后我也就不在使用 Evernote,并转向了 Wiznote。然而这一次 WizNote 这一次策略变更也让我这样一个普通用户无法忍受。最开始知道这个消息的时候还想着年底买一个会员,支持一下这个产品,然而在逛论坛看用户的反馈,其中一条直接让我放弃了购买的打算。

wiznote purchase

对于网站任意改变政策本就是抱有抵制态度,而最近各种网盘纷纷“倒闭”,那些宣称的“无线空间,永久免费”,还在耳边回荡,而就在一夜之间就宣告倒闭了,幸而那些网盘并没有深入使用,数据也并不是很多。而对于WizNote 使用时间几乎是每天,而他宣称的免费一个月500M的流量对于我只记录些笔记已经完全足够。而这一次强硬的收费,明显就是驱逐免费用户,并且毫无信用的将过去的诺言打破。而对于未来也并没有太多承诺,甚至在用户探讨未来收费策略时用好不负责任的口吻对待用户,很难想象其对待用户数据的态度,也坚决了迁移出 WizNote 的决心。而公司内部对定价策略态度暧昧,即使有耐心支持他们,也很难保证他们之后的策略,因为此次的变化带来的一个显著的印象就是,如果不付费,这个软件几乎是不可用状态,停止同步支持几乎就是无用状态。

OneNote

很早就在用了,只是当时并不支持多端同步,所以在 Android 崛起之后就渐渐的转到了 Evernote。OneNote 从各个方面来看都完全符合我的要求,并且最近的更新 Chrome 插件也做的非常不错。只是 Android 端实在太烂,让我提不起兴趣的还有微软的名号,以及缺乏 Linux 客户端。

Google Keep

在我看来只适合轻量级的笔记,网页摘录也做的不是很好。

WizNote

其实之前也一直在使用中,但由于之前 Evernote Chrome Clipper 做的实在太棒了,所以一直没转过来,为知笔记还是很多人推荐的,容量同步 500M/月, 30M / 单笔记,相比 Evernote 已经很不错了。笔记分享这款做的并不是很好,但觉得可以一试。并且 WizNote 支持 markdown 这一条还是非常赞的。

Simplenote

虽然很多人推荐,但是没有 Chrome Clipper,并且注册账号登陆看了一眼 Web 版,功能比较简单,并且就是功能比不上 Evernote 的复刻版,并不是太想使用它,哪一天它也变成 Evernote 怎么办

youdao

有道云笔记在国内还是很多人使用的,但是登陆一看总共3G 空间,顿时让我不想用了。并且有道并不提供导出方式,对于一个在国内的云服务不提供是很正常的,然而毕竟数据在自己手上还是比较安全。

Laverna

这次寻找的过程最让我感到意外的就是这个了,开源,Linux端,集成 Dropbox 同步,代码高亮, Markdown 格式,简直就是完美的代替品,它的 Github 主页上就光明正大的写着 Evernote alternative。但是在我看来它已经完全超越了 Evernote,除了没有一个公司去运营它,它在功能上已经完全超越了 Evernote。

https://laverna.cc

经过以上的总结,接下来在 WizNote 以及 Laverna 中尝试选择一个当笔记同步使。为知笔记的Chrome 扩展 ,相对 Evernote 弱了一些,但是也非常不错。 WizNote 当然各个客户端都有,最棒的是 Linux 客户端也有。多人编辑和笔记分享这一点 WizNote 非 VIP 用户无法使用,但是单纯的当做笔记来用已经完全足够了。 最后代码高亮很早就已经支持了的。

剩下的用 Evernote 来共享笔记,和别人协作好了。

两个月之后更新

现在已经稳定使用 WizNote 了, Evernote 中的数据基本导出到 WizNote,而目前 WizNote 使用过程中基本没有产生什么问题。现在就做一个 WizNote Review。官方的宣传中突出了如下特点:

多级目录、多级标签、Markdown、无限存贮空间

而这4点确实很吸引人,多级目录和标签可以让文件夹更加整洁,原生支持 Markdown 让写作更加便捷,无限空间就让人不用担心。

对于 WizNote 免费用户,单月上传流量 300M,单篇文章限制 30M,附件限制 30M,这个大小和容量,已经完全满足我日常使用。并且在这几个月的使用过程中,除了一次偶然的崩溃,同步,添加附件,甚至保存微信文章,保存邮件文章,都工作正常。

Linux Mint 安装

参考 GitHub 或者 官网,都有详细的安装说明。基本上使用如下命令即可。

$ sudo add-apt-repository ppa:wiznote-team
$ sudo apt-get update
$ sudo apt-get install wiznote

Evennote 迁移 WizNote

WizNote 菜单中能很方便的导入 Evernote 中导出的文件。就不展开细讲了。

扩展工具

邀请

查看上12-09更新,删除邀请链接,不再推荐 WizNote。


2016-07-01 Evernote

Android lib Timber

今天查询Android在release下不显示Log信息,偶然间接触到 Timber 这个库。 Android 原生提供了很多调试 Log 的方法,但是如果想要在release情况下禁用所有调试信息,除非在原生 Log 外再嵌套实现一层自己的方法,或者使用 ProGuard 。Android 本身没有提供一种简单的方式实现,幸而有大神提供了这样的一个库。

Android 原生 Logcat 分成 v/d/i/w/e/wtf . 官方推荐的最佳实践就是定义一个 TAG 变量:

private static final String TAG = "EV_TAG_MyActivity";

然后过滤关键字就能找到对应的 Log 信息。大部分的情况下使用 Log.d 即可,但是各个方法都有其适用的情况:

  • Log.e : 错误输出,用在 catch 语句下,你知道有可能有错误发生,因此打印出 Error
  • Log.w : 警告,用来输出不可判断的错误出现的情况,如果出现了,需要查看
  • Log.i : 信息,用来打印有用的信息,比如连接服务器成功,一般用来报告成功事件
  • Log.d: 用来调试,只在 debug 下出现
  • Log.v:各种小调试信息
  • Log.wtf: 非常严重的错误发生时打 Log

设置Timber

去GitHub 找项目主页 Timber ,在 build.gradle 中加入:

compile 'com.jakewharton.timber:timber:4.1.2'

在 Application 下初始化 Timber

public class ExampleApp extends Application {
  @Override public void onCreate() {
    super.onCreate();

    if (BuildConfig.DEBUG) {
      Timber.plant(new DebugTree());
    } else {
      Timber.plant(new ReleaseTree());
    }
  }
}

调用 Timber.plant(new DebugTree()) 之后,再使用 Timber 的静态方法,则使用了 DebugTree 中设定。DebugTree 是 Timber 库中默认实现的。

Timber.plant(new Timber.DebugTree(){
@Override
protected String createStackElementTag(StackTraceElement element) {
  return super.createStackElementTag(element) + ":" + element.getLineNumber();
}
});

重新实现 createStackElementTag 方法,可以在 Debug 下打印出 Log 所在的行号。

using Timber

同Android提供的 Log 方法类似 Timber 也有 i/v/d/w/e/wtf 这些方法。 Timber 默认 TAG为文件名。当然可以使用 Timber.tag() 方法来设置一次性 tag 。

Timber.tag("LifeCycles");
Timber.d("Activity Created");

官方的使用教程其实只有两条:

  1. 在 application class 下 plant Tree
  2. 然后调用 Timber 的静态方法即可。

但是 Timber 提供了更多的自定义。可以通过继承 Timber.Tree 来实现。

Timber 可以种树也可以移除一棵树,也可以移走全部的树:

  • plant(Tree)
  • uproot(Tree)
  • uprootAll()

Timber Tree

先看看 Timber Tree 实现,这个类是一个抽象类,主要实现管理 TAG,并且提供各个 Log 方法的实现,类中有一个抽象方法

/**
 * Write a log message to its destination. Called for all level-specific methods by default.
 *
 * @param priority Log level. See {@link Log} for constants.
 * @param tag Explicit or inferred tag. May be {@code null}.
 * @param message Formatted log message. May be {@code null}, but then {@code t} will not be.
 * @param t Accompanying exceptions. May be {@code null}, but then {@code message} will not be.
 */
protected abstract void log(int priority, String tag, String message, Throwable t);

DebugTree 实现了 Timber.Tree , 和 log(int priority, String tag, String message, Throwable t)方法。

@Override protected void log(int priority, String tag, String message, Throwable t) {
  if (message.length() < MAX_LOG_LENGTH) {
    if (priority == Log.ASSERT) {
      Log.wtf(tag, message);
    } else {
      Log.println(priority, tag, message);
    }
    return;
  }

  // Split by line, then ensure each line can fit into Log's maximum length.
  for (int i = 0, length = message.length(); i < length; i++) {
    int newline = message.indexOf('\n', i);
    newline = newline != -1 ? newline : length;
    do {
      int end = Math.min(newline, i + MAX_LOG_LENGTH);
      String part = message.substring(i, end);
      if (priority == Log.ASSERT) {
        Log.wtf(tag, part);
      } else {
        Log.println(priority, tag, part);
      }
      i = end;
    } while (i < newline);
  }
}

基本上能看到是为了避免打印长度超出 Log 的最大长度而做的设置。

release logging

在给出来的 Demo 中,JakeWharton 实现了一个发布版本的 Tree,

private static class CrashReportingTree extends Timber.Tree {
@Override protected void log(int priority, String tag, String message, Throwable t) {
  if (priority == Log.VERBOSE || priority == Log.DEBUG) {
    return;
  }

  FakeCrashLibrary.log(priority, tag, message);

  if (t != null) {
    if (priority == Log.ERROR) {
      FakeCrashLibrary.logError(t);
    } else if (priority == Log.WARN) {
      FakeCrashLibrary.logWarning(t);
    }
  }
}
}

通过优先级,在 release 下 VERBOSE 和 DEBUG 就不产生 Log 信息了。而 Error 和 WARN 就交给了 FakeCrashLibrary 去处理了。

更多的方法可以参考 文档

reference


2016-06-24 Android , AndroidDev , 学习

Google+

最近文章

  • Maven 介绍 Maven 是一个项目管理工具,主要用于项目构建,依赖管理,项目信息管理。自动化构建过程,从清理、编译、测试和生成报告、再到打包和部署。
  • IntelliJ IDEA 中使用 Resin 调试 平时开发环境使用的是 jetty,而 Java Web 有一个更好更快的服务器 Resin,这篇文章就来说一下什么是 Resin,以及在 Debug 中如何使用。
  • 使用Shell命令来对 Unix 时间戳和日期进行转换 date 命令 在程序中经常要使用到 Unix timestamp 和日期的转换,通常情况下都是Google一个时间戳转换的网页在进行转换,其实 Linux 命令中就有能够快速实现转换的命令。主要都是集中在 date 这个命令。date 命令主要用于显示或设定系统时间和日期。
  • Mastering the Vim 我已经用了很长一段时间 Vim 了,但是 Vim 最可贵之处便在于你永远达不到 Vim 的天花板,在使用的过程中我永远会发现操作 Vim 的其他便捷方法。最近看了一个关于 Vim 的讲座 ,革新我对 Vim 命令的认识。可以说掌握这样的一个世界观可以对 Vim 的操作上到另外一个层次。下面就总结一下这个视频中的精髓内容。
  • Spring MVC 应用处理 CORS 什么是跨域或者说什么是CORS(Cross-origin resource sharing),中文叫”跨域资源共享”。在了解 CORS 之前首先要知道“同源策略”,出于安全考虑,浏览器会限制Ajax中发起的跨站请求。比如,使用 XMLHttpRequest 对象发起 HTTP 请求就必须遵守同源策略(same-origin policy),”同源策略“是浏览器安全的基石。具体而言,Web 应用程序能且只能使用 XMLHttpRequest 对象向其加载的源域名发起 HTTP 请求,而不能向任何其它域名发起请求。阮一峰写的一篇关于 CORS 的文章 介绍得非常详细,这里主要记录一下重点以及 Spring MVC 中如何处理 CORS。