Android高性能编码 - 第七篇 Efficient Java与Android Lint

7.1 Efficient Java中的性能要点

Effective Java是一本被广泛认可的著作,它指明了在写Java代码时兼顾可维护性与效率的方式。Android也是使用Java来开发的,书中的部分建议可能不适用,因为并非所有Java特性都有针对Android优化(比如说枚举,序列化等等),或者是因为移动设备的局限(例如Dalvik/ART)。不管怎样,书中的大部分规范是稍微修改下甚至不修改就可以直接用的,以便构建更鲁棒,简洁且更可维护的代码库。

7.1.1 强制不可实例化

只包含静态方法的工具类,我们不希望它被外部通过关键字new来创建,那么强制让它的构造方法私有。这对单例的工具类也是一样的道理。

class MovieUtils {
    private MovieUtils() {}

    static String titleAndYear(Movie movie) {
        // ...
    }
}

7.1.2 静态工厂方法

尽可能不要使用 new 关键字和构造方法创建对象,而应当使用静态工厂方法(和私有构造方法)。这些工厂方法具有名字,不需要每次返回一个新的对象实例,它们可以依据需求返回不同的子类型对象。

其中Android Studio自动生成的Fragment模板就是一个典型。

 * Use this factory method to create a new instance of
 * this fragment using the provided parameters.
 *
 * @param param1 Parameter 1.
 * @param param2 Parameter 2.
 * @return A new instance of fragment MainFragment.
 */
public static MainFragment newInstance(String param1, String param2) {
    MainFragment fragment = new MainFragment();
    Bundle args = new Bundle();
    args.putString(ARG_PARAM1, param1);
    args.putString(ARG_PARAM2, param2);
    fragment.setArguments(args);
    return fragment;
}

7.1.3 创建者模式

当对象的构造方法参数不小于3个时,可以考虑创建者模式。这可能需要更多行的代码,但拓展性和可读性会很好。如果使用AS开发,创建一个Builder模式的实体类,可以采用InnerBuilder插件来减少编码工作。

class Movie {
    static Builder newBuilder() {
        return new Builder();
    }
    static class Builder {
        String title;
        Builder withTitle(String title) {
            this.title = title;
            return this;
        }
        Movie build() {
            return new Movie(title);
        }
    }

    private Movie(String title) {
        // ...
    }
}
    
    // Use like this:
    Movie matrix = Movie.newBuilder().withTitle("The Matrix").build();

7.1.4 静态成员类

如果定义了一个不依赖外部类的内部类,不要忘记将其定义为静态的。否则将会导致每一个内部类对象都会持有对外部类的引用。

class Outter {
    //...
    static class Inner {
        //...
    }
}

7.1.5 泛型 (几乎) 无处不在

Java 的泛型限定提供了类型检查,尽量避免使用无类型或 Object 类型。泛型机制,大多数情况下保障了编译时的类型检查,编译期若发生类型转换警告,必须检查和修正,否则运行时强转就可能是ClassCastException。

// 不要这样做
List movies = new ArrayList();
movies.add("Hello!");
//...
String movie = (String) movies.get(0);

// 这样做
List<String> movies = new ArrayList<>();
movies.add("Hello!");
//...
String movie = movies.get(0);

不要忘记你能在方法中对参数和返回值使用泛型。

// 不要这样做
List sort(List input) {
    //...
}

// 这样做
<T> List<T> sort(List<T> input) {
    //...
}

7.1.6 避免返回空对象

当方法的返回类型为list/collecion时,返回空值时要避免返回null。返回一个空的集合类型,这会使得接口简单易用(没有必要写文档来声明方法返回值为null),并且避免空指针异常。

示例,使用Collection的.emptyList()接口返回集合的空值,作为方法返回值。

public List<Movie> latestMovies() {
    boolean empty = db.query().isEmpty();
    if (!empty) {
        return db.query();
    }
    return Collections.emptyList();
}

7.1.7 检查Bean类的toString方法使用StringBuilder拼接

我们习惯性会打印Bean的数据进行调试,但大多数Bean实体类在IDE中自动生成的toString方法,都使用了“+”来拼接字段,性能较差,这种大量字符串拼接场景,要求必须采用StringBuilder进行拼接。

7.1.8 尽量使用移位来代替'a/b',‘a*b’的操作

乘除运算是一个代价很高的操作,使用移位的操作将会更快和更有效。

int num = a / 4;

int num = a * 4;

相应的修改为:

int num = a >> 2;

int num = a << 2;

7.1.9 尽量减小synchronize的范围

实现同步是要很大的系统开销作为代价的,甚至可能造成死锁,所以尽量避免无谓的同步控制。实现方式主要有synchronize方法和synchronize代码块。

synchronize方法被调用时,直接会把当前对象锁 了,在方法执行完之前其他线程无法调用当前对象的其他方法,所以synchronize的方法尽量小。

在考虑性能方面,最好使用同步块来减少锁定范围提高并发效率,同时考虑Java5以后的推荐的lock锁方式,其提供了更优的特性,包括:

l  可以主动控制锁的释放,避免IO阻塞等无谓的等待;

l  提供可重入的读操作,使得在复杂读写的情况下,并发读成为更简单的实现。

示例:

public class Test {
    private Lock lock = new ReentrantLock();    //declare lock
    final Test test = new Test();

    public void test () {
        new Thread(){
            public void run () {
                test.insert(Thread.currentThread());
            }
        }.start();

        new Thread() {
            public void run() {
                test.insert(Thread.currentThread());
            }
        }.start();
    }

    public void insert(Thread thread) {
        lock.lock();
        try {
            System.out.println(thread.getName()+"得到了锁");
            //...
        } catch (Exception e) {
            // TODO: handle exception
        }finally {
            System.out.println(thread.getName()+"释放了锁");
            lock.unlock();
        }
    }
}

7.1.10 慎用异常

当创建一个异常时,需要收集一个栈跟踪(stack track),这个栈跟踪用于描述异常是在何处创建的。构建这些栈跟踪时需要为运行时栈做一份快照,正是这一部分开销很大。栈跟踪不只包含运行时栈中的一两个元素,而是包含这个栈中的每一个元素。

因此,尽管捕获异常在IDE中实现起来已经比较简易,但是考虑到创建异常的性能开销,避免随意抛出异常,仅在必要提示异常的场景中使用异常,同时考虑接下来的一条异常使用规范。

7.1.11 底层接口主动抛出可恢复异常

Exception类本身,以及Exception的子类中除了"运行时异常"之外的其它子类都属于被检查异常。

Java编译器会对"被检查的异常"进行检查,而对"运行时异常"不会检查。也就是说,对于被检查的异常,要么通过throws进行声明抛出,要么通过try-catch进行捕获处理,否则不能通过编译。而对于运行时异常,倘若既"没有通过throws声明抛出它",也"没有用try-catch语句捕获它",还是会编译通过。

这里主要是针对数据层接口的异常设计而言,如果有可恢复的条件异常,通过定义一个可恢复的条件异常及早进行抛出,由上层调用者进行捕获。

如此,使上层调用者可以及早关注到该处逻辑可能会有什么样的异常。

7.2 遵守Android Lint性能规范

Android Lint内置了很多lint规则,到现在为止是220项检查,总共可以分为以下几类:

l  Correctness 正确性

l  Security 安全性

l  Performance 性能

l  Usability 可用性

l  Accessibility 可访问性

l  Internationalization 国际化

以上各项通过IDE自带插件等方式,可以自动化检查,一般要求遵守其所有规范,如有特别例外项,可通过修改检查项进行排除。

具体的检查细节项解释,参考官方文档:Android Lint Checks

猜你喜欢

转载自blog.csdn.net/qq_16206535/article/details/80217994