Android学习笔记---重新学习自定义View#01

最近发现自己对Android的学习只在表面,并没有深入的理解,我不喜欢这种感觉,而且没有自己的理解,学习到的内容也很难为我所用.

所以从本次开始,我要写点自己理解的东西,但要对知识有自己的理解,那就必须深入了解它的原理.而我觉得Android的自定义View是一个很好的入口.

学会如何自定义View,能够了解Android系统中View使如何创建和维护的.这有助于我们学习Android的View的基本机制,也能解决我们日常开发的需求.

接下来我们一起来对自定义View中的各个部分做详细的研究,现在我们先从View的构造函数入手.

自定义View的构造函数

自定义View最基础的方式就是创建一个继承于View或其子类的类,并且最少重写父类的一个构造函数.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
/**
*/

public class MyView extends View {
/**
* 在代码中使用new关键字创建View时会调用
* @param context
*/

public MyView(Context context) {
super(context);
}

/**
* 在layout文件声明View时会调用,只有实现了这个构造函数才能在布局文件中声明此自定义View
* @param context
* @param attrs 存有View在xml布局文件中的自定义的属性
*/

public MyView(Context context, AttributeSet attrs) {
super(context, attrs);
}

/**
* 与第二个构造函数不同的地方在于,此构造函数可以指定View的默认样式
* @param context
* @param attrs
* @param defStyleAttr 是当前theme中包含的指向View的样式资源的属性(0代表此参数无效)
*/

public MyView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
}

Read More

Effective Java学习笔记 --- 对所有对象通用的方法

重写equals方法时遵守通用约定

通常在下面的几种情况下都没有必要重写equals()方法:

  • 类的每个实例意义上都是唯一
  • 不关心类是否提供逻辑相等的测试功能
  • 父类已经重写了equals()方法,并且该方法对子类也适用
  • 类为私有时,equals()方法不会被调用

当类需要拥有逻辑相等的概念且父类没有重写equals()方法时,我们需要进行equals()方法的重写.在重写时要遵循下面的规则:

  • 自反性,即对象必须等于其自身.
  • 对称性,对于两个对象的比较是否相等必须保持一致.
  • 传递性,若x.equals(y)为true,y.equals(z)为true,那么x.equals(z)必须为true.
  • 一致性,比较操作内所用的数据没有修改时,多次的调用结果必须一致.

实现高质量equals()方法的技巧:

  1. 使用==操作符检查 “参数是否为这个对象的引用” .如果是,返回true.若比较操作代价较大,这种优化可提高性能.
  2. 使用instanceof操作符检查 “参数是否为正确的类型” .如果不是,返回false.这里 “正确的类型” 通常指的是equals()方法所在的类,有些情况下指的是所在类实现的接口类型.
  3. 把参数转换为正确的类型.上一条已确保能转换成功.
  4. 对于该类的每个关键域,检查参数中的域是否与对象对应的域匹配.若测试全部成功,返回true,否则返回false.
  5. 使用单元测试进行检验

最后需要注意的几点:

  • 重写equals()时总要重写hashCode()
  • 不要企图让equals()方法过于智能
  • 不要将equals声明中的Object对象替换为其他的类型

    Read More

Effective Java学习笔记 --- 创建与销毁对象03

消除过期的对象引用

由于Java语言具有垃圾回收机制,可能很多人都不会再去关心内存管理的事情.但是有的情况下我们的代码中回无意的存在着内存泄漏的问题.下面的代码是一个例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class Stack{
private Object[] elements;
private int size = 0;
private static final int DEAFULT_INITIAL_CAPACITY = 16;

public Stack(){
elements = new Object[DEAFULT_INITIAL_CAPACITY];
}
public void push(Object e){
ensureCapacity();
elements[size++] = e;
}
public Object pop(){
if(size == 0)
throw new EmptyStackException();
return elements[--size];
}
private void ensureCapacity(){
if(elements.length == size)
elements = Arrays.copyOf(elements,2*size+1);
}
}

上面的代码看似没有任何问题,但当栈先增长后收缩时,从栈中弹出的对象不会被系统回收,即使它们不再被引用.因为栈的内部会维护这些对象的过期引用,即被弹出的对象虽然不在栈内,但栈的内部(elements中下标大于size的部分)仍然持有着他们的引用,只是用户不会访问这些对象而已.
当这些对象被无意识的保留起来时,垃圾回收机制不仅不会处理这些对象,而且也不会处理被这些对象所引用的其他所有对象.这样一来会对性能造成很大的影响.
处理这类问题其实也很简单,只要对象引用过期,就清空这些引用即可.对于上面的例子,我们可以在出栈方法进行下面的改进.

1
2
3
4
5
6
7
public Object pop(){
if(size == 0)
throw new EmptyStackException();
Object result = elements[--size];
elements[size] = null; // 清除过期引用
return result;
}

那么问题来了,我们是否对于每个对象的引用,都要关心其是否已经过期?这肯定是不需要的,而且这也不是我们希望的.只有当类是自己管理内存,我们才应该注意内存泄漏问题.
内存泄漏还有另外的两个常见的情景,就是缓存以及监听器和其他回调.由于内存泄漏很难被发现,所以我们从编码的经验中能获取最有效的解决方法.

避免使用finalizer

finalize方法通常不可预测,一般情况下不推荐使用.因为finalize方法会延迟执行,而延迟的时间是任意的,这会让程序的运行变得不可预测.

Effective Java学习笔记 --- 创建与销毁对象02

使用枚举类型强化Singleton属性

Singleton指的是仅被实例化一次的类,即我们常说的单例.我们可以通过枚举类型来实现单例.

1
2
3
4
public enum Elvis{
INSTANCE;
public void leaveTheBuilding(){......)
}

这种方法是最简单的方法,而且它提供了序列化的机制,绝对防止多次实例化,避免通过反射利用无参构造器获取实例,这种方法可能是目前实现单例的最佳方法.

通过私有构造器强化不可实例化

有些类并不希望被实例化,例如Java内置的工具类,它们只提供一些静态的工具方法.实例化并无任何的意义,当这种类没有显示的构造器时,编译器会自动提供一个公有的无参构造器,这让就会有被实例化的危险了.为了保证不被实例化,可以提供一个私有的构造器,这让就可以避免外部实例化该类.

1
2
3
4
5
6
public class UtilityClass{
private UtilityClass(){
throw new AssertionError();
}
// other method
}

上面的代码中在私有构造器中抛出的AssertionError可以避免在类的内部被实例化.这种方法的缺点就是无法子类化.

Read More

Effective Java学习笔记 --- 创建与销毁对象01

最近在阅读学习一本关于Java的书籍 — Effective Java.这本书讲的是在如何高效的使用Java,让我们的代码更加的高质量和高效率.
这本书可以说是Java进阶的必备书籍.对于想在Java方面有所提高的同学,这本书我觉得是个不错的选择.通过本系列的学习笔记,我会把在我阅读学习时获得的感悟和知识记录下来.同时这个也是相当于我的读书笔记.

考虑使用静态工厂方法代替构造器

首先书本的第一个主题是如何使用Java高效的创建和销毁对象.书本的第一条技巧就是考虑使用静态工厂方法代替构造器.
通常如果想获得一个类的实例,我们最直接的方法就是利用该类的共有构造器来实例化一个对象.还有一种办法就是在类中提供一个公有的静态工厂方法用于返回类的实例.下面是一个Java中Boolean类内置的一个例子:

1
2
3
public static Boolean valueOf(boolean b){
return b ? Boolean.TRUE : Boolean.FALSE;
}

但是这里的静态工厂方法与设计模式中的工厂方法是不同的.
采用静态工厂方法来创建类的实例相对采用简单的构造器有下面的几个优势:

  1. 它们有特定的名称.普通的构造器只能与类同名,但静态工厂方法可以有自己的名称,易于代码的阅读和使用.
  2. 避免重复创建相同的实例.当我们需要相同的对象是,可以使用预先创建好的实例,或使用缓存起来的实例.而每次调用构造器都会创建一个对象.
  3. 可以返回类的任何子类对象.这条优势我也不太明白,所以我先在此做个记号.后面若有所感悟会回来补充的.

静态工厂方法也有它的缺点:

  1. 类必须含有公有或受保护的构造器,否则无法子类化.
  2. 它与其他的静态方法没有区别.因此我们需要正确的对静态工厂方法进行命名.
    下面是一些惯用的命名:
    • valueOf() 类型转换
    • getInstance() 通过不同的参数返回不同的实例
    • newInstance() 与getInstance()类似,但保证每次返回的实例都是唯一的

Read More