Blog


  • 首页

  • 关于

  • 标签

  • 分类

  • 归档

  • 搜索

RxJava2操作符汇总

发表于 2019-08-06 | 分类于 Android知识点

RxJava是一个在Java VM上使用可观测的序列来组成异步的、基于事件的程序的库(a library for composing asynchronous and event-based programs using observable sequences for the Java VM)。

RxJava能帮助我们在实现异步执行的前提下保持代码的清晰。它的原理就是创建一个Observable来完成异步任务,组合使用各种不同的链式操作,来实现各种复杂的操作,最终将任务的执行结果发射给Observer进行处理。

1、简介

RxJava 有以下三个基本的元素:
1.被观察者(Observable)
2.观察者(Observer)
3.订阅(subscribe)

1.创建被观察者:

Observable observable = Observable.create(new ObservableOnSubscribe<Integer>() {
    @Override
    public void subscribe(ObservableEmitter<Integer> e) throws Exception {
        Log.d(TAG, "=========================currentThread name: " + Thread.currentThread().getName());
        e.onNext(1);
        e.onNext(2);
        e.onNext(3);
        e.onComplete();
    }
});

2.创建观察者:

Observer observer = new Observer<Integer>() {
    @Override
    public void onSubscribe(Disposable d) {
        Log.d(TAG, "======================onSubscribe");
    }

    @Override
    public void onNext(Integer integer) {
        Log.d(TAG, "======================onNext " + integer);
    }

    @Override
    public void onError(Throwable e) {
        Log.d(TAG, "======================onError");
    }

    @Override
    public void onComplete() {
        Log.d(TAG, "======================onComplete");
    }
};

3.订阅

observable.subscribe(observer);

也可以链式调用

RxJava 中的调度器

调度器 作用
Schedulers.computation( ) 用于使用计算任务,如事件循环和回调处理
Schedulers.immediate( ) 当前线程
Schedulers.io( ) 用于 IO 密集型任务,如果异步阻塞 IO 操作
Schedulers.newThread( ) 创建一个新的线程
AndroidSchedulers.mainThread() Android 的 UI 线程,用于操作 UI

内存泄漏

  • 每次掉用过onError或onComplete其中一个方法后,就会掉用dispose()方法解除订阅
  • CompositeDisposable可以容纳多个disposable,每当我们得到一个Disposable时就调用CompositeDisposable.add()将它添加到容器中, 在退出的时候, 调用CompositeDisposable.clear() 即可快速解除所有添加的Disposable类.

2、创建操作符

create()

public static <T> Observable<T> create(ObservableOnSubscribe<T> source)

作用:创建一个被观察者

just()

public static <T> Observable<T> just(T item) 
......
public static <T> Observable<T> just(T item1, T item2, T item3, T item4, T item5, T item6, T item7, T item8, T item9, T item10)

作用:创建一个被观察者,并发送事件,发送的事件不可以超过10个以上。

示例如下:

Observable.just(1, 2, 3)
.subscribe(new Observer < Integer > () {
    @Override
    public void onSubscribe(Disposable d) {
        Log.d(TAG, "=================onSubscribe");
    }

    @Override
    public void onNext(Integer integer) {
        Log.d(TAG, "=================onNext " + integer);
    }

    @Override
    public void onError(Throwable e) {
        Log.d(TAG, "=================onError ");
    }

    @Override
    public void onComplete() {
        Log.d(TAG, "=================onComplete ");
    }
});

看看打印结果:
=================onSubscribe
=================onNext 1
=================onNext 2
=================onNext 3
=================onComplete 

fromArray()

public static <T> Observable<T> fromArray(T... items)

作用:这个方法和 just() 类似,只不过 fromArray 可以传入多于10个的变量,并且可以传入一个数组。

fromIterable()

public static <T> Observable<T> fromIterable(Iterable<? extends T> source)

作用:这个方法和 fromArray() 类似,直接发送一个 List 集合数据给观察者

fromCallable() & fromRunnalbe()

public static <T> Observable<T> fromCallable(Callable<? extends T> supplier)

作用:这里的 Callable 是 java.util.concurrent 中的 Callable,Callable 和 Runnable 的用法基本一致,只是它会返回一个结果值,这个结果值就是发给观察者的。

Observable.fromCallable()类似于:

Observable.defer {
    try {
        Observable.just(...)
    } catch(e: Throwable) {
        Observable.error(e)
    }
}

因此,just为运行同步,而fromCallable可以被推迟到另一个Scheduler与subscribeOn(“后”和执行)。

示例如下:

Observable.fromCallable(new Callable < Integer > () {

    @Override
    public Integer call() throws Exception {
        return 1;
    }
})
.subscribe(new Consumer < Integer > () {
    @Override
    public void accept(Integer integer) throws Exception {
        Log.d(TAG, "================accept " + integer);
    }
});

fromAction()

rxjava-async模块还包含一个fromAction操作符,它接受一个Action作为参数,返回一个Observable,一旦Action终止,它发射这个你传递给fromAction的数据。

Maybe、Completable 专用,相当于执行完成 Action 中的代码并且调用 onComplete,很方便。

fromFuture()

public static <T> Observable<T> fromFuture(Future<? extends T> future)

作用:参数中的 Future 是 java.util.concurrent 中的 Future,Future 的作用是增加了 cancel() 等方法操作 Callable,它可以通过 get() 方法来获取 Callable 返回的值。

示例如下:

FutureTask < String > futureTask = new FutureTask < > (new Callable < String > () {
    @Override
    public String call() throws Exception {
        Log.d(TAG, "CallableDemo is Running");
        return "返回结果";
    }
});

Observable.fromFuture(futureTask)
    .doOnSubscribe(new Consumer < Disposable > () {
    @Override
    public void accept(Disposable disposable) throws Exception {
        futureTask.run();
    }
})
.subscribe(new Consumer < String > () {
    @Override
    public void accept(String s) throws Exception {
        Log.d(TAG, "================accept " + s);
    }
});

doOnSubscribe() 的作用就是只有订阅时才会发送事件。

defer()

public static <T> Observable<T> defer(Callable<? extends ObservableSource<? extends T>> supplier)

作用:这个方法的作用就是直到被观察者被订阅后才会创建被观察者。

示例如下:

// i 要定义为成员变量
Integer i = 100;

Observable<Integer> observable = Observable.defer(new Callable<ObservableSource<? extends Integer>>() {
    @Override
    public ObservableSource<? extends Integer> call() throws Exception {
        return Observable.just(i);
    }
});

i = 200;

Observer observer = new Observer<Integer>() {
    @Override
    public void onSubscribe(Disposable d) {

    }

    @Override
    public void onNext(Integer integer) {
        Log.d(TAG, "================onNext " + integer);
    }

    @Override
    public void onError(Throwable e) {

    }

    @Override
    public void onComplete() {

    }
};

observable.subscribe(observer);

i = 300;

observable.subscribe(observer);

打印结果如下:

================onNext 200
================onNext 300

因为 defer() 只有观察者订阅的时候才会创建新的被观察者,所以每订阅一次就会打印一次,并且都是打印 i 最新的值。

timer()

public static Observable<Long> timer(long delay, TimeUnit unit) 

作用:当到指定时间后就会发送一个 0L 的值给观察者。

示例如下:

Observable.timer(2, TimeUnit.SECONDS)
.subscribe(new Observer < Long > () {
    @Override
    public void onSubscribe(Disposable d) {

    }

    @Override
    public void onNext(Long aLong) {
        Log.d(TAG, "===============onNext " + aLong);
    }

    @Override
    public void onError(Throwable e) {

    }

    @Override
    public void onComplete() {

    }
});

interval()

public static Observable<Long> interval(long period, TimeUnit unit)
public static Observable<Long> interval(long initialDelay, long period, TimeUnit unit)

作用:每隔一段时间就会发送一个事件,这个事件是从0开始,不断增1的数字。

示例如下:

Observable.interval(4, TimeUnit.SECONDS)
.subscribe(new Observer < Long > () {
    @Override
    public void onSubscribe(Disposable d) {
        Log.d(TAG, "==============onSubscribe ");
    }

    @Override
    public void onNext(Long aLong) {
        Log.d(TAG, "==============onNext " + aLong);
    }

    @Override
    public void onError(Throwable e) {

    }

    @Override
    public void onComplete() {

    }
});

打印结果:
05-20 20:48:10.321 28723-28723/com.example.louder.rxjavademo D/chan: ==============onSubscribe 
05-20 20:48:14.324 28723-28746/com.example.louder.rxjavademo D/chan: ==============onNext 0
05-20 20:48:18.324 28723-28746/com.example.louder.rxjavademo D/chan: ==============onNext 1
05-20 20:48:22.323 28723-28746/com.example.louder.rxjavademo D/chan: ==============onNext 2
05-20 20:48:26.323 28723-28746/com.example.louder.rxjavademo D/chan: ==============onNext 3
05-20 20:48:30.323 28723-28746/com.example.louder.rxjavademo D/chan: ==============onNext 4
05-20 20:48:34.323 28723-28746/com.example.louder.rxjavademo D/chan: ==============onNext 5

从时间就可以看出每隔4秒就会发出一次数字递增1的事件。这里说下 interval() 第三个方法的 initialDelay 参数,这个参数的意思就是 onSubscribe 回调之后,再次回调 onNext 的间隔时间。

intervalRange()

public static Observable<Long> intervalRange(long start, long count, long initialDelay, long period, TimeUnit unit)
public static Observable<Long> intervalRange(long start, long count, long initialDelay, long period, TimeUnit unit, Scheduler scheduler)

作用:可以指定发送事件的开始值和数量,其他与 interval() 的功能一样。

示例如下:

Observable.intervalRange(2, 5, 2, 1, TimeUnit.SECONDS)
.subscribe(new Observer < Long > () {
    @Override
    public void onSubscribe(Disposable d) {
        Log.d(TAG, "==============onSubscribe ");
    }

    @Override
    public void onNext(Long aLong) {
        Log.d(TAG, "==============onNext " + aLong);
    }

    @Override
    public void onError(Throwable e) {

    }

    @Override
    public void onComplete() {

    }
});
打印结果:
05-21 00:03:01.672 2504-2504/com.example.louder.rxjavademo D/chan: ==============onSubscribe 
05-21 00:03:03.674 2504-2537/com.example.louder.rxjavademo D/chan: ==============onNext 2
05-21 00:03:04.674 2504-2537/com.example.louder.rxjavademo D/chan: ==============onNext 3
05-21 00:03:05.674 2504-2537/com.example.louder.rxjavademo D/chan: ==============onNext 4
05-21 00:03:06.673 2504-2537/com.example.louder.rxjavademo D/chan: ==============onNext 5
05-21 00:03:07.674 2504-2537/com.example.louder.rxjavademo D/chan: ==============onNext 6

可以看出收到5次 onNext 事件,并且是从 2 开始的。

range()

public static Observable<Integer> range(final int start, final int count)

作用:同时发送一定范围的事件序列。

示例如下:

Observable.range(2, 5)
.subscribe(new Observer < Integer > () {
    @Override
    public void onSubscribe(Disposable d) {
        Log.d(TAG, "==============onSubscribe ");
    }

    @Override
    public void onNext(Integer aLong) {
        Log.d(TAG, "==============onNext " + aLong);
    }

    @Override
    public void onError(Throwable e) {

    }

    @Override
    public void onComplete() {

    }
});

打印结果:
==============onSubscribe 
==============onNext 2
==============onNext 3
==============onNext 4
==============onNext 5
==============onNext 6

rangeLong()

public static Observable<Long> rangeLong(long start, long count)

作用:与 range() 一样,只是数据类型为 Long

empty() & never() & error()

public static <T> Observable<T> empty()
public static <T> Observable<T> never()
public static <T> Observable<T> error(final Throwable exception)

作用:
1.empty() : 直接发送 onComplete() 事件
2.never():不发送任何事件
3.error():发送 onError() 事件

示例如下:

Observable.empty()
.subscribe(new Observer < Object > () {

    @Override
    public void onSubscribe(Disposable d) {
        Log.d(TAG, "==================onSubscribe");
    }

    @Override
    public void onNext(Object o) {
        Log.d(TAG, "==================onNext");
    }

    @Override
    public void onError(Throwable e) {
        Log.d(TAG, "==================onError " + e);
    }

    @Override
    public void onComplete() {
        Log.d(TAG, "==================onComplete");
    }
});

打印结果:

==================onSubscribe
==================onComplete

换成 never() 的打印结果:

==================onSubscribe

换成 error() 的打印结果:

==================onSubscribe
==================onError java.lang.NullPointerException

amb()

public static Observable amb(Iterable<? extends ObservableSource<? extends T>> sources)

作用:amb() 要传入一个 Observable 集合,但是只会发送最先发送事件的 Observable 中的事件,其余 Observable 将会被丢弃。

示例如下:

ArrayList < Observable < Long >> list = new ArrayList < > ();
list.add(Observable.intervalRange(1, 5, 2, 1, TimeUnit.SECONDS));
list.add(Observable.intervalRange(6, 5, 0, 1, TimeUnit.SECONDS));
Observable.amb(list)
.subscribe(new Consumer < Long > () {
    @Override
    public void accept(Long aLong) throws Exception {
        Log.d(TAG, "========================aLong " + aLong);
    }
});
打印结果:
05-26 10:21:29.580 17185-17219/com.example.rxjavademo D/chan: ========================aLong 6
05-26 10:21:30.580 17185-17219/com.example.rxjavademo D/chan: ========================aLong 7
05-26 10:21:31.579 17185-17219/com.example.rxjavademo D/chan: ========================aLong 8
05-26 10:21:32.579 17185-17219/com.example.rxjavademo D/chan: ========================aLong 9
05-26 10:21:33.579 17185-17219/com.example.rxjavademo D/chan: ========================aLong 10

3、转换操作符

map()

map可以将被观察者发送的数据类型转变成其他的类型

public final <R> Observable<R> map(Function<? super T, ? extends R> mapper)

以下代码将 Integer 类型的数据转换成 String:

Observable.just(1, 2, 3)
.map(new Function < Integer, String > () {
    @Override
    public String apply(Integer integer) throws Exception {
        return "I'm " + integer;
    }
})
.subscribe(new Observer < String > () {
    @Override
    public void onSubscribe(Disposable d) {
        Log.e(TAG, "===================onSubscribe");
    }

    @Override
    public void onNext(String s) {
        Log.e(TAG, "===================onNext " + s);
    }

    @Override
    public void onError(Throwable e) {

    }

    @Override
    public void onComplete() {

    }
});

打印结果:
===================onSubscribe
===================onNext I'm 1
===================onNext I'm 2
===================onNext I'm 3

flatMap()

这个方法可以将事件序列中的元素进行整合加工,返回一个新的被观察者。flatMap() 其实与 map() 类似,但是 flatMap() 返回的是一个 Observerable。

public final <R> Observable<R> flatMap(Function<? super T, ? extends ObservableSource<? extends R>> mapper)

现在有一个需求就是要将 Person 集合中的每个元素中的 Plan 的 action 打印出来。首先用 map() 来实现这个需求看看:

Observable.fromIterable(personList)
.map(new Function < Person, List < Plan >> () {
    @Override
    public List < Plan > apply(Person person) throws Exception {
        return person.getPlanList();
    }
})
.subscribe(new Observer < List < Plan >> () {
    @Override
    public void onSubscribe(Disposable d) {

    }

    @Override
    public void onNext(List < Plan > plans) {
        for (Plan plan: plans) {
            List < String > planActionList = plan.getActionList();
            for (String action: planActionList) {
                Log.d(TAG, "==================action " + action);
            }
        }
    }

    @Override
    public void onError(Throwable e) {

    }

    @Override
    public void onComplete() {

    }
});

可以看到 onNext() 用了嵌套 for 循环来实现,如果代码逻辑复杂起来的话,可能需要多重循环才可以实现。现在看下使用 flatMap() 实现:

Observable.fromIterable(personList)
.flatMap(new Function < Person, ObservableSource < Plan >> () {
    @Override
    public ObservableSource < Plan > apply(Person person) {
        return Observable.fromIterable(person.getPlanList());
    }
})
.flatMap(new Function < Plan, ObservableSource < String >> () {
    @Override
    public ObservableSource < String > apply(Plan plan) throws Exception {
        return Observable.fromIterable(plan.getActionList());
    }
})
.subscribe(new Observer < String > () {
    @Override
    public void onSubscribe(Disposable d) {

    }

    @Override
    public void onNext(String s) {
        Log.d(TAG, "==================action: " + s);
    }

    @Override
    public void onError(Throwable e) {

    }

    @Override
    public void onComplete() {

    }
});

从代码可以看出,只需要两个 flatMap() 就可以完成需求,并且代码逻辑非常清晰。

concatMap()

concatMap() 和 flatMap() 基本上是一样的,只不过 concatMap() 转发出来的事件是有序的,不管是否延时,而 flatMap() 是无序的。

public final <R> Observable<R> concatMap(Function<? super T, ? extends ObservableSource<? extends R>> mapper)
public final <R> Observable<R> concatMap(Function<? super T, ? extends ObservableSource<? extends R>> mapper, int prefetch)

buffer()

从需要发送的事件当中获取一定数量的事件,并将这些事件放到缓冲区当中一并发出。

public final Observable<List<T>> buffer(int count, int skip)

buffer 有两个参数,一个是 count,另一个 skip。count 缓冲区元素的数量,skip 就代表缓冲区满了之后,发送下一次事件序列的时候要跳过多少元素。这样说可能还是有点抽象,直接看代码:

Observable.just(1, 2, 3, 4, 5)
.buffer(2, 1)
.subscribe(new Observer < List < Integer >> () {
    @Override
    public void onSubscribe(Disposable d) {

    }

    @Override
    public void onNext(List < Integer > integers) {
        Log.d(TAG, "================缓冲区大小: " + integers.size());
        for (Integer i: integers) {
            Log.d(TAG, "================元素: " + i);
        }
    }

    @Override
    public void onError(Throwable e) {

    }

    @Override
    public void onComplete() {

    }
});
打印结果:
================缓冲区大小: 2
================元素: 1
================元素: 2
================缓冲区大小: 2
================元素: 2
================元素: 3
================缓冲区大小: 2
================元素: 3
================元素: 4
================缓冲区大小: 2
================元素: 4
================元素: 5
================缓冲区大小: 1
================元素: 5

从结果可以看出,每次发送事件,指针都会往后移动一个元素再取值,直到指针移动到没有元素的时候就会停止取值。

scan()

将数据以一定的逻辑聚合起来。

public final Observable<T> scan(BiFunction<T, T, T> accumulator)

示例如下:

Observable.just(1, 2, 3, 4, 5)
.scan(new BiFunction < Integer, Integer, Integer > () {
    @Override
    public Integer apply(Integer integer, Integer integer2) throws Exception {
        Log.d(TAG, "====================apply ");
        Log.d(TAG, "====================integer " + integer);
        Log.d(TAG, "====================integer2 " + integer2);
        return integer + integer2;
    }
})
.subscribe(new Consumer < Integer > () {
    @Override
    public void accept(Integer integer) throws Exception {
        Log.d(TAG, "====================accept " + integer);
    }
});
打印结果:
====================accept 1
====================apply 
====================integer 1
====================integer2 2
====================accept 3
====================apply 
====================integer 3
====================integer2 3
====================accept 6
====================apply 
====================integer 6
====================integer2 4
====================accept 10
====================apply 
====================integer 10
====================integer2 5
====================accept 15

groupBy()

将发送的数据进行分组,每个分组都会返回一个被观察者。

public final <K> Observable<GroupedObservable<K, T>> groupBy(Function<? super T, ? extends K> keySelector)

示例如下:

Observable.just(5, 2, 3, 4, 1, 6, 8, 9, 7, 10)
.groupBy(new Function < Integer, Integer > () {
    @Override
    public Integer apply(Integer integer) throws Exception {
        return integer % 3;
    }
})
.subscribe(new Observer < GroupedObservable < Integer, Integer >> () {
    @Override
    public void onSubscribe(Disposable d) {
        Log.d(TAG, "====================onSubscribe ");
    }

    @Override
    public void onNext(GroupedObservable < Integer, Integer > integerIntegerGroupedObservable) {
        Log.d(TAG, "====================onNext ");
        integerIntegerGroupedObservable.subscribe(new Observer < Integer > () {
            @Override
            public void onSubscribe(Disposable d) {
                Log.d(TAG, "====================GroupedObservable onSubscribe ");
            }

            @Override
            public void onNext(Integer integer) {
                Log.d(TAG, "====================GroupedObservable onNext  groupName: " + integerIntegerGroupedObservable.getKey() + " value: " + integer);
            }

            @Override
            public void onError(Throwable e) {
                Log.d(TAG, "====================GroupedObservable onError ");
            }

            @Override
            public void onComplete() {
                Log.d(TAG, "====================GroupedObservable onComplete ");
            }
        });
    }

    @Override
    public void onError(Throwable e) {
        Log.d(TAG, "====================onError ");
    }

    @Override
    public void onComplete() {
        Log.d(TAG, "====================onComplete ");
    }
});

window()

发送指定数量的事件时,就将这些事件分为一组。window 中的 count 的参数就是代表指定的数量,例如将 count 指定为2,那么每发2个数据就会将这2个数据分成一组。

public final Observable<Observable<T>> window(long count)

示例如下:

Observable.just(1, 2, 3, 4, 5)
.window(2)
.subscribe(new Observer < Observable < Integer >> () {
    @Override
    public void onSubscribe(Disposable d) {
        Log.d(TAG, "=====================onSubscribe ");
    }

    @Override
    public void onNext(Observable < Integer > integerObservable) {
        integerObservable.subscribe(new Observer < Integer > () {
            @Override
            public void onSubscribe(Disposable d) {
                Log.d(TAG, "=====================integerObservable onSubscribe ");
            }

            @Override
            public void onNext(Integer integer) {
                Log.d(TAG, "=====================integerObservable onNext " + integer);
            }

            @Override
            public void onError(Throwable e) {
                Log.d(TAG, "=====================integerObservable onError ");
            }

            @Override
            public void onComplete() {
                Log.d(TAG, "=====================integerObservable onComplete ");
            }
        });
    }

    @Override
    public void onError(Throwable e) {
        Log.d(TAG, "=====================onError ");
    }

    @Override
    public void onComplete() {
        Log.d(TAG, "=====================onComplete ");
    }
});
打印结果:
=====================onSubscribe 
=====================integerObservable onSubscribe 
=====================integerObservable onNext 1
=====================integerObservable onNext 2
=====================integerObservable onComplete 
=====================integerObservable onSubscribe 
=====================integerObservable onNext 3
=====================integerObservable onNext 4
=====================integerObservable onComplete 
=====================integerObservable onSubscribe 
=====================integerObservable onNext 5
=====================integerObservable onComplete 
=====================onComplete 

从结果可以发现,window() 将 1~5 的事件分成了3组。

compose()

对Observable进行变换,加工处理。

public final <R> Observable<R> compose(ObservableTransformer<? super T, ? extends R> composer)

示例如下:

Observable.just(1, 2, 3, 4, 5)
          .compose(new ObservableTransformer<T, T>() {
            @Override
            public ObservableSource<T> apply(Observable<T> observable) { //比如给observable添加subscribeOn、observeOn
                return observable.subscribeOn(Schedulers.io())
                        .observeOn(AndroidSchedulers.mainThread());
            })
      .subscribe();

4、功能操作符

subscribeOn()

指定被观察者的线程,要注意的时,如果多次调用此方法,只有第一次有效。如果不指定,则默认为主线程。

public final Observable<T> subscribeOn(Scheduler scheduler)

observeOn()

指定观察者的线程,可以切换线程,每指定一次就会生效一次。

public final Observable<T> observeOn(Scheduler scheduler)

示例如下:

Observable.just(1, 2, 3)
.observeOn(Schedulers.newThread())
.flatMap(new Function < Integer, ObservableSource < String >> () {
    @Override
    public ObservableSource < String > apply(Integer integer) throws Exception {
        Log.d(TAG, "======================flatMap Thread name " + Thread.currentThread().getName());
        return Observable.just("chan" + integer);
    }
})
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Observer < String > () {
    @Override
    public void onSubscribe(Disposable d) {
        Log.d(TAG, "======================onSubscribe");
    }

    @Override
    public void onNext(String s) {
        Log.d(TAG, "======================onNext Thread name " + Thread.currentThread().getName());
        Log.d(TAG, "======================onNext " + s);
    }

    @Override
    public void onError(Throwable e) {
        Log.d(TAG, "======================onError");
    }

    @Override
    public void onComplete() {
        Log.d(TAG, "======================onComplete");
    }
});
打印结果:
======================onSubscribe
======================flatMap Thread name RxNewThreadScheduler-1
======================flatMap Thread name RxNewThreadScheduler-1
======================flatMap Thread name RxNewThreadScheduler-1
======================onNext Thread name main
======================onNext chan1
======================onNext Thread name main
======================onNext chan2
======================onNext Thread name main
======================onNext chan3
======================onComplete

从打印结果可以知道,observeOn 成功切换了线程。

delay()

延迟一段事件发送事件, onSubscribe 回调之后 onNext 延时后才会回调。

public final Observable<T> delay(long delay, TimeUnit unit)

doOnEach()

Observable 每发送一件事件之前都会先回调这个方法。

public final Observable<T> doOnEach(final Consumer<? super Notification<T>> onNotification)

示例如下:

Observable.create(new ObservableOnSubscribe < Integer > () {
    @Override
    public void subscribe(ObservableEmitter < Integer > e) throws Exception {
        e.onNext(1);
        e.onNext(2);
        e.onNext(3);
        //      e.onError(new NumberFormatException());
        e.onComplete();
    }
})
.doOnEach(new Consumer < Notification < Integer >> () {
    @Override
    public void accept(Notification < Integer > integerNotification) throws Exception {
        Log.d(TAG, "==================doOnEach " + integerNotification.getValue());
    }
})
.subscribe(new Observer < Integer > () {
    @Override
    public void onSubscribe(Disposable d) {
        Log.d(TAG, "==================onSubscribe ");
    }

    @Override
    public void onNext(Integer integer) {
        Log.d(TAG, "==================onNext " + integer);
    }

    @Override
    public void onError(Throwable e) {
        Log.d(TAG, "==================onError ");
    }

    @Override
    public void onComplete() {
        Log.d(TAG, "==================onComplete ");
    }
});
打印结果:
==================onSubscribe 
==================doOnEach 1
==================onNext 1
==================doOnEach 2
==================onNext 2
==================doOnEach 3
==================onNext 3
==================doOnEach null
==================onComplete 

从结果就可以看出每发送一个事件之前都会回调 doOnEach 方法,并且可以取出 onNext() 发送的值。

doOnNext()

Observable 每发送 onNext() 之前都会先回调这个方法。

public final Observable<T> doOnNext(Consumer<? super T> onNext)

doAfterNext()

Observable 每发送 onNext() 之后都会回调这个方法。

public final Observable<T> doAfterNext(Consumer<? super T> onAfterNext)

doOnComplete()

Observable 每发送 onComplete() 之前都会回调这个方法。

public final Observable<T> doOnComplete(Action onComplete)

doOnError()

Observable 每发送 onError() 之前都会回调这个方法。

public final Observable<T> doOnError(Consumer<? super Throwable> onError)

doOnSubscribe()

Observable 每发送 onSubscribe() 之前都会回调这个方法。

public final Observable<T> doOnSubscribe(Consumer<? super Disposable> onSubscribe)

doOnDispose()

当调用 Disposable 的 dispose() 之后回调该方法。

public final Observable<T> doOnDispose(Action onDispose)

doOnLifecycle()

在回调 onSubscribe 之前回调该方法的第一个参数的回调方法,可以使用该回调方法决定是否取消订阅。第二个参数的回调方法的作用与 doOnDispose() 是一样的。

public final Observable<T> doOnLifecycle(final Consumer<? super Disposable> onSubscribe, final Action onDispose)

示例如下:

Observable.create(new ObservableOnSubscribe<Integer>() {
    @Override
    public void subscribe(ObservableEmitter<Integer> e) throws Exception {
        e.onNext(1);
        e.onNext(2);
        e.onNext(3);
        e.onComplete();
    }
})
.doOnLifecycle(new Consumer<Disposable>() {
    @Override
    public void accept(Disposable disposable) throws Exception {
        Log.d(TAG, "==================doOnLifecycle accept");
    }
}, new Action() {
    @Override
    public void run() throws Exception {
        Log.d(TAG, "==================doOnLifecycle Action");
    }
})
.doOnDispose(
    new Action() {
        @Override
        public void run() throws Exception {
            Log.d(TAG, "==================doOnDispose Action");
        }
})
.subscribe(new Observer<Integer>() {
    private Disposable d;
    @Override
    public void onSubscribe(Disposable d) {
        Log.d(TAG, "==================onSubscribe ");
        this.d = d;
    }

    @Override
    public void onNext(Integer integer) {
        Log.d(TAG, "==================onNext " + integer);
        d.dispose();
    }

    @Override
    public void onError(Throwable e) {
        Log.d(TAG, "==================onError ");
    }

    @Override
    public void onComplete() {
        Log.d(TAG, "==================onComplete ");
    }

});
打印结果:
 ==================doOnLifecycle accept
==================onSubscribe 
==================onNext 1
==================doOnDispose Action
==================doOnLifecycle Action

可以看到当在 onNext() 方法进行取消订阅操作后,doOnDispose() 和 doOnLifecycle() 都会被回调。
如果使用 doOnLifecycle 进行取消订阅,来看看打印结果:

==================doOnLifecycle accept
==================onSubscribe 

可以发现 doOnDispose Action 和 doOnLifecycle Action 都没有被回调。

doOnTerminate() & doAfterTerminate()

doOnTerminate 是在 onError 或者 onComplete 发送之前回调,而 doAfterTerminate 则是 onError 或者 onComplete 发送之后回调。

public final Observable<T> doOnTerminate(final Action onTerminate)
public final Observable<T> doAfterTerminate(Action onFinally)

doFinally()

在所有事件发送完毕之后回调该方法。

public final Observable<T> doFinally(Action onFinally)

这里可能你会有个问题,那就是 doFinally() 和 doAfterTerminate() 到底有什么区别?区别就是在于取消订阅,如果取消订阅之后 doAfterTerminate() 就不会被回调,而 doFinally() 无论怎么样都会被回调,且都会在事件序列的最后。

onErrorReturn()

当接受到一个 onError() 事件之后回调,返回的值会回调 onNext() 方法,并正常结束该事件序列。

public final Observable<T> onErrorReturn(Function<? super Throwable, ? extends T> valueSupplier)

示例如下:

Observable.create(new ObservableOnSubscribe<Integer>() {
    @Override
    public void subscribe(ObservableEmitter<Integer> e) throws Exception {
        e.onNext(1);
        e.onNext(2);
        e.onNext(3);
        e.onError(new NullPointerException());
    }
})
.onErrorReturn(new Function<Throwable, Integer>() {
    @Override
    public Integer apply(Throwable throwable) throws Exception {
        Log.d(TAG, "==================onErrorReturn " + throwable);
        return 404;
    }
})
.subscribe(new Observer<Integer>() {
    @Override
    public void onSubscribe(Disposable d) {
        Log.d(TAG, "==================onSubscribe ");
    }

    @Override
    public void onNext(Integer integer) {
        Log.d(TAG, "==================onNext " + integer);
    }

    @Override
    public void onError(Throwable e) {
        Log.d(TAG, "==================onError ");
    }

    @Override
    public void onComplete() {
        Log.d(TAG, "==================onComplete ");
    }
});
打印结果:
==================onSubscribe 
==================onNext 1
==================onNext 2
==================onNext 3
==================onErrorReturn java.lang.NullPointerException
==================onNext 404
==================onComplete 

onErrorResumeNext()

当接收到 onError() 事件时,返回一个新的 Observable,并正常结束事件序列。

public final Observable<T> onErrorResumeNext(Function<? super Throwable, ? extends ObservableSource<? extends T>> resumeFunction)

示例如下:

Observable.create(new ObservableOnSubscribe<Integer>() {
    @Override
    public void subscribe(ObservableEmitter<Integer> e) throws Exception {
        e.onNext(1);
        e.onNext(2);
        e.onNext(3);
        e.onError(new NullPointerException());
    }
})
.onErrorResumeNext(new Function<Throwable, ObservableSource<? extends Integer>>() {
    @Override
    public ObservableSource<? extends Integer> apply(Throwable throwable) throws Exception {
        Log.d(TAG, "==================onErrorResumeNext " + throwable);
        return Observable.just(4, 5, 6);
    }
})
.subscribe(new Observer<Integer>() {
    @Override
    public void onSubscribe(Disposable d) {
        Log.d(TAG, "==================onSubscribe ");
    }

    @Override
    public void onNext(Integer integer) {
        Log.d(TAG, "==================onNext " + integer);
    }

    @Override
    public void onError(Throwable e) {
        Log.d(TAG, "==================onError ");
    }

    @Override
    public void onComplete() {
        Log.d(TAG, "==================onComplete ");
    }
});
打印结果:
==================onSubscribe 
==================onNext 1
==================onNext 2
==================onNext 3
==================onErrorResumeNext java.lang.NullPointerException
==================onNext 4
==================onNext 5
==================onNext 6
==================onComplete 

onExceptionResumeNext()

与 onErrorResumeNext() 作用基本一致,但是这个方法只能捕捉 Exception。

public final Observable<T> onExceptionResumeNext(final ObservableSource<? extends T> next)

retry()

如果出现错误事件,则会重新发送所有事件序列。times 是代表重新发的次数。

public final Observable<T> retry(long times)

retryUntil()

出现错误事件之后,可以通过此方法判断是否继续发送事件。指示Observable遇到错误时,是否让Observable重新订阅。

public final Observable<T> retryUntil(final BooleanSupplier stop)

示例如下:

Observable.create(new ObservableOnSubscribe < Integer > () {
    @Override
    public void subscribe(ObservableEmitter < Integer > e) throws Exception {
        e.onNext(1);
        e.onNext(2);
        e.onNext(3);
        e.onError(new Exception("404"));
    }
})
.retryUntil(new BooleanSupplier() {
    @Override
    public boolean getAsBoolean() throws Exception {
        if (i == 6) {
            return true;
        }
        return false;
    }
})
.subscribe(new Observer < Integer > () {
    @Override
    public void onSubscribe(Disposable d) {
        Log.d(TAG, "==================onSubscribe ");
    }

    @Override
    public void onNext(Integer integer) {
        i += integer;
        Log.d(TAG, "==================onNext " + integer);
    }

    @Override
    public void onError(Throwable e) {
        Log.d(TAG, "==================onError ");
    }

    @Override
    public void onComplete() {
        Log.d(TAG, "==================onComplete ");
    }
});
打印结果:
==================onSubscribe 
==================onNext 1
==================onNext 2
==================onNext 3
==================onError 

retryWhen()

当被观察者接收到异常或者错误事件时会回调该方法,这个方法会返回一个新的被观察者。如果返回的被观察者发送 Error 事件则之前的被观察者不会继续发送事件,如果发送正常事件则之前的被观察者会继续不断重试发送事件。

public final void safeSubscribe(Observer<? super T> s)

示例如下:

Observable.create(new ObservableOnSubscribe < String > () {
    @Override
    public void subscribe(ObservableEmitter < String > e) throws Exception {
        e.onNext("chan");
        e.onNext("ze");
        e.onNext("de");
        e.onError(new Exception("404"));
        e.onNext("haha");
    }
})
.retryWhen(new Function < Observable < Throwable > , ObservableSource <? >> () {
    @Override
    public ObservableSource <? > apply(Observable < Throwable > throwableObservable) throws Exception {
        return throwableObservable.flatMap(new Function < Throwable, ObservableSource <? >> () {
            @Override
            public ObservableSource <? > apply(Throwable throwable) throws Exception {
                if(!throwable.toString().equals("java.lang.Exception: 404")) {
                    return Observable.just("可以忽略的异常");
                } else {
                    return Observable.error(new Throwable("终止啦"));
                }
            }
        });
    }
})
.subscribe(new Observer < String > () {
    @Override
    public void onSubscribe(Disposable d) {
        Log.d(TAG, "==================onSubscribe ");
    }

    @Override
    public void onNext(String s) {
        Log.d(TAG, "==================onNext " + s);
    }

    @Override
    public void onError(Throwable e) {
        Log.d(TAG, "==================onError " + e.toString());
    }

    @Override
    public void onComplete() {
        Log.d(TAG, "==================onComplete ");
    }
});
打印结果:
==================onSubscribe 
==================onNext chan
==================onNext ze
==================onNext de
==================onError java.lang.Throwable: 终止啦

将 onError(new Exception("404")) 改为 onError(new Exception("303")) 看看打印结果:
==================onNext chan
==================onNext ze
==================onNext de
==================onNext chan
==================onNext ze
==================onNext de
==================onNext chan
==================onNext ze
==================onNext de
==================onNext chan
==================onNext ze
==================onNext de
==================onNext chan
==================onNext ze
==================onNext de
==================onNext chan
......
从结果可以看出,会不断重复发送消息。

repeat()

重复发送被观察者的事件,times 为发送次数。

public final Observable<T> repeat(long times)

repeatWhen()

这个方法可以会返回一个新的被观察者设定一定逻辑来决定是否重复发送事件。

public final Observable<T> repeatWhen(final Function<? super Observable<Object>, ? extends ObservableSource<?>> handler)

repeatWhen可以实现重订阅功能,而触发重订阅两个关键因素:
1.Obervable完成一次订阅,就是Observable调用onComplete
2.当Observable调用onComplete就会进入到repeatWhen方法里面,是否要触发重订阅,就需要通过repeatWhen的Function方法所返回的ObservableSource确定,如果返回的是onNext则触发重订阅,而返回的是onComplete/onError则不会触发重订阅

Observable.create(new ObservableOnSubscribe<String>() {
            @Override
            public void subscribe(ObservableEmitter<String> emitter) throws Exception {
                emitter.onComplete();
            }
        }).doOnComplete(new Action() {
            @Override
            public void run() throws Exception {
                Log.d(TAG, "触发重订阅");
            }
        }).repeatWhen(new Function<Observable<Object>, ObservableSource<?>>() {

            private int n = 0;

            @Override
            public ObservableSource<?> apply(Observable<Object> objectObservable) throws Exception {
                return objectObservable.flatMap(new Function<Object, ObservableSource<?>>() {
                    @Override
                    public ObservableSource<?> apply(Object o) throws Exception {
                        if (n != 3) {
                            n++;
                            return Observable.timer(3, TimeUnit.SECONDS);
                        } else {
                            return Observable.empty();
                        }
                    }
                });
            }
        }).subscribe(new Observer<String>() {
            @Override
            public void onSubscribe(Disposable d) {

            }

            @Override
            public void onNext(String s) {
                Log.d(TAG, "onNext: " + s);
            }

            @Override
            public void onError(Throwable e) {
                Log.d(TAG, "onError: " + e);
            }

            @Override
            public void onComplete() {
                Log.d(TAG, "onComplete: ");
            }
        });

运行结果:
08-02 10:26:13.187 D/MainActivity-vv: 触发重订阅
08-02 10:26:16.196 D/MainActivity-vv: 触发重订阅
08-02 10:26:19.204 D/MainActivity-vv: 触发重订阅
08-02 10:26:22.206 D/MainActivity-vv: 触发重订阅

repeatWhen可以用于延时轮询,在doOnComplete进行操作

5、组合操作符

concat()

可以将多个观察者组合在一起,然后按照之前发送顺序发送事件。需要注意的是,concat() 最多只可以发送4个事件。

public static <T> Observable<T> concat(ObservableSource<? extends T> source1, ObservableSource<? extends T> source2, ObservableSource<? extends T> source3, ObservableSource<? extends T> source4)

示例如下:

Observable.concat(Observable.just(1, 2),
Observable.just(3, 4),
Observable.just(5, 6),
Observable.just(7, 8))
.subscribe(new Observer < Integer > () {
    @Override
    public void onSubscribe(Disposable d) {

    }

    @Override
    public void onNext(Integer integer) {
        Log.d(TAG, "================onNext " + integer);
    }

    @Override
    public void onError(Throwable e) {

    }

    @Override
    public void onComplete() {

    }
});
打印如下:
================onNext 1
================onNext 2
================onNext 3
================onNext 4
================onNext 5
================onNext 6
================onNext 7
================onNext 8

concatArray()

与 concat() 作用一样,不过 concatArray() 可以发送多于 4 个被观察者。

public static <T> Observable<T> concatArray(ObservableSource<? extends T>... sources)

merge() & mergeArray()

这个方法与 concat() 作用基本一样,只是 concat() 是串行发送事件,而 merge() 并行发送事件。

public static <T> Observable<T> merge(ObservableSource<? extends T> source1, ObservableSource<? extends T> source2, ObservableSource<? extends T> source3, ObservableSource<? extends T> source4)

现在来演示 concat() 和 merge() 的区别:

Observable.merge(
Observable.interval(1, TimeUnit.SECONDS).map(new Function < Long, String > () {
    @Override
    public String apply(Long aLong) throws Exception {
        return "A" + aLong;
    }
}),
Observable.interval(1, TimeUnit.SECONDS).map(new Function < Long, String > () {
    @Override
    public String apply(Long aLong) throws Exception {
        return "B" + aLong;
    }
}))
    .subscribe(new Observer < String > () {
    @Override
    public void onSubscribe(Disposable d) {

    }

    @Override
    public void onNext(String s) {
        Log.d(TAG, "=====================onNext " + s);
    }

    @Override
    public void onError(Throwable e) {

    }

    @Override
    public void onComplete() {

    }
});
打印结果如下:
05-21 16:10:31.125 12801-12850/com.example.rxjavademo D/chan: =====================onNext B0
05-21 16:10:31.125 12801-12849/com.example.rxjavademo D/chan: =====================onNext A0
05-21 16:10:32.125 12801-12849/com.example.rxjavademo D/chan: =====================onNext A1
05-21 16:10:32.126 12801-12850/com.example.rxjavademo D/chan: =====================onNext B1
05-21 16:10:33.125 12801-12849/com.example.rxjavademo D/chan: =====================onNext A2
05-21 16:10:33.125 12801-12850/com.example.rxjavademo D/chan: =====================onNext B2
05-21 16:10:34.125 12801-12849/com.example.rxjavademo D/chan: =====================onNext A3
05-21 16:10:34.125 12801-12850/com.example.rxjavademo D/chan: =====================onNext B3
05-21 16:10:35.124 12801-12849/com.example.rxjavademo D/chan: =====================onNext A4
05-21 16:10:35.125 12801-12850/com.example.rxjavademo D/chan: =====================onNext B4
05-21 16:10:36.125 12801-12849/com.example.rxjavademo D/chan: =====================onNext A5
05-21 16:10:36.125 12801-12850/com.example.rxjavademo D/chan: =====================onNext B5
......
从结果可以看出,A 和 B 的事件序列都可以发出,将以上的代码换成 concat() 看看打印结果:
05-21 16:17:52.352 14597-14621/com.example.rxjavademo D/chan: =====================onNext A0
05-21 16:17:53.351 14597-14621/com.example.rxjavademo D/chan: =====================onNext A1
05-21 16:17:54.351 14597-14621/com.example.rxjavademo D/chan: =====================onNext A2
05-21 16:17:55.351 14597-14621/com.example.rxjavademo D/chan: =====================onNext A3
05-21 16:17:56.351 14597-14621/com.example.rxjavademo D/chan: =====================onNext A4
05-21 16:17:57.351 14597-14621/com.example.rxjavademo D/chan: =====================onNext A5
......
从结果可以知道,只有等到第一个被观察者发送完事件之后,第二个被观察者才会发送事件。

mergeArray() 与 merge() 的作用是一样的,只是它可以发送4个以上的被观察者。

concatArrayDelayError() & mergeArrayDelayError()

在 concatArray() 和 mergeArray() 两个方法当中,如果其中有一个被观察者发送了一个 Error 事件,那么就会停止发送事件,如果你想 onError() 事件延迟到所有被观察者都发送完事件后再执行的话,就可以使用 concatArrayDelayError() 和 mergeArrayDelayError()

public static <T> Observable<T> concatArrayDelayError(ObservableSource<? extends T>... sources)
public static <T> Observable<T> mergeArrayDelayError(ObservableSource<? extends T>... sources)

示例如下:

Observable.concatArrayDelayError(
Observable.create(new ObservableOnSubscribe < Integer > () {
    @Override
    public void subscribe(ObservableEmitter < Integer > e) throws Exception {
        e.onNext(1);
        e.onError(new NumberFormatException());
    }
}), Observable.just(2, 3, 4))
.subscribe(new Observer < Integer > () {
    @Override
    public void onSubscribe(Disposable d) {

    }

    @Override
    public void onNext(Integer integer) {
        Log.d(TAG, "===================onNext " + integer);
    }

    @Override
    public void onError(Throwable e) {
        Log.d(TAG, "===================onError ");
    }

    @Override
    public void onComplete() {

    }
});
打印结果如下:
===================onNext 1
===================onNext 2
===================onNext 3
===================onNext 4
===================onError 

从结果可以看到,onError 事件是在所有被观察者发送完事件才发送的。mergeArrayDelayError() 也是有同样的作用。

zip()

会将多个被观察者合并,根据各个被观察者发送事件的顺序一个个结合起来,最终发送的事件数量会与源 Observable 中最少事件的数量一样。

public static <T1, T2, R> Observable<R> zip(ObservableSource<? extends T1> source1, ObservableSource<? extends T2> source2, BiFunction<? super T1, ? super T2, ? extends R> zipper)

示例如下:

Observable.zip(Observable.intervalRange(1, 5, 1, 1, TimeUnit.SECONDS)
    .map(new Function<Long, String>() {
        @Override
        public String apply(Long aLong) throws Exception {
            String s1 = "A" + aLong;
            Log.d(TAG, "===================A 发送的事件 " + s1);
            return s1;
        }}),
        Observable.intervalRange(1, 6, 1, 1, TimeUnit.SECONDS)
            .map(new Function<Long, String>() {
            @Override
            public String apply(Long aLong) throws Exception {
                String s2 = "B" + aLong;
                Log.d(TAG, "===================B 发送的事件 " + s2);
                return s2;
            }
        }),
        new BiFunction<String, String, String>() {
            @Override
            public String apply(String s, String s2) throws Exception {
                String res = s + s2;
                return res;
            }
        })
.subscribe(new Observer<String>() {
    @Override
    public void onSubscribe(Disposable d) {
        Log.d(TAG, "===================onSubscribe ");
    }

    @Override
    public void onNext(String s) {
        Log.d(TAG, "===================onNext " + s);
    }

    @Override
    public void onError(Throwable e) {
        Log.d(TAG, "===================onError ");
    }

    @Override
    public void onComplete() {
        Log.d(TAG, "===================onComplete ");
    }
});
上面代码中有两个 Observable,第一个发送事件的数量为5个,第二个发送事件的数量为6个。现在来看下打印结果:
05-22 09:10:39.952 5338-5338/com.example.rxjavademo D/chan: ===================onSubscribe 
05-22 09:10:40.953 5338-5362/com.example.rxjavademo D/chan: ===================A 发送的事件 A1
05-22 09:10:40.953 5338-5363/com.example.rxjavademo D/chan: ===================B 发送的事件 B1
===================onNext A1B1
05-22 09:10:41.953 5338-5362/com.example.rxjavademo D/chan: ===================A 发送的事件 A2
05-22 09:10:41.954 5338-5363/com.example.rxjavademo D/chan: ===================B 发送的事件 B2
===================onNext A2B2
05-22 09:10:42.953 5338-5362/com.example.rxjavademo D/chan: ===================A 发送的事件 A3
05-22 09:10:42.953 5338-5363/com.example.rxjavademo D/chan: ===================B 发送的事件 B3
05-22 09:10:42.953 5338-5362/com.example.rxjavademo D/chan: ===================onNext A3B3
05-22 09:10:43.953 5338-5362/com.example.rxjavademo D/chan: ===================A 发送的事件 A4
05-22 09:10:43.953 5338-5363/com.example.rxjavademo D/chan: ===================B 发送的事件 B4
05-22 09:10:43.954 5338-5363/com.example.rxjavademo D/chan: ===================onNext A4B4
05-22 09:10:44.953 5338-5362/com.example.rxjavademo D/chan: ===================A 发送的事件 A5
05-22 09:10:44.953 5338-5363/com.example.rxjavademo D/chan: ===================B 发送的事件 B5
05-22 09:10:44.954 5338-5363/com.example.rxjavademo D/chan: ===================onNext A5B5
===================onComplete 

可以发现最终接收到的事件数量是5,那么为什么第二个 Observable 没有发送第6个事件呢?因为在这之前第一个 Observable 已经发送了 onComplete 事件,所以第二个 Observable 不会再发送事件。

combineLatest() & combineLatestDelayError()

public static <T1, T2, R> Observable<R> combineLatest(ObservableSource<? extends T1> source1, ObservableSource<? extends T2> source2, BiFunction<? super T1, ? super T2, ? extends R> combiner)

combineLatest() 的作用与 zip() 类似,但是 combineLatest() 发送事件的序列是与发送的时间线有关的,当 combineLatest() 中所有的 Observable 都发送了事件,只要其中有一个 Observable 发送事件,这个事件就会和其他 Observable 最近发送的事件结合起来发送,这样可能还是比较抽象,看看以下例子代码:

Observable.combineLatest(
Observable.intervalRange(1, 4, 1, 1, TimeUnit.SECONDS)
    .map(new Function < Long, String > () {@Override
    public String apply(Long aLong) throws Exception {
        String s1 = "A" + aLong;
        Log.d(TAG, "===================A 发送的事件 " + s1);
        return s1;
    }
}),
Observable.intervalRange(1, 5, 2, 2, TimeUnit.SECONDS)
    .map(new Function < Long, String > () {@Override
    public String apply(Long aLong) throws Exception {
        String s2 = "B" + aLong;
        Log.d(TAG, "===================B 发送的事件 " + s2);
        return s2;
    }
}),
new BiFunction < String, String, String > () {@Override
    public String apply(String s, String s2) throws Exception {
        String res = s + s2;
        return res;
    }
})
.subscribe(new Observer < String > () {
    @Override
    public void onSubscribe(Disposable d) {
        Log.d(TAG, "===================onSubscribe ");
    }

    @Override
    public void onNext(String s) {
        Log.d(TAG, "===================最终接收到的事件 " + s);
    }

    @Override
    public void onError(Throwable e) {
        Log.d(TAG, "===================onError ");
    }

    @Override
    public void onComplete() {
        Log.d(TAG, "===================onComplete ");
    }
});
分析上面的代码,Observable A 会每隔1秒就发送一次事件,Observable B 会隔2秒发送一次事件。来看看打印结果:
05-22 11:41:20.859 15104-15104/? D/chan: ===================onSubscribe 
05-22 11:41:21.859 15104-15128/com.example.rxjavademo D/chan: ===================A 发送的事件 A1
05-22 11:41:22.860 15104-15128/com.example.rxjavademo D/chan: ===================A 发送的事件 A2
05-22 11:41:22.861 15104-15129/com.example.rxjavademo D/chan: ===================B 发送的事件 B1
05-22 11:41:22.862 15104-15129/com.example.rxjavademo D/chan: ===================最终接收到的事件 A2B1
05-22 11:41:23.860 15104-15128/com.example.rxjavademo D/chan: ===================A 发送的事件 A3
===================最终接收到的事件 A3B1
05-22 11:41:24.860 15104-15128/com.example.rxjavademo D/chan: ===================A 发送的事件 A4
05-22 11:41:24.861 15104-15129/com.example.rxjavademo D/chan: ===================B 发送的事件 B2
05-22 11:41:24.861 15104-15128/com.example.rxjavademo D/chan: ===================最终接收到的事件 A4B1
05-22 11:41:24.861 15104-15129/com.example.rxjavademo D/chan: ===================最终接收到的事件 A4B2
05-22 11:41:26.860 15104-15129/com.example.rxjavademo D/chan: ===================B 发送的事件 B3
05-22 11:41:26.861 15104-15129/com.example.rxjavademo D/chan: ===================最终接收到的事件 A4B3
05-22 11:41:28.860 15104-15129/com.example.rxjavademo D/chan: ===================B 发送的事件 B4
05-22 11:41:28.861 15104-15129/com.example.rxjavademo D/chan: ===================最终接收到的事件 A4B4
05-22 11:41:30.860 15104-15129/com.example.rxjavademo D/chan: ===================B 发送的事件 B5
05-22 11:41:30.861 15104-15129/com.example.rxjavademo D/chan: ===================最终接收到的事件 A4B5
===================onComplete 

分析上述结果可以知道,当发送 A1 事件之后,因为 B 并没有发送任何事件,所以根本不会发生结合。当 B 发送了 B1 事件之后,就会与 A 最近发送的事件 A2 结合成 A2B1,这样只有后面一有被观察者发送事件,这个事件就会与其他被观察者最近发送的事件结合起来了。

因为 combineLatestDelayError() 就是多了延迟发送 onError() 功能,这里就不再赘述了。

reduce()

与 scan() 操作符的作用也是将发送数据以一定逻辑聚合起来,这两个的区别在于 scan() 每处理一次数据就会将事件发送给观察者,而 reduce() 会将所有数据聚合在一起才会发送事件给观察者。

public final Maybe<T> reduce(BiFunction<T, T, T> reducer)

示例如下:
Observable.just(0, 1, 2, 3)
.reduce(new BiFunction < Integer, Integer, Integer > () {
@Override
public Integer apply(Integer integer, Integer integer2) throws Exception {
int res = integer + integer2;
Log.d(TAG, “====================integer “ + integer);
Log.d(TAG, “====================integer2 “ + integer2);
Log.d(TAG, “====================res “ + res);
return res;
}
})
.subscribe(new Consumer < Integer > () {
@Override
public void accept(Integer integer) throws Exception {
Log.d(TAG, “==================accept “ + integer);
}
});
打印结果:
====================integer 0
====================integer2 1
====================res 1
====================integer 1
====================integer2 2
====================res 3
====================integer 3
====================integer2 3
====================res 6
==================accept 6
从结果可以看到,其实就是前2个数据聚合之后,然后再与后1个数据进行聚合,一直到没有数据为止。

collect()

将数据收集到数据结构当中。

public final <U> Single<U> collect(Callable<? extends U> initialValueSupplier, BiConsumer<? super U, ? super T> collector)

示例如下:

Observable.just(1, 2, 3, 4)
.collect(new Callable < ArrayList < Integer >> () {
    @Override
    public ArrayList < Integer > call() throws Exception {
        return new ArrayList < > ();
    }
},
new BiConsumer < ArrayList < Integer > , Integer > () {
    @Override
    public void accept(ArrayList < Integer > integers, Integer integer) throws Exception {
        integers.add(integer);
    }
})
.subscribe(new Consumer < ArrayList < Integer >> () {
    @Override
    public void accept(ArrayList < Integer > integers) throws Exception {
        Log.d(TAG, "===============accept " + integers);
    }
});
打印结果:
===============accept [1, 2, 3, 4]

startWith() & startWithArray()

在发送事件之前追加事件,startWith() 追加一个事件,startWithArray() 可以追加多个事件。追加的事件会先发出。

public final Observable<T> startWith(T item)
public final Observable<T> startWithArray(T... items)

示例如下:

Observable.just(5, 6, 7)
.startWithArray(2, 3, 4)
.startWith(1)
.subscribe(new Consumer < Integer > () {
    @Override
    public void accept(Integer integer) throws Exception {
        Log.d(TAG, "================accept " + integer);
    }
});
打印结果:
================accept 1
================accept 2
================accept 3
================accept 4
================accept 5
================accept 6
================accept 7

count()

返回被观察者发送事件的数量。

public final Single<Long> count()

示例如下:

Observable.just(1, 2, 3)
.count()
.subscribe(new Consumer < Long > () {
    @Override
    public void accept(Long aLong) throws Exception {
        Log.d(TAG, "=======================aLong " + aLong);
    }
});
打印结果:
=======================aLong 3

6、过滤操作符

filter()

通过一定逻辑来过滤被观察者发送的事件,如果返回 true 则会发送事件,否则不会发送。
public final Observable filter(Predicate<? super T> predicate)

示例如下:

 Observable.just(1, 2, 3)
    .filter(new Predicate < Integer > () {
        @Override
        public boolean test(Integer integer) throws Exception {
            return integer < 2;
        }
})
.subscribe(new Observer < Integer > () {
    @Override
    public void onSubscribe(Disposable d) {
        Log.d(TAG, "==================onSubscribe ");
    }

    @Override
    public void onNext(Integer integer) {
        i += integer;
        Log.d(TAG, "==================onNext " + integer);
    }

    @Override
    public void onError(Throwable e) {
        Log.d(TAG, "==================onError ");
    }

    @Override
    public void onComplete() {
        Log.d(TAG, "==================onComplete ");
    }
});
以上代码只有小于2的事件才会发送,来看看打印结果:
==================onSubscribe 
==================onNext 1
==================onComplete 

ofType()

过滤不符合该类型事件

public final <U> Observable<U> ofType(final Class<U> clazz)

示例如下:

Observable.just(1, 2, 3, "chan", "zhide")
.ofType(Integer.class)
.subscribe(new Observer < Integer > () {
    @Override
    public void onSubscribe(Disposable d) {
        Log.d(TAG, "==================onSubscribe ");
    }

    @Override
    public void onNext(Integer integer) {
        i += integer;
        Log.d(TAG, "==================onNext " + integer);
    }

    @Override
    public void onError(Throwable e) {
        Log.d(TAG, "==================onError ");
    }

    @Override
    public void onComplete() {
        Log.d(TAG, "==================onComplete ");
    }
});
打印结果:
==================onSubscribe 
==================onNext 1
==================onNext 2
==================onNext 3
==================onComplete 

skip() & skipLast()

跳过正序某些事件,count 代表跳过事件的数量

public final Observable<T> skip(long count)

skipLast() 作用也是跳过某些事件,不过它是用来跳过正序的后面的事件。

distinct()

过滤事件序列中的重复事件。

public final Observable<T> distinct() 

distinctUntilChanged()

过滤掉连续重复的事件

public final Observable<T> distinctUntilChanged()

示例如下:

Observable.just(1, 2, 3, 3, 2, 1)
.distinctUntilChanged()
.subscribe(new Observer < Integer > () {
    @Override
    public void onSubscribe(Disposable d) {
        Log.d(TAG, "==================onSubscribe ");
    }

    @Override
    public void onNext(Integer integer) {
        i += integer;
        Log.d(TAG, "==================onNext " + integer);
    }

    @Override
    public void onError(Throwable e) {
        Log.d(TAG, "==================onError ");
    }

    @Override
    public void onComplete() {
        Log.d(TAG, "==================onComplete ");
    }
});
打印结果:
==================onSubscribe 
==================onNext 1
==================onNext 2
==================onNext 3
==================onNext 2
==================onNext 1
==================onComplete 

因为事件序列中连续出现两次3,所以第二次3并不会发出。

take() & takeLast()

控制观察者接收的事件的数量。

public final Observable<T> take(long count)

示例如下:

Observable.just(1, 2, 3, 4, 5)
.take(3)
.subscribe(new Observer < Integer > () {
    @Override
    public void onSubscribe(Disposable d) {
        Log.d(TAG, "==================onSubscribe ");
    }

    @Override
    public void onNext(Integer integer) {
        i += integer;
        Log.d(TAG, "==================onNext " + integer);
    }

    @Override
    public void onError(Throwable e) {
        Log.d(TAG, "==================onError ");
    }

    @Override
    public void onComplete() {
        Log.d(TAG, "==================onComplete ");
    }
});
打印结果:
==================onSubscribe 
==================onNext 1
==================onNext 2
==================onNext 3
==================onComplete 

takeLast() 的作用就是控制观察者只能接受事件序列的后面几件事情。

debounce()

如果两件事件发送的时间间隔小于设定的时间间隔则前一件事件就不会发送给观察者。

public final Observable<T> debounce(long timeout, TimeUnit unit)

throttleWithTimeout() 与此方法的作用一样。

firstElement() && lastElement()

firstElement() 取事件序列的第一个元素,lastElement() 取事件序列的最后一个元素。

public final Maybe<T> firstElement()
public final Maybe<T> lastElement()

elementAt() & elementAtOrError()

elementAt() 可以指定取出事件序列中事件,但是输入的 index 超出事件序列的总数的话就不会出现任何结果。这种情况下,你想发出异常信息的话就用 elementAtOrError() 。

public final Maybe<T> elementAt(long index)
public final Single<T> elementAtOrError(long index)

示例如下:

Observable.just(1, 2, 3, 4)
.elementAt(0)
.subscribe(new Consumer < Integer > () {
    @Override
    public void accept(Integer integer) throws Exception {
        Log.d(TAG, "====================accept " + integer);
    }
});
打印结果:
====================accept 1

将 elementAt() 的值改为5,这时是没有打印结果的,因为没有满足条件的元素。
替换 elementAt() 为 elementAtOrError(),代码如下:

Observable.just(1, 2, 3, 4)
.elementAtOrError(5)
.subscribe(new Consumer < Integer > () {
    @Override
    public void accept(Integer integer) throws Exception {
        Log.d(TAG, "====================accept " + integer);
    }
});

这时候会抛出 NoSuchElementException 异常。

7、条件操作符

all()

判断事件序列是否全部满足某个事件,如果都满足则返回 true,反之则返回 false。

public final Single<Boolean> all(Predicate<? super T> predicate)

示例如下:

Observable.just(1, 2, 3, 4)
.all(new Predicate < Integer > () {
    @Override
    public boolean test(Integer integer) throws Exception {
        return integer < 5;
    }
})
.subscribe(new Consumer < Boolean > () {
    @Override
    public void accept(Boolean aBoolean) throws Exception {
        Log.d(TAG, "==================aBoolean " + aBoolean);
    }
});
打印结果:
==================aBoolean true

takeWhile()

可以设置条件,当某个数据满足条件时就会发送该数据,反之则不发送

public final Observable<T> takeWhile(Predicate<? super T> predicate)

示例如下:

Observable.just(1, 2, 3, 4)
.takeWhile(new Predicate < Integer > () {
    @Override
    public boolean test(Integer integer) throws Exception {
        return integer < 3;
    }
})
.subscribe(new Consumer < Integer > () {
    @Override
    public void accept(Integer integer) throws Exception {
        Log.d(TAG, "========================integer " + integer);
    }
});
打印结果:
========================integer 1
========================integer 2

skipWhile()

可以设置条件,当某个数据满足条件时不发送该数据,反之则发送。

public final Observable<T> skipWhile(Predicate<? super T> predicate)

takeUntil()

可以设置条件,当事件满足此条件时,下一次的事件就不会被发送了。

public final Observable<T> takeUntil(Predicate<? super T> stopPredicate

示例如下:

Observable.just(1, 2, 3, 4, 5, 6)
.takeUntil(new Predicate < Integer > () {
    @Override
    public boolean test(Integer integer) throws Exception {
        return integer > 3;
    }
})
.subscribe(new Consumer < Integer > () {
    @Override
    public void accept(Integer integer) throws Exception {
        Log.d(TAG, "========================integer " + integer);
    }
});
打印结果:
========================integer 1
========================integer 2
========================integer 3
========================integer 4

skipUntil()

当 skipUntil() 中的 Observable 发送事件了,原来的 Observable 才会发送事件给观察者。skipUntil() 里的 Observable 并不会发送事件给观察者。

public final <U> Observable<T> skipUntil(ObservableSource<U> other)

示例如下:

Observable.intervalRange(1, 5, 0, 1, TimeUnit.SECONDS)
.skipUntil(Observable.intervalRange(6, 5, 3, 1, TimeUnit.SECONDS))
.subscribe(new Observer < Long > () {
    @Override
    public void onSubscribe(Disposable d) {
        Log.d(TAG, "========================onSubscribe ");
    }

    @Override
    public void onNext(Long along) {
        Log.d(TAG, "========================onNext " + along);
    }

    @Override
    public void onError(Throwable e) {
        Log.d(TAG, "========================onError ");
    }

    @Override
    public void onComplete() {
        Log.d(TAG, "========================onComplete ");
    }
});
打印结果:
05-26 10:08:50.574 13023-13023/com.example.rxjavademo D/chan: ========================onSubscribe 
05-26 10:08:53.576 13023-13054/com.example.rxjavademo D/chan: ========================onNext 4
05-26 10:08:54.576 13023-13054/com.example.rxjavademo D/chan: ========================onNext 5
========================onComplete 

sequenceEqual()

判断两个 Observable 发送的事件是否相同。

public static <T> Single<Boolean> sequenceEqual(ObservableSource<? extends T> source1, ObservableSource<? extends T> source2)

contains()

判断事件序列中是否含有某个元素,如果有则返回 true,如果没有则返回 false。

public final Single<Boolean> contains(final Object element)

isEmpty()

判断事件序列是否为空。

public final Single<Boolean> isEmpty()

defaultIfEmpty()

如果观察者只发送一个 onComplete() 事件,这个方法会发送一个值。

public final Observable<T> defaultIfEmpty(T defaultItem)

示例如下:

Observable.create(new ObservableOnSubscribe < Integer > () {

    @Override
    public void subscribe(ObservableEmitter < Integer > e) throws Exception {
        e.onComplete();
    }
})
.defaultIfEmpty(666)
.subscribe(new Consumer < Integer > () {
    @Override
    public void accept(Integer integer) throws Exception {
        Log.d(TAG, "========================onNext " + integer);
    }
});
打印结果:
========================onNext 666

参考资料

https://juejin.im/post/5b17560e6fb9a01e2862246f
https://gank.io/post/560e15be2dca930e00da1083

Flutter集成Android项目

发表于 2019-06-28 | 分类于 Hybrid Develop

在现有项目上集成Flutter

1.在andorid工程目录上级目录下执行命令:

flutter create -t module xxxx(想要创建的flutter项目名)

执行完后,一个和Android项目平级的flutter项目就已经创建好了。

2.添加flutter到当前Android项目:
在Android项目根目录下的settings.gradle文件中,添加如下代码:

setBinding(new Binding([gradle: this]))
evaluate(new File(
        settingsDir.parentFile,
        "flutter_hybrid/.android/include_flutter.groovy"
))

然后,在项目的app目录下,build.gradle文件中,添加如下dependency:

implementation project(':flutter')

如上,添加完之后,准备工作就完成了。

集成Flutter页面跳转

通过继承FlutterActivity跳到默认页面

1.Application:初始化Flutter

public class App extends Application {

    @Override
    public void onCreate() {
        super.onCreate();
        FlutterMain.startInitialization(this);
    }
}

2.Activity:继承FlutterActivity

/**
 * debug模式原生跳转到flutter界面会出现白屏,release包就不会出现白屏了
 */
public class MainFlutterActivity extends FlutterActivity {
  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    GeneratedPluginRegistrant.registerWith(this);
  }

这样以后,我们就可以跳转这个MainFlutterActivity,实现在Android工程里面进入Flutter工程的默认页面了。

通过FlutterView跳到指定页面

首先通过Flutter.createFlutterView方法来创建一个FlutterView类型的View。它有三个构造参数:

public static FlutterView createView(Activity activity, Lifecycle lifecycle, String initialRoute)

三个参数分别是:
1.Activity,即当前附着的Activity

2.LifeCycle,且是不能为空(NonNull)的,这也就要求我们必须用AppCompatActivity来承载FlutterView了,如果我们的Activity继承自android.app.Activity是没有getLifeCycle()这个方法的。

public class FlutterViewActivity extends AppCompatActivity 复制代码

3.initialRoute这是一个String类型的变量,根据不同的标识跳转不同的Flutter页面:

Widget _widgetForRoute(String route) {
  switch (route) {
    case 'route1':
      return  MyHomePage(title: 'Flutter Demo Home Page1');
    case 'route2':
      return  MyHomePage(title: 'Flutter Demo Home Page2');
    default:
        return  MyHomePage(title: 'Flutter Demo Home Page2');
  }
}

然后在当前Activity的OnCreate方法中,执行:

FlutterView flutterView = Flutter.createView(this, getLifecycle(), "route1");    
setContentView(flutterView);

//或者:

setContentView(R.layout.activity_flutter);
rlContainer = findViewById(R.id.rl_container);
rlContainer.addView(flutterView)

集成到现有项目遇到的坑

Check failed: vm. Must be able to initialize the VM:

E/flutter: [ERROR:flutter/runtime/dart_vm_data.cc(19)] VM snapshot invalid and could not be inferred from settings.
    [ERROR:flutter/runtime/dart_vm.cc(241)] Could not setup VM data to bootstrap the VM from.
    [ERROR:flutter/runtime/dart_vm_lifecycle.cc(89)] Could not create Dart VM instance.
 A/flutter: [FATAL:flutter/shell/common/shell.cc(218)] Check failed: vm. Must be able to initialize the VM.

SIGABRT
    0x146b
    #00    pc 0002210c    /system/lib/libc.so (tgkill+12) [armeabi-v7a::ac712d92afdda143205c7d7c8befc336]
    #01    pc 00013165    /system/lib/libc.so (pthread_kill+48) [armeabi-v7a::ac712d92afdda143205c7d7c8befc336]
    #02    pc 00013379    /system/lib/libc.so (raise+10) [armeabi-v7a::ac712d92afdda143205c7d7c8befc336]
    #03    pc 000120a3    /system/lib/libc.so [armeabi-v7a::ac712d92afdda143205c7d7c8befc336]
    #04    pc 000219c0    /system/lib/libc.so (abort+4) [armeabi-v7a::ac712d92afdda143205c7d7c8befc336]
    #05    pc 00af85ab    /data/app-lib/xxx.debug-1/libflutter.so [armeabi-v7a::a12434e0b53806a35730000001000000]

经github上#24679及其他issue参考,得出以下解决方案:

Import flutter module to your HOST app(set binding, implementation, and instantiate view/fragment)
When you clean/rebuild from HOST app, flutter module does not generate properly
You need to open your flutter module in a separate AS
flutter clean
cd .android
./gradlew clean
./gradlew assembleDebug
Return to host app and run app(do not sync or rebuild) :)

翻译过来就是:

本项目执行清理命令。./gradlew clean
进入 flutter module 项目执行清理命令。flutter packages get;flutter clean
进入 flutter module 的 .android 项目执行清理命令和打包操作。./gradlew clean;./gradlew assemble
回到本项目执行打包命令。./gradlew assemble

直接执行下面命令即可:

$ ./gradlew clean;cd ../flutter_module;flutter packages get;flutter clean;cd .android/;./gradlew clean;./gradlew assemble;cd /d/workspace/app_proj/;./gradlew installDebug

后续假设你 flutter module 没有更新过,那么以后修改本地项目之后,就直接执行./gradlew installDebug。 注意不要用studio工具运行、rebuild或clean。

flutter_assets

assets下缺少flutter_assets也有可能造成此问题。具体可以参考:https://www.jianshu.com/p/2d0bba84e57e

混淆

按照上述方式,debug版本没问题,release却报错。需加以下混淆:

-keep class io.flutter.app.** { *; }
-keep class io.flutter.plugin.** { *; }
-keep class io.flutter.util.** { *; }
-keep class io.flutter.view.** { *; }
-keep class io.flutter.** { *; }
-keep class io.flutter.plugins.** { *; }
-keep class myapp.flutter.** { *; }

交互

Flutter 调用Android

java

new MethodChannel(flutterView, FlutterToAndroidCHANNEL).setMethodCallHandler(new MethodChannel.MethodCallHandler() {
    @Override
    public void onMethodCall(MethodCall methodCall, MethodChannel.Result result) {

        //接收来自flutter的指令withoutParams
        if (methodCall.method.equals("withoutParams")) {

            //跳转到指定Activity
            Intent intent = new Intent(NativeActivity.this, NativeActivity.class);
            startActivity(intent);

            //返回给flutter的参数
            result.success("success");
        }
        //接收来自flutter的指令withParams
        else if (methodCall.method.equals("withParams")) {

            //解析参数
            String text = methodCall.argument("flutter");

            //带参数跳转到指定Activity
            Intent intent = new Intent(NativeActivity.this, NativeActivity.class);
            intent.putExtra("test", text);
            startActivity(intent);

            //返回给flutter的参数
            result.success("success");
        } else {
            result.notImplemented();
        }
    }
});

dart

Future<Null> _jumpToNative() async {
  String result = await toAndroidPlugin.invokeMethod('withoutParams');

  print(result);
}


Future<Null> _jumpToNativeWithParams() async {

  Map<String, String> map = { "flutter": "这是一条来自flutter的参数" };

  String result = await toAndroidPlugin.invokeMethod('withParams', map);

  print(result);
}

Android 向 Flutter 传参

java

new EventChannel(flutterView, AndroidToFlutterCHANNEL)
            .setStreamHandler(new EventChannel.StreamHandler() {
                @Override
                public void onListen(Object o, EventChannel.EventSink eventSink) {
                    String androidParmas = "来自android原生的参数";
                    eventSink.success(androidParmas);
                }

                @Override
                public void onCancel(Object o) {

                }
            });

dart

 void _startfromAndroiPlugin(){
   if(_fromAndroiSub == null){
     _fromAndroiSub =  fromAndroiPlugin.receiveBroadcastStream()
     .listen(_onfromAndroiEvent,onError: _onfromAndroiError);
   }
 }

void _onfromAndroiEvent(Object event) {
   setState(() {
     _nativeParams = event;
   });
 }

 void _onfromAndroiError(Object error) {
   setState(() {
     _nativeParams = "error";
     print(error);
   });
 }

有几处 要注意一一对应:

public static final String FlutterToAndroidCHANNEL = "com.litngzhe.toandroid/plugin"; 
public static final String AndroidToFlutterCHANNEL= "com.litngzhe.toflutter/plugin";

new MethodChannel(flutterView, FlutterToAndroidCHANNEL)
new EventChannel(flutterView, AndroidToFlutterCHANNEL) 


//dart中 获取到插件与原生的交互通道 MethodChannel 中 涉及到的方法名要要统一
static const toAndroidPlugin = const MethodChannel('com.litngzhe.toandroid/plugin');
static const fromAndroiPlugin = const EventChannel('com.litngzhe.toflutter/plugin'); 

参考资料

FlutterDemo
https://juejin.im/post/5c74dbeee51d45708f2a20c6
https://github.com/flutter/flutter/issues/24679
https://github.com/flutter/flutter/issues/25147
https://mp.weixin.qq.com/s/OGbH3G3wHVTUt-0EJit8RA

Dart语言基础

发表于 2019-06-25 | 分类于 Hybrid Develop
  • 在Dart中,一切都是对象,一切对象都是class的实例,哪怕是数字类型、方法甚至null都是对象,所有的对象都是继承自Object
  • 虽然Dart是强类型语言,但变量类型是可选的,因为Dart可以自动推断变量类型
  • Dart支持范型,List<int>表示一个整型的数据列表,List<dynamic>则是一个对象的列表,其中可以装任意对象
  • Dart支持顶层方法(如main方法),也支持类方法或对象方法,同时你也可以在方法内部创建方法
  • Dart支持顶层变量,也支持类变量或对象变量
  • 跟Java不同的是,Dart没有public protected private等关键字,如果某个变量以下划线(_)开头,代表这个变量在库中是私有的
  • Dart中变量可以以字母或下划线开头,后面跟着任意组合的字符或数字

变量

以下代码是Dart中定义变量的方法:

main() {
  var a = 1;
  int b = 10;
  String s = "hello";
  dynamic c = 0.5;

  var count = 10; 
  final Num = count;  // final 只能赋值一次
  const Num1 = 10; // const赋值必须是编译时常量
}

你可以明确指定某个变量的类型,如int bool String,也可以用var或 dynamic来声明一个变量,Dart会自动推断其数据类型。

使用final或const,不要使用var或其他类型,一个被final修饰的变量只能被赋值一次,一个被const修饰的变量是一个编译时常量(const常量毫无疑问也是final常量)。final 要求变量只能初始化一次,并不要求赋的值一定是编译时常量;而 const 要求在声明时初始化,并且赋值必需为编译时常量。

注意:没有赋初值的变量都会有默认值null
注意:实例变量可以是final的但不能是const的

内建数据类型

Dart有如下几种内建的数据类型:

  • numbers
  • strings
  • booleans
  • lists(或者是arrays)
  • maps
  • runes(UTF-32字符集的字符)
  • symbols
    下面用一段代码来演示以上各类数据类型:

    main() {
      // numbers
      var a = 0;
      int b = 1;
      double c = 0.1;
    
      // strings
      var s1 = 'hello';
      String s2 = "world";
    
      // booleans
      var real = true;
      bool isReal = false;
    
      // lists
      var arr = [1, 2, 3, 4, 5];
      List<String> arr2 = ['hello', 'world', "123", "456"];
      List<dynamic> arr3 = [1, true, 'haha', 1.0];
    
      // maps
      var map = new Map();
      map['name'] = 'zhangsan';
      map['age'] = 10;
      Map m = new Map();
      m['a'] = 'a';
    
      //runes,Dart 中 使用runes 来获取UTF-32字符集的字符。String的 codeUnitAt and codeUnit属性可以获取UTF-16字符集的字符
      var clapping = '\u{1f44f}';
      print(clapping); // 打印的是拍手emoji的表情
    
      // symbols
      print(#s == new Symbol("s")); // true
    }
    

函数

Dart是一个面向对象的编程语言,所以即使是函数也是一个对象,也有一种类型Function,这就意味着函数可以赋值给某个变量或者作为参数传给另外的函数。虽然Dart推荐你给函数加上返回值,但是不加返回值的函数同样可以正常工作,另外你还可以用=>代替return语句,比如下面的代码:

// 声明返回值
int add(int a, int b) {
  return a + b;
}

// 不声明返回值
add2(int a, int b) {
  return a + b;
}

// =>是return语句的简写
add3(a, b) => a + b; 

main() {
  print(add(1, 2)); // 3
  print(add2(2, 3)); // 5
  print(add3(1, 2)); // 3
}

命名参数

sayHello({String name}) {
  print("hello, my name is $name");
}

sayHello2({name: String}) {
  print("hello, my name is $name");
}

main() {
  // 打印 hello, my name is zhangsan
  sayHello(name: 'zhangsan');

  // 打印 hello, my name is wangwu
  sayHello2(name: 'wangwu');
}

可以看到,定义命名参数时,你可以以 {type paramName} 或者 {paramName: type} 两种方式声明参数,而调用命名参数时,需要以 funcName(paramName: paramValue) 的形式调用。

命名参数的参数并不是必须的,所以上面的代码中,如果调用sayHello()不带任何参数,也是可以的,只不过最后打印出来的结果是:hello, my name is null,在Flutter开发中,你可以使用@required注解来标识一个命名参数,这代表该参数是必须的,你不传则会报错,比如下面的代码:

const Scrollbar({Key key, @required Widget child})

位置参数

使用中括号[]括起来的参数是函数的位置参数,代表该参数可传可不传,位置参数只能放在函数的参数列表的最后面,如下代码所示:

sayHello(String name, int age, [String hobby]) { // 位置参数可以有多个,比如[String a, int b]
  StringBuffer sb = new StringBuffer();
  sb.write("hello, this is $name and I am $age years old");
  if (hobby != null) {
    sb.write(", my hobby is $hobby");
  }
  print(sb.toString());
}

main() {
  // hello, this is zhangsan and I am 20 years old
  sayHello("zhangsan", 20);
  // hello, this is zhangsan and I am 20 years old, my hobby is play football
  sayHello("zhangsan", 20, "play football");
}

参数默认值

你可以为命名参数或者位置参数设置默认值,如下代码所示:

// 命名参数的默认值
int add({int a, int b = 3}) { // 不能写成:int add({a: int, b: int = 3})
  return a + b;
}

// 位置参数的默认值
int sum(int a, int b, [int c = 3]) {
  return a + b + c;
}

main()函数

不论在Dart还是Flutter中,必须都需要一个顶层的main()函数,它是整个应用的入口函数,main()函数的返回值是void,还有一个可选的参数,参数类型是List<String>。

函数作为一类对象

你可以将一个函数作为参数传给另一个函数,比如下面的代码:

printNum(int a) {
  print("$a");
}

main() {
  //  依次打印:
  //  1
  //  2
  //  3
  var arr = [1, 2, 3];
  arr.forEach(printNum);
}

你也可以将一个函数赋值给某个变量,比如下面的代码:

printNum(int a) {
  print("$a");
}

main() {
  var f1 = printNum;
  Function f2 = printNum;
  var f3 = (int a) => print("a = $a");
  f1(1);
  f2(2);
  f3(6);
}

匿名函数

大多数函数都是有名称的,比如main() printName()等,但是你也可以写匿名函数,如果你对Java比较熟悉,那下面的Dart代码你肯定也不会陌生:

test(Function callback) {
  callback("hello");
}

main() {
  test((param) {
    // 打印hello
    print(param);
  });
}

匿名函数类似于Java中的接口,往往在某个函数的参数为函数时使用到。

函数返回值

所有的函数都有返回值,如果没有指定return语句,那么该函数的返回值为null

运算符

Dart中的运算符与Java中的类似,比如++a a == b b ? a : b,但是也有一些与Java不太一样的运算符,下面用代码说明:

main() {
  // 与Java相同的运算符操作

  int a = 1;
  ++a;
  a++;
  var b = 1;
  print(a == b);  // false
  print(a * b); // 3
  bool real = false;
  real ? print('real') : print('not real'); // not real
  print(real && a == b); // false
  print(real || a == 3); // true
  print(a != 2); // true
  print(a <= b); // false
  var c = 9;
  c += 10;
  print("c = $c"); // c = 19
  print(1<<2); // 4

  // 与Java不太一样的运算符操作

  // is运算符用于判断一个变量是不是某个类型的数据
  // is!则是判断变量不是某个类型的数据
  var s = "hello";
  print(s is String); // true
  var num = 6;
  print(num is! String); // true

  // ~/才是取整运算符,如果使用/则是除法运算,不取整
  int k = 1;
  int j = 2;
  print(k / j); // 0.5
  print(k ~/ j); // 0

  // as运算符类似于Java中的cast操作,将一个对象强制类型转换
  (emp as Person).teach();

  // ??=运算符 如果 ??= 运算符前面的变量为null,则赋值,否则不赋值
  var param1 = "hello", param2 = null;
  param1 ??= "world";
  param2 ??= "world";
  print("param1 = $param1"); // param1 = hello
  print("param2 = $param2"); // param2 = world

  // ?.运算符
  var str1 = "hello world";
  var str2 = null;
  print(str1?.length); // 11
  print(str2?.length); // null 
  print(str2.length); // 报错
}

..运算符(级联操作)

如果你对Java中的建造者模式比较熟悉的话,Dart中的..运算符也很好理解,先看下面的代码:

class Person {
  eat() {
    print("I am eating...");
  }

  sleep() {
    print("I am sleeping...");
  }

  study() {
    print("I am studying...");
  }
}

main() {
  // 依次打印
  //  I am eating...
  //  I am sleeping...
  //  I am studying...
  new Person()..eat()
      ..sleep()
      ..study();
}

可以看到,使用..调用某个对象的方法(或者成员变量)时,返回值是这个对象本身,所以你可以接着使用..调用这个对象的其他方法,这不就类似于Java中的建造者模式,每次build某个属性时,都返回一个this对象吗。

typedefs

typedef 本质上为 一个方法签名提供了一个别名。官网上介绍TypeDef的时候有一句话说道:
“If we change the code to use explicit names and retain type information”
使用typedef会保留方法的类型信息。

考虑下面的代码,哪一个没有使用 typedef。

class SortedCollection {
  Function compare;

  SortedCollection(int f(Object a, Object b)) {
    compare = f;
  }
}

 // Initial, broken implementation.
 int sort(Object a, Object b) => 0;

main() {
  SortedCollection coll = new SortedCollection(sort);

  // All we know is that compare is a function,
  // but what type of function?
  assert(coll.compare is Function);
}

当 f 分配到 compare 的时候类型信息丢失了。f的类型是 (Object, Object) → int(→ 意味着返回的),然而compare 的类型是方法。如果我们使用显式的名字更改代码并保留类型信息,则开发者和工具都可以使用这些信息。

typedef int Compare(Object a, Object b);

class SortedCollection {
  Compare compare;

  SortedCollection(this.compare);
}

 // Initial, broken implementation.
 int sort(Object a, Object b) => 0;

main() {
  SortedCollection coll = new SortedCollection(sort);
  assert(coll.compare is Function);
  assert(coll.compare is Compare);
}

请注意目前 typedefs 仅限于函数类型。

因为 typedefs 是简单的别名,所以它提供了一种方法来检查任何函数的类型。比如:

typedef int Compare(int a, int b);

int sort(int a, int b) => a - b;

main() {
  assert(sort is Compare); // True!
}

控制流程

if / else switch for /while try / catch语句跟Java中都类似,try / catch语句可能稍有不同,下面用一段代码说明:

Dart中规定,if中当且仅当参数为bool类型true时,为真,其他全部都为假。

main() {
  // if else语句
  int score = 80;
  if (score < 60) {
    print("so bad!");
  } else if (score >= 60 && score < 80) {
    print("just so so!");
  } else if (score >= 80) {
    print("good job!");
  }

  // switch语句
  String a = "hello";
  // case语句中的数据类型必须是跟switch中的类型一致
  switch (a) {
    case "hello":
      print("haha");
      break;
    case "world":
      print("heihei");
      break;
    default:
      print("WTF");
  }

  // for语句
  List<String> list = ["a", "b", "c"];
  for (int i = 0; i < list.length; i++) {
    print(list[i]);
  }
  for (var i in list) {
    print(i);
  }
  // 这里的箭头函数参数必须用圆括号扩起来
  list.forEach((item) => print(item));

  // while语句
  int start = 1;
  int sum = 0;
  while (start <= 100) {
    sum += start;
    start++;
  }
  print(sum);

  // try catch语句
  try {
    print(1 ~/ 0);
  } catch (e) {
    // IntegerDivisionByZeroException
    print(e);
  }
  try {
    1 ~/ 0;
  } on IntegerDivisionByZeroException { // 捕获指定类型的异常
    print("error"); // 打印出error
  } finally {
    print("over"); // 打印出over
  }
}

rethrow

rethrow语句用来处理一个异常,同时希望这个异常能够被其它调用的部分使用:

final foo = '';

void misbehave() {
   try {
     foo = "1";
   } catch (e) {
     print('2');
     rethrow;// 如果不重新抛出异常,main函数中的catch语句执行不到
   }
}

void main() {
   try {
     misbehave();
   } catch (e) {
     print('3');
   }
}

类(Class)

类的定义与构造方法

Dart中的类没有访问控制,所以你不需要用private, protected, public等修饰成员变量或成员函数,一个简单的类如下代码所示:

class Person {
  String name;
  int age;
  String gender;
  Person(this.name, this.age, this.gender);
  sayHello() {
    print("hello, this is $name, I am $age years old, I am a $gender");
  }
}

上面的Person类中有3个成员变量,一个构造方法和一个成员方法,看起来比较奇怪的是Person的构造方法,里面传入的3个参数都是this.xxx,而且没有大括号{}包裹的方法体,这种语法是Dart比较独特而简洁的构造方法声明方式,它等同于下面的代码:

Person(String name, int age, String gender) {
    this.name = name;
    this.age = age;
    this.gender = gender;
}

要调用Person类的成员变量或成员方法,可以用下面的代码:

var p = new Person("zhangsan", 20, "male");
p.sayHello(); // hello, this is zhangsan, I am 20 years old, I am a male
p.age = 50;
p.gender = "female";
p.sayHello(); // hello, this is zhangsan, I am 50 years old, I am a female

类除了有跟类名相同的构造方法外,还可以添加命名的构造方法,如下代码所示:

class Point {
  num x, y;
  Point(this.x, this.y);
  // 类的命名构造方法
  Point.origin() {
    x = 0;
    y = 0;
  }
}

main() {
  // 调用Point类的命名构造方法origin()
  var p = new Point.origin();
  var p2 = new Point(1, 2);
}

Dart中使用extends关键字做类的继承,如果一个类只有命名的构造方法,在继承时需要注意,如下代码:

class Human {
  String name;
  Human.fromJson(Map data) {
    print("Human's fromJson constructor");
  }
}

class Man extends Human {
  Man.fromJson(Map data) : super.fromJson(data) {
    print("Man's fromJson constructor");
  }
}

由于Human类没有默认构造方法,只有一个命名构造方法fromJson,所以在Man类继承Human类时,需要调用父类的fromJson方法做初始化,而且必须使用Man.fromJson(Map data) : super.fromJson(data)这种写法,而不是像Java那样将super写到花括号中。Dart语言中,子类不会继承父类的命名构造函数。如果不显式提供子类的构造函数,系统就提供默认的构造函数。

有时候你仅仅只是在某个类的构造方法中,调用这个类的另一个构造方法,你可以这么写:

class Point {
  num x, y;
  Point(this.x, this.y);
  // 命名构造方法调用了默认的构造方法
  Point.alongXAxis(num x) : this(x, 0);
}

初始化列表

除了调用父类的构造函数,也可以通过初始化列表在子类的构造函数体前(大括号前)来初始化实例的变量值,使用逗号,分隔。如下所示:

class Point {
   num x;
   num y;

   Point(this.x, this.y);

   // 初始化列表在构造函数运行前设置实例变量。
   Point.fromJson(Map jsonMap)
   : x = jsonMap['x'],
     y = jsonMap['y'] {
      print('In Point.fromJson(): ($x, $y)');
   }
 }

注意:上述代码,初始化程序无法访问 this 关键字。

类的成员方法

一个类的成员方法是一个函数,为这个类提供某些行为。上面的代码中已经有了一些类的成员方法的定义,这些定义方式跟Java很类似,你可以为某个类的成员变量提供getter/setter方法,如下代码:

class Rectangle {
  num left, top, width, height;

  // 构造方法传入left, top, width, height几个参数
  Rectangle(this.left, this.top, this.width, this.height);

  // right, bottom两个成员变量提供getter/setter方法
  num get right => left + width;
  set right(num value) => left = value - width;
  num get bottom => top + height;
  set bottom(num value) => top = value - height;
}

抽象类和抽象方法

使用abstract修饰一个类,则这个类是抽象类,抽象类中可以有抽象方法和非抽象方法,抽象方法没有方法体,需要子类去实现,如下代码:

abstract class Doer {
  // 抽象方法,没有方法体,需要子类去实现
  void doSomething();
  // 普通的方法
  void greet() {
    print("hello world!");
  }
}

class EffectiveDoer extends Doer {
  // 实现了父类的抽象方法
  void doSomething() {
    print("I'm doing something...");
  }
}

运算符重载

Dart中有类似于C++中的运算符重载语法,比如下面的代码定义了一个向量类,重载了向量的+ -运算:

class Vector {
  num x, y;
  Vector(this.x, this.y);
  Vector operator +(Vector v) => new Vector(x + v.x, y + v.y);
  Vector operator -(Vector v) => new Vector(x - v.x, y - v.y);
  printVec() {
    print("x: $x, y: $y");
  }
}

main() {
  Vector v1 = new Vector(1, 2);
  Vector v2 = new Vector(3, 4);
  (v1 - v2).printVec(); // -2, -2
  (v1 + v2).printVec(); // 4, 6
}

枚举类

使用enum关键字定义一个枚举类,这个语法跟Java类似,如下代码:

enum Color { red, green, blue }

Mixins

mixins是一个重复使用类中代码的方式,比如下面的代码:

class A {
  a() {
    print("A's a()");
  }
}

class B {
  b() {
    print("B's b()");
  }
}

// 使用with关键字,表示类C是由类A和类B混合而构成
class C = A with B;

main() {
  C c = new C();
  c.a(); // A's a()
  c.b(); // B's b()
}

Dart和Java一样只支持单继承。而且Dart中没有和Java一样提供Interface字段去声明一个接口。如果想使用和Java接口一样的功能可以使用Mixins和implements两种方式,分别解释下两种方式:

  • Mixins : 指能够将另一个或多个类的功能添加到您自己的类中,而无需继承这些类
  • implements : 将一个类作为接口使用

    class A {
      void a() {
        print('a');
      }
    }
    
    class B implements A {
      @override
      void a() {
        print('override a');
      }
    }
    
    class C {
      void c() {
        print('c');
      }
    }
    
    class E {
      String e = 'eeee';
    }
    
    class D extends A with C, E {
      void c() {
        print('c is D');
      }
    
      void d() {
        c();
      }
    }
    

首先看B implements A,所以此时A相对于B来说就是一个接口,所以他要实现B中的方法。换句话说,Dart每个类都是接口

静态成员变量和静态成员方法

// 类的静态成员变量和静态成员方法
class Cons {
  static const name = "zhangsan";
  static sayHello() {
    print("hello, this is ${Cons.name}");
  }
}

main() {
  Cons.sayHello(); // hello, this is zhangsan
  print(Cons.name); // zhangsan
}

泛型(Generics)

Java和C++语言都有泛型,Dart语言也不例外,使用泛型有很多好处,比如:
正确指定泛型类型会产生更好的生成代码。
泛型可以减小代码的复杂度
Dart内置的数据类型List就是一个泛型数据类型,你可以往List中塞任何你想的数据类型比如整型、字符串、布尔值等

Dart库(Libraries)

Dart目前已经有很多的库提供给开发者,许多功能不需要开发者自己去实现,只需要导入对应的包即可,使用import语句来导入某个包,比如下面的代码:

import 'dart:html';

如果你想导入自己写的某个代码文件,使用相对路径即可,例如当前有一个demo.dart文件,跟该文件同级目录下有个util.dart文件,文件代码如下:

// util.dart文件内容

int add(int a, int b) {
  return a + b;
}

在demo.dart文件中如果要引用util.dart文件,使用下面的方式导入:

import './util.dart';

main() {
  print(add(1, 2));
}

你可以使用as关键字为导入的某个包设置一个前缀,或者说别名,比如下面的代码:

import 'package:lib1/lib1.dart';
import 'package:lib2/lib2.dart' as lib2;

// Uses Element from lib1.
Element element1 = Element();

// Uses Element from lib2.
lib2.Element element2 = lib2.Element();

你也可以在导入包时使用show hide关键字来导入某个包中的部分功能,比如下面的代码:

// 只导入foo
import 'package:lib1/lib1.dart' show foo;

// 导入除了foo的所有其他部分
import 'package:lib2/lib2.dart' hide foo;

导入包时使用deferred as可以让这个包懒加载,懒加载的包只会在该包被使用时得到加载,而不是一开始就加载,比如下面的代码:

import 'package:greetings/hello.dart' deferred as hello;

利用library identifier(库标识符)声明库:

// 声明库,名ballgame
library ballgame;

// 导入html库
import 'dart:html';

// ...代码从这里开始... 

关联文件与库

添加实现文件,把part fileUri放在有库的文件,其中fileURI是实现文件的路径。然后在实现文件中,添加部分标识符(part of identifier),其中标识符是库的名称。下面的示例使用的一部分,在三个文件来实现部分库。

第一个文件,ballgame.dart,声明球赛库,导入其他需要的库,并指定ball.dart和util.dart是此库的部分:

library ballgame;

import 'dart:html';
// ...其他导入在这里...

part 'ball.dart';
part 'util.dart';

// ...代码从这里开始...

第二、三个文件ball.dart、util.dart,实现了球赛库的一部分:

part of ballgame;

// ...代码从这里开始...

重新导出库(Re-exporting libraries)

可以通过重新导出部分库或者全部库来组合或重新打包库。例如,你可能有实现为一组较小的库集成为一个较大库。或者你可以创建一个库,提供了从另一个库方法的子集:

// In french.dart:
library french;

hello() => print('Bonjour!');
goodbye() => print('Au Revoir!');


// In togo.dart:
library togo;

import 'french.dart';
export 'french.dart' show hello;


// In another .dart file:
import 'togo.dart';

void main() {
    hello();   //print bonjour
    goodbye(); //FAIL
}

异步

Dart是单线程模型,也就没有了所谓的主线程/子线程之分。

Dart提供了类似ES7中的async await等异步操作,Dart库大多方法返回 Future 和 Stream 对象。这些方法是异步的:它们在设置一个可能的耗时操作(比如 I/O 操作)之后返回,而无需等待操作完成。

async和await往往是成对出现的,如果一个方法中有耗时的操作,你需要将这个方法设置成async,并给其中的耗时操作加上await关键字,如果这个方法有返回值,你需要将返回值塞到Future中并返回,如下代码所示:

Future checkVersion() async {
  var version = await lookUpVersion();
  // Do something with version
}

下面的代码使用Dart从网络获取数据并打印出来:

import 'dart:async';
import 'package:http/http.dart' as http;

Future<String> getNetData() async{
  http.Response res = await http.get("https://www.baidu.com");
  return res.body;
}

main() {
  getNetData().then((str) {
    print(str);
  });
}

Even-Looper

Dart是单线程模型,也就没有了所谓的主线程/子线程之分。
Dart也是Event-Looper以及Event-Queue的模型,所有的事件都是通过EventLooper的依次执行。而Dart的Event Loop就是:

  • 从EventQueue中获取Event
  • 处理Event
  • 直到EventQueue为空


而这些Event包括了用户输入,点击,Timer,文件IO等

单线程模型

一旦某个Dart的函数开始执行,它将执行到这个函数结束,也就是Dart的函数不会被其他Dart代码打断。

Dart中没有线程的概念,只有isolate,每个isolate都是隔离的,并不会共享内存。而一个Dart程序是在Main isolate的main函数开始,而在Main函数结束后,Main isolate线程开始一个一个(one by one)的开始处理Event Queue中的每一个Event。

Event Queue以及Microtask Queue

Dart中的Main Isolate只有一个Event Looper,但是存在两个Event Queue:Event Queue以及Microtask Queue。

Microtask Queue存在的意义是:希望通过这个Queue来处理稍晚一些的事情,但是在下一个消息到来之前需要处理完的事情。

当Event Looper正在处理Microtask Queue中的Event时候,Event Queue中的Event就停止了处理了,此时App不能绘制任何图形,不能处理任何鼠标点击,不能处理文件IO等等

Event-Looper挑选Task的执行顺序为:1.优先全部执行完Microtask Queue中的Event;2.直到Microtask Queue为空时,才会执行Event Queue中的Event:


Dart中只能知道Event处理的先后顺序,但是并不知道某个Event执行的具体时间点,因为它的处理模型是一个单线程循环,而不是基于时钟调度(即它的执行只是按照Event处理完,就开始循环下一个Event,而与Java中的Thread调度不一样,没有时间调度的概念),也就是我们既是指定另一个Delay Time的Task,希望它在预期的时间后开始执行,它有可能不会在那个时间执行,需要看是否前面的Event是否已经Dequeue。

异步任务调度

当有代码可以在后续任务执行的时候,有两种方式,通过dart:async这个Lib中的API即可:

  • 使用Future类,可以将任务加入到Event Queue的队尾
  • 使用scheduleMicrotask函数,将任务加入到Microtask Queue队尾

当使用EventQueue时,需要考虑清楚,尽量避免microtask queue过于庞大,否则会阻塞其他事件的处理:

使用Future

一般常用的Future构造函数:

new Future((){
    //  doing something
});

而一般常用的还有当有分治任务时,需要将一个大任务拆成很多小任务一步步执行时,就需要使用到Future.then函数来拆解任务

void main(){
new Future(() => futureTask)  //  异步任务的函数
        .then((m) => "futueTask execute result:$m")  //   任务执行完后的子任务
        .then((m) => m.length)  //  其中m为上个任务执行完后的返回的结果
        .then((m) => printLength(m))
        .whenComplete(() => whenTaskCompelete);  //  当所有任务完成后的回调函数
}

int futureTask() {
    return 21; 
}

void printLength(int length) {
    print("Text Length:$length");
}

void whenTaskCompelete() {
    print("Task Complete");
}

当任务需要延迟执行时,可以使用new Future.delay来将任务延迟执行,而如上所述,只有当Main isolate的Event Queue处于Idle的状态时,才会延迟1s执行,否则等待的时间会比1s长很多

new Future.delayed(const Duration(seconds: 1), () => futureTask);

当需要做动画的时候,不要使用Future,而需要使用animateFrame

PS:

  • Future中的then并没有创建新的Event丢到Event Queue中,而只是一个普通的Function Call,在FutureTask执行完后,立即开始执行
  • 当Future在then函数之前已经执行完成了,则会创建一个task,将该task的添加到microtask queue中,并且该任务将会执行通过then传入的函数
  • Future只是创建了一个Event,将Event插入到了Event Queue的队尾
  • 使用Future.value构造函数的时候,就会和第二条一样,创建Task丢到microtask Queue中执行then传入的函数
  • Future.sync构造函数执行了它传入的函数之后,也会立即创建Task丢到microtask Queue中执行

使用scheduleMicrotask

在最顶层的调用关系中,使用该函数即可:

async.scheduleMicrotask(() => microtask());

void microtask(){
  //  doing something
}

使用isolate以及Worker

当有计算很繁重的任务时,则需要使用isolate或者Worker来执行,以保持App对用户操作的及时响应。Isolate的实现可能是一个单独的线程,或者一个单独的进程,需要看Dart VM是如何实现的。

参考资料

https://www.jianshu.com/p/06aebcad0543
https://www.jianshu.com/p/9e5f4c81cc7d
Flutter–Dart中的异步
https://dart.dev/articles/archive/event-loop

Flutter知识点

发表于 2019-06-25 | 分类于 Hybrid Develop

Views

在Flutter中,View相当于是Widget。Widget仅支持一帧,并且在每一帧上,Flutter的框架都会创建一个Widget实例树(相当于一次性绘制整个界面)。在Flutter中的widget是不可变的,这允许widget变得超级轻量。

Widget状态

在Flutter中Widget是不可变的,不会直接更新,而必须使用Widget的状态。这是Stateful和Stateless widget的概念的来源。一个Stateless Widget就像它的名字,是一个没有状态信息的widget。

这里要注意的重要一点是无状态和有状态widget的核心特性是相同的。每一帧它们都会重新构建,不同之处在于StatefulWidget有一个State对象,它可以跨帧存储状态数据并恢复它。

如果你有疑问,那么要记住这个规则:如果一个widget发生了变化(例如用户与它交互),它就是有状态的。但是,如果一个子widget对变化做出反应,而其父widget对变化没有反应,那么包含的父widget仍然可以是无状态的widget。

Widget编写

在Android中,您通过XML编写布局,但在Flutter中,使用widget树来编写布局。
可以查看Flutter所提供的所有布局: Flutter widget layout

在Android中,您可以从父级控件调用addChild或removeChild以动态添加或删除View。 在Flutter中,因为widget是不可变的,所以没有addChild。相反,您可以传入一个函数,该函数返回一个widget给父项,并通过布尔值控制该widget的创建。

在Flutter中,可以通过动画库给widget添加动画,将widget包装到Animation中。与Android相似,在Flutter中,您有一个AnimationController和一个Interpolator, 它是Animation类的扩展,例如CurvedAnimation。您将控制器和动画传递到AnimationWidget中,并告诉控制器启动动画。
See https://flutter.io/widgets/animation/ and https://flutter.io/tutorials/animation for more specific details.

Flutter有两个类可以帮助您绘制画布,CustomPaint和CustomPainter,它们实现您的算法以绘制到画布。
在这个人气较高的的StackOverFlow答案中,您可以看到签名painter是如何实现的:请参阅https://stackoverflow.com/questions/46241071/create-signature-area-for-mobile-app-in-dart-flutter

如何构建自定义 Widgets

在Flutter中,一个自定义widget通常是通过组合其它widget来实现的,而不是继承。

我们来看看如何构建持有一个label的CustomButton。这是通过将Text与RaisedButton组合来实现的,而不是扩展RaisedButton并重写其绘制方法实现:

class CustomButton extends StatelessWidget {
  final String label;
  CustomButton(this.label);

  @override
  Widget build(BuildContext context) {
    return new RaisedButton(onPressed: () {}, child: new Text(label));
  }
}

Intents

Flutter不具有Intents的概念,但如果需要的话,Flutter可以通过Native整合来触发Intents。

要在Flutter中切换屏幕,您可以访问路由以绘制新的Widget。 管理多个屏幕有两个核心概念和类:Route 和 Navigator。Route是应用程序的“屏幕”或“页面”的抽象(可以认为是Activity), Navigator是管理Route的Widget。Navigator可以通过push和pop route以实现页面切换。

在Flutter中,可以将具有指定Route的Map传递到顶层MaterialApp实例

void main() {
  runApp(new MaterialApp(
    home: new MyAppHome(), // becomes the route named '/'
    routes: <String, WidgetBuilder> {
      '/a': (BuildContext context) => new MyPage(title: 'page A'),
      '/b': (BuildContext context) => new MyPage(title: 'page B'),
      '/c': (BuildContext context) => new MyPage(title: 'page C'),
    },
  ));
}

然后,您可以通过Navigator来切换到命名路由的页面。

Navigator.of(context).pushNamed('/b');

Intents的另一个主要的用途是调用外部组件,如Camera或File picker。为此,您需要和native集成(或使用现有的库)

处理外部传入的Intents

Flutter可以通过直接与Android层通信并请求共享的数据来处理来自Android的Intents

在这个例子中,我们注册文本共享intent,所以其他应用程序可以共享文本到我们的Flutter应用程序

这个应用程序的基本流程是我们首先处理Android端的共享文本数据,然后等待Flutter请求数据,然后通过MethodChannel发送。

首先在在AndroidManifest.xml中注册我们想要处理的intent:

<activity
       android:name=".MainActivity"
       android:launchMode="singleTop"
       android:theme="@style/LaunchTheme"
       android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|layoutDirection"
       android:hardwareAccelerated="true"
       android:windowSoftInputMode="adjustResize">
       <!-- This keeps the window background of the activity showing
            until Flutter renders its first frame. It can be removed if
            there is no splash screen (such as the default splash screen
            defined in @style/LaunchTheme). -->
       <meta-data
           android:name="io.flutter.app.android.SplashScreenUntilFirstFrame"
           android:value="true" />
       <intent-filter>
           <action android:name="android.intent.action.MAIN"/>
           <category android:name="android.intent.category.LAUNCHER"/>
       </intent-filter>
       <intent-filter>
           <action android:name="android.intent.action.SEND" />
           <category android:name="android.intent.category.DEFAULT" />
           <data android:mimeType="text/plain" />
       </intent-filter>
   </activity>

然后,在MainActivity中处理intent,一旦我们从intent中获得共享文本数据,我们就会持有它,直到Flutter在完成准备就绪时请求它。

public class MainActivity extends FlutterActivity {
    String sharedText;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        GeneratedPluginRegistrant.registerWith(this);
        Intent intent = getIntent();
        String action = intent.getAction();
        String type = intent.getType();

        if (Intent.ACTION_SEND.equals(action) && type != null) {
            if ("text/plain".equals(type)) {
                handleSendText(intent); // Handle text being sent
            }
        }

        new MethodChannel(getFlutterView(), "app.channel.shared.data").setMethodCallHandler(new MethodChannel.MethodCallHandler() {
            @Override
            public void onMethodCall(MethodCall methodCall, MethodChannel.Result result) {
                if (methodCall.method.contentEquals("getSharedText")) {
                    result.success(sharedText);
                    sharedText = null;
                }
            }
        });
    }


    void handleSendText(Intent intent) {
        sharedText = intent.getStringExtra(Intent.EXTRA_TEXT);
    }
}

最后,在Flutter中,在渲染Flutter视图时请求数据:

class _SampleAppPageState extends State<SampleAppPage> {
  static const platform = const MethodChannel('app.channel.shared.data');
  String dataShared = "No data";

  @override
  void initState() {
    super.initState();
    getSharedText();
  }

  @override
  Widget build(BuildContext context) {
    return new Scaffold(body: new Center(child: new Text(dataShared)));
  }

  getSharedText() async {
    var sharedData = await platform.invokeMethod("getSharedText");
    if (sharedData != null) {
      setState(() {
        dataShared = sharedData;
      });
    }
  }
}

startActivityForResult 在Flutter中等价于什么

处理Flutter中所有路由的Navigator类可用于从已经push到栈的路由中获取结果。 这可以通过等待push返回的Future来完成。例如,如果您要启动让用户选择其位置的位置的路由,则可以执行以下操作:

Map coordinates = await Navigator.of(context).pushNamed('/location');

然后在你的位置路由中,一旦用户选择了他们的位置,你可以将结果”pop”出栈:

Navigator.of(context).pop({"lat":43.821757,"long":-79.226392});

异步UI

runOnUiThread 在Flutter中等价于什么

Dart是单线程执行模型,支持Isolates(在另一个线程上运行Dart代码的方式)、事件循环和异步编程。 除非您启动一个Isolate,否则您的Dart代码将在主UI线程中运行,并由事件循环驱动(译者语:和JavaScript一样)。

AsyncTask和IntentService在Flutter中等价于什么

由于Flutter是单线程的,运行一个事件循环(如Node.js),所以您不必担心线程管理或者使用AsyncTasks、IntentServices。

要异步运行代码,可以将函数声明为异步函数,并在该函数中等待这个耗时任务

loadData() async {
  String dataURL = "https://jsonplaceholder.typicode.com/posts";
  http.Response response = await http.get(dataURL);
  setState(() {
    widgets = JSON.decode(response.body);
  });
}

这就是典型的进行网络或数据库调用的方式

在Android上,当您继承AsyncTask时,通常会覆盖3个方法,OnPreExecute、doInBackground和onPostExecute。 在Flutter中没有这种模式的等价物,因为您只需等待一个长时间运行的函数,而Dart的事件循环将负责其余的事情。但是,有时您可能需要处理大量数据,导致UI可能会挂起。在这种情况下,与AsyncTask一样,在Flutter中,可以利用多个CPU内核来执行耗时或计算密集型任务。这是通过使用Isolates来完成的。

Isolate是一个独立的执行线程,它运行时不会与主线程共享任何内存。这意味着你不能从该线程访问变量或通过调用setState来更新你的UI。

OkHttp在Flutter中等价于什么

当使用受欢迎的“http”package时,Flutter进行网络信非常简单。

虽然“http” package 没有实现OkHttp的所有功能,但“http” package 抽象出了许多常用的API,可以简单有效的发起网络请求。

您可以通过在pubspec.yaml中添加依赖项来使用它

dependencies:
  ...
  http: '>=0.11.3+12'

在Android中,当您执行耗时任务时,通常会显示进度指示器。在Flutter中,这可以通过渲染Progress Indicator widget来实现。您可以通过编程方式显示Progress Indicator , 通过布尔值通知Flutter在耗时任务发起之前更新其状态。

项目结构和资源

在哪里存储分辨率相关的图片文件? HDPI/XXHDPI

Flutter遵循像iOS这样简单的3种分辨率格式: 1x, 2x, and 3x.

创建一个名为images的文件夹,并为每个图像文件生成一个@2x和@3x文件,并将它们放置在如下这样的文件夹中:

…/my_icon.png
…/2.0x/my_icon.png
…/3.0x/my_icon.png

然后,您需要在pubspec.yaml文件中声明这些图片:

assets:
 - images/a_dot_burr.jpeg
 - images/a_dot_ham.jpeg

然后您可以使用AssetImage访问您的图像

return new AssetImage("images/a_dot_burr.jpeg");

在哪里存储字符串? 如何存储不同的语言

目前,最好的做法是创建一个名为Strings的类

class Strings{
  static String welcomeMessage = "Welcome To Flutter";
}

然后在你的代码中,你可以像访问你的字符串一样:

new Text(Strings.welcomeMessage)

Flutter对Android的可访问性提供了基本的支持,虽然这个功能正在进行中。

鼓励Flutter开发者使用intl package进行国际化和本地化

Android Gradle vs Flutter pubspec.yaml

在Android中,您可以在Gradle文件来添加依赖项。

在Flutter中,虽然在Flutter项目中的Android文件夹下有Gradle文件,但只有在添加平台相关所需的依赖关系时才使用这些文件。 否则,应该使用pubspec.yaml声明用于Flutter的外部依赖项。

发现好的flutter packages的一个好地方 Pub

Activities 和 Fragments

在Flutter中,这两个概念都等同于Widget。

如何监听Android Activity生命周期事件

在Android中,您可以覆盖Activity的方法来捕获Activity的生命周期回调。

在Flutter中您可以通过挂接到WidgetsBinding观察并监听didChangeAppLifecycleState更改事件来监听生命周期事件

您可以监听到的生命周期事件是

  • resumed - 应用程序可见并响应用户输入。这是来自Android的onResume
  • inactive - 应用程序处于非活动状态,并且未接收用户输入。此事件在Android上未使用,仅适用于iOS
  • paused - 应用程序当前对用户不可见,不响应用户输入,并在后台运行。这是来自Android的暂停
  • suspending - 该应用程序将暂时中止。这在iOS上未使用

    class _LifecycleWatcherState extends State with WidgetsBindingObserver {

    AppLifecycleState _lastLifecyleState;
    
    @override
    void initState() {
      super.initState();
      WidgetsBinding.instance.addObserver(this);
    }
    
    @override
    void dispose() {
      WidgetsBinding.instance.removeObserver(this);
      super.dispose();
    }
    
    @override
    void didChangeAppLifecycleState(AppLifecycleState state) {
      setState(() {
        _lastLifecyleState = state;
      });
    }
    
    @override
    Widget build(BuildContext context) {
      if (_lastLifecyleState == null)
        return new Text('This widget has not observed any lifecycle changes.', textDirection: TextDirection.ltr);
      return new Text('The most recent lifecycle state this widget observed was: $_lastLifecyleState.',
          textDirection: TextDirection.ltr);
    }
    

    }

Layouts

LinearLayout在Flutter中相当于什么

在Android中,使用LinearLayout来使您的控件呈水平或垂直排列。在Flutter中,您可以使用Row或Co​​lumn来实现相同的结果:

@override
Widget build(BuildContext context) {
  return new Row(
    mainAxisAlignment: MainAxisAlignment.center,
    children: <Widget>[
      new Text('Row One'),
      new Text('Row Two'),
      new Text('Row Three'),
      new Text('Row Four'),
    ],
  );
}

RelativeLayout在Flutter中等价于什么

RelativeLayout用于使widget相对于彼此位置排列。在Flutter中,有几种方法可以实现相同的结果

您可以通过使用Column、Row和Stack的组合来实现RelativeLayout的效果。您可以为widget构造函数指定相对于父组件的布局规则。

一个在Flutter中构建RelativeLayout的好例子,请参考在StackOverflow上: https://stackoverflow.com/questions/44396075/equivalent-of-relativelayout-in -flutter

ScrollView在Flutter中等价于什么

在Android中,ScrollView允许您包含一个子控件,以便在用户设备的屏幕比控件内容小的情况下,使它们可以滚动。

在Flutter中,最简单的方法是使用ListView。但在Flutter中,一个ListView既是一个ScrollView,也是一个Android ListView。

手势检测和触摸事件处理

如何将一个onClick监听器添加到Flutter中的widget

在Flutter中,添加触摸监听器有两种方法:

  1. 如果Widget支持事件监听,则可以将一个函数传递给它并进行处理。例如,RaisedButton有一个onPressed参数:

    @override
    Widget build(BuildContext context) {
      return new RaisedButton(
          onPressed: () {
            print("click");
          },
          child: new Text("Button"));
    }
    
  2. 如果Widget不支持事件监听,则可以将该Widget包装到GestureDetector中,并将处理函数传递给onTap参数:

    class SampleApp extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return new Scaffold(
            body: new Center(
          child: new GestureDetector(
            child: new FlutterLogo(
              size: 200.0,
            ),
            onTap: () {
              print("tap");
            },
          ),
        ));
      }
    }
    

如何处理widget上的其他手势

使用GestureDetector,可以监听多种手势,例如:

Tap

onTapDown
onTapUp
onTap
onTapCancel

Double tap

onDoubleTap 用户快速连续两次在同一位置轻敲屏幕.

长按

onLongPress

垂直拖动

onVerticalDragStart
onVerticalDragUpdate
onVerticalDragEnd

水平拖拽

onHorizontalDragStart
onHorizontalDragUpdate
onHorizontalDragEnd

Listview & Adapter

ListView在Flutter中相当于什么

在Flutter中,ListView就是一个ListView!

在Android ListView中,您可以创建一个适配器,然后您可以将它传递给ListView,该适配器将使用适配器返回的内容来展示每一行。 然而,你必须确保在合适的时机回收行,否则,你会得到各种疯狂的视觉和内存问题。

在Flutter中,由于Flutter的不可变的widget模型,将一个Widgets列表传递给的ListView,而Flutter将负责确保它们快速平滑地滚动。

怎么知道哪个列表项被点击

在Android中,ListView有一个方法’onItemClickListener’来确定哪个列表项被点击。 Flutter中可以更轻松地通过您传入的处理回调来进行操作:

_getListData() {
   List<Widget> widgets = [];
   for (int i = 0; i < 100; i++) {
     widgets.add(new GestureDetector(
       child: new Padding(
           padding: new EdgeInsets.all(10.0),
           child: new Text("Row $i")),
       onTap: () {
         print('row tapped');
       },
     ));
   }
   return widgets;
 }

如何动态更新ListView

需要更新适配器并调用notifyDataSetChanged。在Flutter中,如果setState()中更新widget列表,您会发现没有变化, 这是因为当setState被调用时,Flutter渲染引擎会遍历所有的widget以查看它们是否已经改变。 当遍历到你的ListView时,它会做一个==运算,以查看两个ListView是否相同,因为没有任何改变,因此没有更新数据。

要更新您的ListView,然后在setState中创建一个新的List()并将所有旧数据复制到新列表中。这是实现更新的简单方法(译者语:此时状态改变,ListView被重新构建)

然而,推荐的方法是使用ListView.Builder。当您拥有动态列表或包含大量数据的列表时,此方法非常有用。 这实际上相当于在Android上使用RecyclerView,它会自动为您回收列表元素:

@override
 Widget build(BuildContext context) {
   return new Scaffold(
       appBar: new AppBar(
         title: new Text("Sample App"),
       ),
       body: new ListView.builder(
           itemCount: widgets.length,
           itemBuilder: (BuildContext context, int position) {
             return getRow(position);
           }));
 }

Widget getRow(int i) {
   return new GestureDetector(
     child: new Padding(
         padding: new EdgeInsets.all(10.0),
         child: new Text("Row $i")),
     onTap: () {
       setState(() {
         widgets.add(getRow(widgets.length + 1));
         print('row $i');
       });
     },
   );
 }

我们不是创建一个“新的ListView”,而是创建一个新的ListView.builder,它接受两个参数,即列表的初始长度和一个ItemBuilder函数。ItemBuilder函数非常类似于Android适配器中的getView函数,它需要一个位置并返回要为该位置渲染的行。

最后,但最重要的是,如果您注意到onTap函数,在里面,我们不会再重新创建列表,而只是添加新元素到列表。

使用 Text

如何在 Text widget上设置自定义字体

在Android SDK(从Android O开始)中,创建一个Font资源文件并将其传递到TextView的FontFamily参数中。

在Flutter中,首先你需要把你的字体文件放在项目文件夹中(最好的做法是创建一个名为assets的文件夹)

接下来在pubspec.yaml文件中,声明字体:

fonts:
   - family: MyCustomFont
     fonts:
       - asset: fonts/MyCustomFont.ttf
       - style: italic

最后,将字体应用到Text widget:

@override
Widget build(BuildContext context) {
  return new Scaffold(
    appBar: new AppBar(
      title: new Text("Sample App"),
    ),
    body: new Center(
      child: new Text(
        'This is a custom font text',
        style: new TextStyle(fontFamily: 'MyCustomFont'),
      ),
    ),
  );
}

如何在Text上定义样式

Text的样式参数需要一个TextStyle对象,您可以在其中自定义许多参数。

表单输入

Input的”hint”在flutter中相当于什么

在Flutter中,您可以通过向Text Widget的装饰构造函数参数添加InputDecoration对象,轻松地为输入框显示占位符文本

body: new Center(
  child: new TextField(
    decoration: new InputDecoration(hintText: "This is a hint"),
  )
)

如何显示验证错误

就像您如何使用“hint”一样,您可以将InputDecoration对象传递给Text的装饰构造函数。

但是,您不希望首先显示错误,并且通常会在用户输入一些无效数据时显示该错误。这可以通过更新状态并传递一个新的InputDecoration对象来完成:

class _SampleAppPageState extends State<SampleAppPage> {
  String _errorText;

  @override
  Widget build(BuildContext context) {
    return new Scaffold(
      appBar: new AppBar(
        title: new Text("Sample App"),
      ),
      body: new Center(
        child: new TextField(
          onSubmitted: (String text) {
            setState(() {
              if (!isEmail(text)) {
                _errorText = 'Error: This is not an email';
              } else {
                _errorText = null;
              }
            });
          },
          decoration: new InputDecoration(hintText: "This is a hint", errorText: _getErrorText()),
        ),
      ),
    );
  }

  _getErrorText() {
    return _errorText;
  }

  bool isEmail(String em) {
    String emailRegexp =
        r'^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$';

    RegExp regExp = new RegExp(p);

    return regExp.hasMatch(em);
  }
}

Flutter 插件

如何使用 GPS sensor
要访问GPS传感器,您可以使用社区插件 https://pub.dartlang.org/packages/location

如何访问相机
访问相机的流行社区插件是 https://pub.dartlang.org/packages/image_picker

如何使用Facebook登陆
要访问Facebook Connect功能,您可以使用 https://pub.dartlang.org/packages/flutter_facebook_connect .

如何构建自定义集成Native功能
如果有Flutter或其社区插件缺失的平台特定功能,那么您可以自己按照以下教程构建https://flutterchina.club/developing-packages/
简而言之,Flutter的插件架构就像在Android中使用Event bus一样:您可以发出消息并让接收者进行处理并将结果返回给您,在这种情况下,接收者将是iOS或Android。

如何在我的Flutter应用程序中使用NDK
自定义插件首先会与Android应用程序通信,您可以在其中调用native标记的函数。一旦Native完成了相应操作,就可以将响应消息发回给Flutter并呈现结果。

主题

Flutter很好的实现了一个美丽的Material Design,它会满足很多样式和主题的需求。 与Android中使用XML声明主题不同,在Flutter中,您可以通过顶层widget声明主题。

MaterialApp是一个方便的widget,它包装了许多Material Design应用通常需要的widget,它通过添加Material特定功能构建在WidgetsApp上。

如果你不想使用Material Components,那么你可以声明一个顶级widget-WidgetsApp,它是一个便利的类,它包装了许多应用程序通常需要的widget。

要自定义Material Components的颜色和样式,您可以将ThemeData对象传递到MaterialApp widget中,例如在下面的代码中,您可以看到主色板设置为蓝色,并且所有选择区域的文本颜色都应为红色。

class SampleApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return new MaterialApp(
      title: 'Sample App',
      theme: new ThemeData(
        primarySwatch: Colors.blue,
        textSelectionColor: Colors.red
      ),
      home: new SampleAppPage(),
    );
  }
}

Scaffold 是 Material library 中提供的一个widget, 它提供了默认的导航栏、标题和包含主屏幕widget树的body属性。widget树可以很复杂。

数据库和本地存储

如何在Flutter中访问Shared Preferences ?

在Flutter中,您可以通过使用插件Shared_Preferences来访问此功能

这个插件包装了Shared Preferences和NSUserDefaults(与iOS相同)的功能

import 'package:flutter/material.dart';
import 'package:shared_preferences/shared_preferences.dart';

void main() {
  runApp(
    new MaterialApp(
      home: new Scaffold(
        body: new Center(
          child: new RaisedButton(
            onPressed: _incrementCounter,
            child: new Text('Increment Counter'),
          ),
        ),
      ),
    ),
  );
}

_incrementCounter() async {
  SharedPreferences prefs = await SharedPreferences.getInstance();
  int counter = (prefs.getInt('counter') ?? 0) + 1;
  print('Pressed $counter times.');
  prefs.setInt('counter', counter);
}

如何在Flutter中访问SQLite

在Android中,您可以使用SQLite存储,通过SQL查询的结构化数据。

在Flutter中,您可以使用SQFlite插件来访问SQFlite此功能

通知

在Android中,您可以使用Firebase云消息传递为您的应用设置推送通知。

在Flutter中,您可以使用Firebase_Messaging插件访问此功能

注意:在中国无法使用Firebase服务。

yaml

YAML是一个类似 XML的标记语言。YAML强调以数据为中心,并不是以标识语言为重点

规范:

  • 大小写敏感
  • 缩进代表层级,使用空格,默认2个空格(flutter工具做了处理,tab也可以)
  • #表示注释内容
  • : 表示键值对,注意后面要空格
  • {} 表示键值表
  • 表示列表,注意后面要空格
  • [] 表示数组,注意每项之间有空格
  • ? 表示复杂的键

    # 依赖库
    dependencies:
      flutter:
        sdk: flutter
      cupertino_icons: ^0.1.2
    
    flutter:
      #使用Material图标
      uses-material-design: true
      #assets文件
      assets:
        - images/1.png
        - images/2.gif
      #字体样式
      fonts:
         #family与fonts是一个整体,列表的一项
         - family: Schyler
           fonts:
             - asset: fonts/Schyler-Regular.ttf
             - asset: fonts/Schyler-Italic.ttf
               style: italic
         - family: Trajan Pro
           fonts:
             - asset: fonts/TrajanPro.ttf
             - asset: fonts/TrajanPro_Bold.ttf
               weight: 700
    

-可转化为[],:也可以转化为{},转化后类似json

dependencies: {flutter: {sdk: flutter}, cupertino_icons: ^0.1.2}
assets: [images/1.png, images/2.gif]

参考资料

Flutter for Android 开发者

Redux概念

发表于 2019-06-25 | 分类于 Hybrid Develop

Redux是一个状态管理器。

Redux最主要是用作应用状态的管理。简言之,Redux用一个单独的常量状态树(对象)保存这一整个应用的状态,这个对象不能直接被改变。当一些数据变化了,一个新的对象就会被创建(使用actions和reducers)。

Redux核心概念

  • actions
  • store
  • reducers

Actions
简单地,Actions就是事件。Actions传递来自这个应用(用户接口,内部事件比如API调用和表单提交)的数据给store。store只获取来自Actions的信息。内部Actions就是简单的具有一个type属性(通常是常量)的JavaScript对象,这个对象描述了action的类型以及传递给store的负载信息:

{
    type: LOGIN_FORM_SUBMIT,
    payload: {username: 'alex', password: '123456'}
}

Reducers
在函数式JavaScript中reducer基于数组reduce方法,接收一个回调(reducer)让你从多个值中获得单个值,整数和,或者一个一系列值的累积。在Redux中,reducer就是获得这个应用的当前状态和事件然后返回一个新状态的函数。理解reducer是怎样工作的至关重要,因为它们完成大部分工作。这是一个非常简单的reducer,通过获取当前state和一个action作为参数,再返回下一个state:

function handleAuth(state, action) {
    return _.assign({}, state, {
        auth: action.payload  
    });
}

对于更多复杂的项目,使用Redux提供的combineReducers()实例是必要的(推荐)。它把在这个应用中所有的reducer结合在一起成为单个索引reducer。每一个reducer负责它自己那部分应用的状态,这个状态参数和其他reducer的不一样。combineReducers()实例使文件结构更容易维护。
如果一个对象(state)只改变一些值,Redux就创建一个新的对象,那些没有改变的值将会指向旧的对象而且新的值将会被创建。这对性能是极好的。为了让它更有效率你可以添加 Immutable.js

const rootReducer = combineReducers({
    handleAuth: handleAuth,
    editProfile: editProfile,
    changePassword: changePassword
});

Store
Store对象保存应用的状态并提供一些帮助方法来存取状态,分发状态以及注册监听。全部state由一个store来表示。任何action通过reducer返回一个新的状态对象。这就使得Redux非常简单以及可预测。

参考资料

完全理解 redux(从零实现一个 redux)

Android IBinder机制

发表于 2019-05-30 | 分类于 Android知识点

Android系统Binder机制中的四个组件Client、Server、Service Manager和Binder驱动程序的关系如下图所示:

  1. Client、Server和Service Manager实现在用户空间中,Binder驱动程序实现在内核空间中

  2. Binder驱动程序和Service Manager在Android平台中已经实现,开发者只需要在用户空间实现自己的Client和Server

  3. Binder驱动程序提供设备文件/dev/binder与用户空间交互,Client、Server和Service Manager通过open和ioctl文件操作函数与Binder驱动程序进行通信

  4. Client和Server之间的进程间通信通过Binder驱动程序间接实现

  5. Service Manager是一个守护进程,用来管理Server,并向Client提供查询Server接口的能力

Android 整体架构

我们先来大概看下 Android 这座大山的整体轮廓。我们先从 Android 的整体架构来看看 Binder 是处于什么地位,这张图引自 Android 项目开源网站:https://source.android.com:

从下往上依次为

  • 内核层:Linux 内核和各类硬件设备的驱动,这里需要注意的是,Binder IPC 驱动也是在这一层实现,比较特殊
  • 硬件抽象层:封装「内核层」硬件驱动,提供可供「系统服务层」调用的统一硬件接口
  • 系统服务层:提供核心服务,并且提供可供「应用程序框架层」调用的接口
  • Binder IPC 层:作为「系统服务层」与「应用程序框架层」的 IPC 桥梁,互相传递接口调用的数据,实现跨进层的通讯
  • 应用程序框架层:这一层可以理解为 Android SDK,提供四大组件,View 绘制体系等平时开发中用到的基础部件

在一个大的项目里面,分层是非常重要的,处于最底层的接口最具有「通用性」,接口粒度最细,越往上层通用性降低。理论上来说上面的每一层都可以「开放」给开发者调用,例如开发者可以直接调用硬件抽象层的接口去操作硬件,或者直接调用系统服务层中的接口去直接操作系统服务,甚至是像 Windows 开发一样,开发者可以在内核层写程序,运行在内核中。不过开放带来的问题就是开发者权利太大,对于系统的稳定性是没有任何好处的,一个病毒制作者写了一个内核层的病毒,系统也许永远也起不来了。所以谷歌的做法是将开发者的权利收拢到了「应用程序框架层」,开发者只能调用这一层提供的接口。

上面的层次中,内核层与硬件抽象层均用 C/C++ 实现,系统服务层是以 Java 实现,硬件抽象层编译为 so 文件,以 JNI 的形式供系统服务层使用。系统服务层中的服务随系统的启动而启动,只要不关机,就会一直运行。这些服务干什么事情呢?其实很简单,就是完成一个手机该有的核心功能如短信的收发管理、电话的接听、挂断以及应用程序的包管理、Activity 的管理等等。每一个服务均运行在一个独立进程中,因为是以 Java 实现,所以本质上来说就是运行在一个独立进程的 Dalvik 虚拟机中。问题就来了,开发者的 APP 运行在一个新的进程空间,如何调用到系统服务层中的接口呢?答案是 IPC(Inter-Process Communication),进程间通讯,缩写与 RPC(Remote Procedure Call)是不一样的,实现原理也是不一样的。每一个系统服务在应用层序框架层都有一个 Manager 与之对应,方便开发者调用其相关的功能,具体关系大致如下

IPC 的方式有很多种,例如 socket、共享内存、管道、消息队列等等,我们就不去深究为何要使用 Binder 而不使用其他方式去做,到目前为止,这座大山的面目算是有个大概的轮廓了。

小结

  • Android 从下而上分了内核层、硬件抽象层、系统服务层、Binder IPC 层、应用程序框架层
  • Android 中「应用程序框架层」以 SDK 的形式开放给开发者使用,「系统服务层」中的核心服务随系统启动而运行,通过应用层序框架层提供的 Manager 实时为应用程序提供服务调用。系统服务层中每一个服务运行在自己独立的进程空间中,应用程序框架层中的 Manager 通过 Binder IPC 的方式调用系统服务层中的服务。

Binder IPC 的架构

下面我们就来看看 Binder IPC 的架构是怎样的

Binder IPC 属于 C/S 结构,Client 部分是用户代码,用户代码最终会调用 Binder Driver 的 transact 接口,Binder Driver 会调用 Server,这里的 Server 与 service 不同,可以理解为 Service 中 onBind 返回的 Binder 对象,请注意区分下:

  • Client:用户需要实现的代码,如 AIDL 自动生成的接口类
  • Binder Driver:在内核层实现的 Driver
  • Server:这个 Server 就是 Service 中 onBind 返回的 IBinder 对象

需要注意的是,上面绿色的色块部分都是属于用户需要实现的部分,而蓝色部分是系统去实现了。也就是说 Binder Driver 这块并不需要知道,Server 中会开启一个线程池去处理客户端调用。为什么要用线程池而不是一个单线程队列呢?试想一下,如果用单线程队列,则会有任务积压,多个客户端同时调用一个服务的时候就会有来不及响应的情况发生,这是绝对不允许的。

对于调用 Binder Driver 中的 transact 接口,客户端可以手动调用,也可以通过 AIDL 的方式生成的代理类来调用,服务端可以继承 Binder 对象,也可以继承 AIDL 生成的接口类的 Stub 对象。

切记,这里 Server 的实现是线程池的方式,而不是单线程队列的方式,区别在于,单线程队列的话,Server 的代码是线程安全的,线程池的话,Server 的代码则不是线程安全的,需要开发者自己做好多线程同步。

小结

  • Binder IPC 属于 C/S 架构,包括 Client、Driver、Server 三个部分
  • Client 可以手动调用 Driver 的 transact 接口,也可以通过 AIDL 生成的 Proxy 调用
  • Server 中会启动一个「线程池」来处理 Client 的调用请求,处理完成后将结果返回给 Driver,Driver 再返回给 Client

这里就回答了两个问题:Service 中通过 AIDL 提供的接口并不是线程安全的,同理 ContentProvider 底层也是使用 Binder,同样不是线程安全的,至于是否需要做多线程保护,看业务而定,最好是做好多线程同步,以防万一。

使用 AIDL 实现 Binder IPC

Android 给了我们更好用的方式那就是 AIDL,假如我们要做一个上报数据的功能,运行在 Service 中,在后台上报数据,接口定义如下

IReporter.aidl

package com.android.binder;

interface IReporter {

    int report(String values, int type);
}

Server

AidlService.java

public class AidlService extends Service {

    public static final class Reporter extends IReporter.Stub {

        @Override
        public int report(String values, int type) throws RemoteException {
            return type;
        }
    }

    private Reporter mReporter;

    public AidlService() {
        mReporter = new Reporter();
    }

    @Override
    public IBinder onBind(Intent intent) {
        return mReporter;
    }
}

继承了 AIDL 自动生成的 Stub 对象,它是什么呢?我们可以看下它的定义

IReporter.java

public interface IReporter extends android.os.IInterface
{

    public static abstract class Stub extends android.os.Binder implements com.android.binder.IReporter {
        ...

        @Override
        public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException
        {
            switch (code)
            {
                case INTERFACE_TRANSACTION:
                {
                    reply.writeString(DESCRIPTOR);
                    return true;
                }
                case TRANSACTION_report:
                {
                    data.enforceInterface(DESCRIPTOR);
                    java.lang.String _arg0;
                    _arg0 = data.readString();
                    int _arg1;
                    _arg1 = data.readInt();
                    int _result = this.report(_arg0, _arg1);
                    reply.writeNoException();
                    reply.writeInt(_result);
                    return true;
                }
            }
            return super.onTransact(code, data, reply, flags);
        }
    }

...

}

自动生成的 IReporter 类自动给我们处理了一些参数的组包和解包而已,在 case 语句中调用了 this.report 即可调用到自己的业务逻辑部分了。

Driver

该部分已经被 Binder 类给封装了,暴露给开发者的已经是很简单的使用方式了,即继承 Binder,实现 onTransact 即可。

Client

MainActivity.java

private IReporter mReporterAidl;

private class AidlConnection implements ServiceConnection {

    @Override
    public void onServiceConnected(ComponentName name, IBinder service) {
        mReporterAidl = IReporter.Stub.asInterface(service);
    }

    @Override
    public void onServiceDisconnected(ComponentName name) {
        mReporterAidl = null;
    }
}

...

@Override
protected void onCreate(Bundle savedInstanceState) {

    ...

    Intent intent = new Intent(this, AidlService.class);
    bindService(intent, new AidlConnection(), BIND_AUTO_CREATE);
}

这里调用了 Stub 对象的 asInterface,具体做了什么呢?

public static com.android.binder.IReporter asInterface(android.os.IBinder obj)
{
    if ((obj==null)) {
        return null;
    }
    android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
    if (((iin!=null)&&(iin instanceof com.android.binder.IReporter))) {
        return ((com.android.binder.IReporter)iin);
    }
    return new com.android.binder.IReporter.Stub.Proxy(obj);
}

先查找本地接口是否存在,判断是否是本地调用,如果是则直接返回 IReporter 的对象,否则返回 Stub.Proxy 对象,这个 Proxy 对象是做什么的呢?

private static class Proxy implements com.android.binder.IReporter
{
    private android.os.IBinder mRemote;
    Proxy(android.os.IBinder remote)
    {
        mRemote = remote;
    }
    @Override public android.os.IBinder asBinder()
    {
        return mRemote;
    }
    public java.lang.String getInterfaceDescriptor()
    {
        return DESCRIPTOR;
    }
    @Override public int report(java.lang.String values, int type) throws android.os.RemoteException
    {
        android.os.Parcel _data = android.os.Parcel.obtain();
        android.os.Parcel _reply = android.os.Parcel.obtain();
        int _result;
        try {
            _data.writeInterfaceToken(DESCRIPTOR);
            _data.writeString(values);
            _data.writeInt(type);
            mRemote.transact(Stub.TRANSACTION_report, _data, _reply, 0);
            _reply.readException();
            _result = _reply.readInt();
        }
        finally {
            _reply.recycle();
            _data.recycle();
        }
        return _result;
    }
}

基本上已经很明了了,就是一个代理对象,对调用接口参数做组包而已,然后调用了 mRemote.transact 接口。

小结

  • AIDL 自动生成了 Stub 类
  • 在 Service 端继承 Stub 类,Stub 类中实现了 onTransact 方法实现了「解包」的功能
  • 在 Client 端使用 Stub 类的 Proxy 对象,该对象实现了「组包」并且调用 transact 的功能

有了 AIDL 之后,IReporter 接口就变得有意义了,Client 调用接口,Server 端实现接口,一切「组包」、「解包」的逻辑封装在了 Stub 类中,一切就是那么完美。

参考资料

https://www.jianshu.com/p/bdef9e3178c9
https://blog.csdn.net/luoshengyang/article/details/6618363
Android IBinder机制简单介绍

Java常量池

发表于 2019-05-29 | 分类于 Java知识点

常量池是为了避免频繁的创建和销毁对象而影响系统性能,实现了对象的共享。

Jvm虚拟内存分布


程序计数器是jvm执行程序的流水线,存放一些跳转指令。

本地方法栈是jvm调用操作系统方法所使用的栈。

虚拟机栈是jvm执行java代码所使用的栈。

方法区存放了一些常量、静态变量、类信息等,可以理解成class文件在内存中的存放位置。

虚拟机堆是jvm执行java代码所使用的堆。

Java常量池分类

Java中的常量池,实际上分为两种形态:静态常量池和运行时常量池。

所谓静态常量池,即*.class文件中的常量池,class文件中的常量池不仅仅包含字符串(数字)字面量,还包含类、方法的信息,占用class文件绝大部分空间。这种常量池主要用于存放两大类常量:字面量(Literal)和符号引用量(Symbolic References),字面量相当于Java语言层面常量的概念,如文本字符串,声明为final的常量值等,符号引用则属于编译原理方面的概念,包括了如下三种类型的常量:

类和接口的全限定名
字段名称和描述符
方法名称和描述符

而运行时常量池,则是jvm虚拟机在完成类装载操作后,将class文件中的常量池载入到内存中,并保存在方法区中,我们常说的常量池,就是指方法区中的运行时常量池。

运行时常量池相对于CLass文件常量池的另外一个重要特征是具备动态性,Java语言并不要求常量一定只有编译期才能产生,也就是并非预置入CLass文件中常量池的内容才能进入方法区运行时常量池,运行期间也可能将新的常量放入池中,这种特性被开发人员利用比较多的就是String类的intern()方法。String的intern()方法会查找在常量池中是否存在一份equal相等的字符串,如果有则返回该字符串的引用,如果没有则添加自己的字符串进入常量池。

常量池的好处

常量池是为了避免频繁的创建和销毁对象而影响系统性能,实现了对象的共享。
例如字符串常量池,在编译阶段就把所有的字符串文字放到一个常量池中。
(1)节省内存空间:常量池中所有相同的字符串常量被合并,只占用一个空间。
(2)节省运行时间:比较字符串时,==比equals()快。对于两个引用变量,只用==判断引用是否相等,也就可以判断实际值是否相等。

享元模式(Flyweight Pattern)主要用于减少创建对象的数量,以减少内存占用和提高性能。这种类型的设计模式属于结构型模式,减少对象数量从而改善应用所需的对象结构的方式。实现方式一般是通过HashMap完成。java常量池的设计初中也是为了减少内存占用,同时保证访问安全。继承Number的包装类常量池存储使用数组,String使用继承自HashTable的StringTable

实例

接下来我们引用一些网络上流行的常量池例子,然后借以讲解。

 1 String s1 = "Hello";
 2 String s2 = "Hello";
 3 String s3 = "Hel" + "lo";
 4 String s4 = "Hel" + new String("lo");
 5 String s5 = new String("Hello");
 6 String s6 = s5.intern();
 7 String s7 = "H";
 8 String s8 = "ello";
 9 String s9 = s7 + s8;
10           
11 System.out.println(s1 == s2);  // true
12 System.out.println(s1 == s3);  // true
13 System.out.println(s1 == s4);  // false
14 System.out.println(s1 == s9);  // false
15 System.out.println(s4 == s5);  // false
16 System.out.println(s1 == s6);  // true

首先说明一点,在java 中,直接使用==操作符,比较的是两个字符串的引用地址,并不是比较内容,比较内容请用String.equals()。

s1 == s2这个非常好理解,s1、s2在赋值时,均使用的字符串字面量,说白话点,就是直接把字符串写死,在编译期间,这种字面量会直接放入class文件的常量池中,从而实现复用,载入运行时常量池后,s1、s2指向的是同一个内存地址,所以相等。

s1 == s3这个地方有个坑,s3虽然是动态拼接出来的字符串,但是所有参与拼接的部分都是已知的字面量,在编译期间,这种拼接会被优化,编译器直接帮你拼好,因此String s3 = “Hel” + “lo”;在class文件中被优化成String s3 = “Hello”,所以s1 == s3成立。只有使用引号包含文本的方式创建的String对象之间使用“+”连接产生的新对象才会被加入字符串池中。

s1 == s4当然不相等,s4虽然也是拼接出来的,但new String(“lo”)这部分不是已知字面量,是一个不可预料的部分,编译器不会优化,必须等到运行时才可以确定结果,结合字符串不变定理,鬼知道s4被分配到哪去了,所以地址肯定不同。对于所有包含new方式新建对象(包括null)的“+”连接表达式,它所产生的新对象都不会被加入字符串池中。

配上一张简图理清思路:


s1 == s9也不相等,道理差不多,虽然s7、s8在赋值的时候使用的字符串字面量,但是拼接成s9的时候,s7、s8作为两个变量,都是不可预料的,编译器毕竟是编译器,不可能当解释器用,不能在编译期被确定,所以不做优化,只能等到运行时,在堆中创建s7、s8拼接成的新字符串,在堆中地址不确定,不可能与方法区常量池中的s1地址相同。


s4 == s5已经不用解释了,绝对不相等,二者都在堆中,但地址不同。
s1 == s6这两个相等完全归功于intern方法,s5在堆中,内容为Hello ,intern方法会尝试将Hello字符串添加到常量池中,并返回其在常量池中的地址,因为常量池中已经有了Hello字符串,所以intern方法直接返回地址;而s1在编译期就已经指向常量池了,因此s1和s6指向同一地址,相等。

特例1

public static final String A = "ab"; // 常量A
public static final String B = "cd"; // 常量B
public static void main(String[] args) {
     String s = A + B;  // 将两个常量用+连接对s进行初始化 
     String t = "abcd";   
    if (s == t) {   
         System.out.println("s等于t,它们是同一个对象");   
     } else {   
         System.out.println("s不等于t,它们不是同一个对象");   
     }   
 } 
s等于t,它们是同一个对象

A和B都是常量,值是固定的,因此s的值也是固定的,它在类被编译时就已经确定了。也就是说:String s=A+B; 等同于:String s=”ab”+”cd”;

特例2

public static final String A; // 常量A
public static final String B;    // 常量B
static {   
     A = "ab";   
     B = "cd";   
 }   
 public static void main(String[] args) {   
    // 将两个常量用+连接对s进行初始化   
     String s = A + B;   
     String t = "abcd";   
    if (s == t) {   
         System.out.println("s等于t,它们是同一个对象");   
     } else {   
         System.out.println("s不等于t,它们不是同一个对象");   
     }   
 } 
s不等于t,它们不是同一个对象

A和B虽然被定义为常量,但是它们都没有马上被赋值。在运算出s的值之前,他们何时被赋值,以及被赋予什么样的值,都是个变数。因此A和B在被赋值之前,性质类似于一个变量。那么s就不能在编译期被确定,而只能在运行时被创建了。

至此,我们可以得出三个非常重要的结论:

必须要关注编译期的行为,才能更好的理解常量池。
运行时常量池中的常量,基本来源于各个class文件中的常量池。
程序运行时,除非手动向常量池中添加常量(比如调用intern方法),否则jvm不会自动添加常量到常量池。

以上所讲仅涉及字符串常量池,实际上还有整型常量池、浮点型常量池(java中基本类型的包装类的大部分都实现了常量池技术,即Byte,Short,Integer,Long,Character,Boolean;两种浮点数类型的包装类Float,Double并没有实现常量池技术) 等等,但都大同小异,只不过数值类型的常量池不可以手动添加常量,程序启动时常量池中的常量就已经确定了,比如整型常量池中的常量范围:-128~127,(Byte,Short,Integer,Long,Character,Boolean)这5种包装类默认创建了数值[-128,127]的相应类型的缓存数据,但是超出此范围仍然会去创建新的对象。

例如在自动装箱时,把int变成Integer的时候,是有规则的,当你的int的值在-128-IntegerCache.high(127) 时,返回的不是一个新new出来的Integer对象,而是一个已经缓存在堆 中的Integer对象,(我们可以这样理解,系统已经把-128到127之 间的Integer缓存到一个Integer数组中去了,如果你要把一个int变成一个Integer对象,首先去缓存中找,找到的话直接返回引用给你就 行了,不必再新new一个),如果不在-128-IntegerCache.high(127) 时会返回一个新new出来的Integer对象。

实践

说了这么多理论,接下来让我们触摸一下真正的常量池。

前文提到过,class文件中存在一个静态常量池,这个常量池是由编译器生成的,用来存储java源文件中的字面量(本文仅仅关注字面量),假设我们有如下java代码:

1 String s = "hi";

为了方便起见,就这么简单,没错!将代码编译成class文件后,用winhex打开二进制格式的class文件。如图:

简单讲解一下class文件的结构,开头的4个字节是class文件魔数,用来标识这是一个class文件,说白话点就是文件头,既:CA FE BA BE。

紧接着4个字节是java的版本号,这里的版本号是34,因为笔者是用jdk8编译的,版本号的高低和jdk版本的高低相对应,高版本可以兼容低版本,但低版本无法执行高版本。所以,如果哪天读者想知道别人的class文件是用什么jdk版本编译的,就可以看这4个字节。

接下来就是常量池入口,入口处用2个字节标识常量池常量数量,本例中数值为00 1A,翻译成十进制是26,也就是有25个常量,其中第0个常量是特殊值,所以只有25个常量。

常量池中存放了各种类型的常量,他们都有自己的类型,并且都有自己的存储规范,本文只关注字符串常量,字符串常量以01开头(1个字节),接着用2个字节记录字符串长度,然后就是字符串实际内容。本例中为:01 00 02 68 69。

接下来再说说运行时常量池,由于运行时常量池在方法区中,我们可以通过jvm参数:-XX:PermSize、-XX:MaxPermSize来设置方法区大小,从而间接限制常量池大小。

假设jvm启动参数为:-XX:PermSize=2M -XX:MaxPermSize=2M,然后运行如下代码:

1 //保持引用,防止自动垃圾回收
2 List<String> list = new ArrayList<String>();
3         
4 int i = 0;
5         
6 while(true){
7     //通过intern方法向常量池中手动添加常量
8     list.add(String.valueOf(i++).intern());
9 }

程序立刻会抛出:Exception in thread “main” java.lang.outOfMemoryError: PermGen space异常。PermGen space正是方法区,足以说明常量池在方法区中。

在jdk8中,移除了方法区,转而用Metaspace区域替代,所以我们需要使用新的jvm参数:-XX:MaxMetaspaceSize=2M,依然运行如上代码,抛出:java.lang.OutOfMemoryError: Metaspace异常。同理说明运行时常量池是划分在Metaspace区域中。

参考资料

https://www.cnblogs.com/syp172654682/p/8082625.html
https://www.jianshu.com/p/aaa80665542d

Java注解(Annotation)

发表于 2019-05-28 | 分类于 Java知识点

Java注解是附加在代码中的一些元信息,用于一些工具在编译、运行时进行解析和使用,起到说明、配置的功能。注解不会也不能影响代码的实际逻辑,仅仅起到辅助性的作用。包含在 java.lang.annotation 包中。

注解本质是一个继承了Annotation的特殊接口,其具体实现类是Java运行时生成的动态代理类。通过代理对象调用自定义注解(接口)的方法,会最终调用AnnotationInvocationHandler的invoke方法。该方法会从memberValues这个Map中索引出对应的值。而memberValues的来源是Java常量池。

元注解

java.lang.annotation 提供了四种元注解,专门注解其他的注解(在自定义注解的时候,需要使用到元注解):

  • @Documented – 注解是否将包含在JavaDoc中

  • @Retention – 什么时候使用该注解

  • @Target – 注解用于什么地方

  • @Inherited – 是否允许子类继承该注解

1)@Retention —— 定义该注解的生命周期

  • RetentionPolicy.SOURCE : 在编译阶段丢弃。这些注解在编译结束之后就不再有任何意义,所以它们不会写入字节码。@Override, @SuppressWarnings都属于这类注解。

  • RetentionPolicy.CLASS : 在类加载的时候丢弃。在字节码文件的处理中有用。注解默认使用这种方式

  • RetentionPolicy.RUNTIME : 始终不会丢弃,运行期也保留该注解,因此可以使用反射机制读取该注解的信息。我们自定义的注解通常使用这种方式。

2)Target —— 表示该注解用于什么地方。默认值为任何元素,表示该注解用于什么地方。可用的ElementType 参数包括

  • ElementType.CONSTRUCTOR: 用于描述构造器

  • ElementType.FIELD: 成员变量、对象、属性(包括enum实例)

  • ElementType.LOCAL_VARIABLE: 用于描述局部变量

  • ElementType.METHOD: 用于描述方法

  • ElementType.PACKAGE: 用于描述包

  • ElementType.PARAMETER: 用于描述参数

  • ElementType.TYPE: 用于描述类、接口(包括注解类型) 或enum声明

3) @Documented —— 一个简单的Annotations 标记注解,没有成员,表示是否将注解信息添加在java 文档中。

4) @Inherited —— 定义该注释和子类的关系
@Inherited 元注解是一个标记注解,@Inherited 阐述了某个被标注的类型是被继承的。如果一个使用了@Inherited 修饰的annotation 类型被用于一个class,则这个annotation 将被用于该class 的子类。

注意:Inherited annotation类型是被标注过的class的子类所继承。类并不从它所实现的接口继承annotation,方法并不从它所重载的方法继承annotation。

当Inherited annotation类型标注的annotation的Retention是RetentionPolicy.RUNTIME,则反射API增强了这种继承性。如果我们使用java.lang.reflect去查询一个Inherited annotation类型的annotation时,反射代码检查将展开工作:检查class和其父类,直到发现指定的annotation类型被发现,或者到达类继承结构的顶层。

自定义注解

自定义注解类编写的一些规则:

  1. Annotation 型定义为@interface, 所有的Annotation 会自动继承java.lang.Annotation这一接口,并且不能再去继承别的类或是接口.

  2. 参数成员只能用public 或默认(default) 这两个访问权修饰

  3. 参数成员只能用基本类型byte、short、char、int、long、float、double、boolean八种基本数据类型和String、Enum、Class、annotations等数据类型,以及这一些类型的数组.

  4. 要获取类方法和字段的注解信息,必须通过Java的反射技术来获取 Annotation 对象,因为你除此之外没有别的获取注解对象的方法

  5. 注解也可以没有定义成员,不过这样注解就没啥用了

PS:自定义注解需要使用到元注解

注解实现

创建注解处理器,利用反射对注解加以处理。AnnotatedElement 接口是所有程序元素(Class、Method和Constructor)的父接口。所以程序通过反射获取了某个类的AnnotatedElement对象之后,程序就可以调用该对象的如下四个个方法来访问Annotation信息:

方法1: T getAnnotation(Class annotationClass):返回改程序元素上存在的、指定类型的注解,如果该类型注解不存在,则返回null。

方法2:Annotation[] getAnnotations():返回该程序元素上存在的所有注解。

方法3:boolean is AnnotationPresent(Class annotationClass):判断该程序元素上是否包含指定类型的注解,存在则返回true,否则返回false.

方法4:Annotation[] getDeclaredAnnotations():返回直接存在于此元素上的所有注释。与此接口中的其他方法不同,该方法将忽略继承的注释。(如果没有注释直接存在于此元素上,则返回长度为零的一个数组。)该方法的调用者可以随意修改返回的数组;这不会对其他调用者返回的数组产生任何影响。

实例

FruitName.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.RetentionPolicy.RUNTIME;

/**
* 水果名称注解
*/
@Target(FIELD)
@Retention(RUNTIME)
@Documented
public @interface FruitName {
String value() default "";
}

FruitColor.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.RetentionPolicy.RUNTIME;

/**
* 水果颜色注解
*/
@Target(FIELD)
@Retention(RUNTIME)
@Documented
public @interface FruitColor {
/**
* 颜色枚举
*/
public enum Color{ BLUE,RED,GREEN};

/**
* 颜色属性
*/
Color fruitColor() default Color.GREEN;

}

FruitProvider.java

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
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.RetentionPolicy.RUNTIME;


/**
* 水果供应者注解
*/
@Target(FIELD)
@Retention(RUNTIME)
@Documented
public @interface FruitProvider {
/**
* 供应商编号
*/
public int id() default -1;

/**
* 供应商名称
*/
public String name() default "";

/**
* 供应商地址
*/
public String address() default "";
}

FruitInfoUtil.java

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
31
32
33
import java.lang.reflect.Field;

/**
* 注解处理器
*/
public class FruitInfoUtil {
public static void getFruitInfo(Class<?> clazz){

String strFruitName=" 水果名称:";
String strFruitColor=" 水果颜色:";
String strFruitProvicer="供应商信息:";

Field[] fields = clazz.getDeclaredFields();

for(Field field :fields){
if(field.isAnnotationPresent(FruitName.class)){
FruitName fruitName = (FruitName) field.getAnnotation(FruitName.class);
strFruitName=strFruitName+fruitName.value();
System.out.println(strFruitName);
}
else if(field.isAnnotationPresent(FruitColor.class)){
FruitColor fruitColor= (FruitColor) field.getAnnotation(FruitColor.class);
strFruitColor=strFruitColor+fruitColor.fruitColor().toString();
System.out.println(strFruitColor);
}
else if(field.isAnnotationPresent(FruitProvider.class)){
FruitProvider fruitProvider= (FruitProvider) field.getAnnotation(FruitProvider.class);
strFruitProvicer=" 供应商编号:"+fruitProvider.id()+" 供应商名称:"+fruitProvider.name()+" 供应商地址:"+fruitProvider.address();
System.out.println(strFruitProvicer);
}
}
}
}

Apple.java

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
31
32
33
34
35
36
37
38
39
40
41
import test.FruitColor.Color;

/**
* 注解使用
*/
public class Apple {

@FruitName("Apple")
private String appleName;

@FruitColor(fruitColor=Color.RED)
private String appleColor;

@FruitProvider(id=1,name="陕西红富士集团",address="陕西省西安市延安路89号红富士大厦")
private String appleProvider;

public void setAppleColor(String appleColor) {
this.appleColor = appleColor;
}
public String getAppleColor() {
return appleColor;
}

public void setAppleName(String appleName) {
this.appleName = appleName;
}
public String getAppleName() {
return appleName;
}

public void setAppleProvider(String appleProvider) {
this.appleProvider = appleProvider;
}
public String getAppleProvider() {
return appleProvider;
}

public void displayName(){
System.out.println("水果的名字是:苹果");
}
}

FruitRun.java

1
2
3
4
5
6
7
8
/**
* 输出结果
*/
public class FruitRun {
public static void main(String[] args) {
FruitInfoUtil.getFruitInfo(Apple.class);
}
}

运行结果是:

1
2
3
水果名称:Apple
水果颜色:RED
供应商编号:1 供应商名称:陕西红富士集团 供应商地址:陕西省西安市延安路89号红富士大厦

参考资料

Java注解(Annotation)原理详解
https://www.cnblogs.com/acm-bingzi/p/javaAnnotation.html

类加载器ClassLoader及Dex/Class

发表于 2019-05-10 | 分类于 Android插件化

ClassLoader

顾名思义,类加载器用来加载 Java 类到 Java 虚拟机中。一般来说,Java 虚拟机使用 Java 类的方式如下:Java 源程序(.java 文件)在经过 Java 编译器编译之后就被转换成 Java 字节代码(.class 文件)。类加载器负责读取 Java 字节代码,并转换成 java.lang.Class类的一个实例,每个这样的实例用来表示一个 Java 类,通过此实例的 newInstance()方法就可以创建出该类的一个对象。

类加载器是 Java 语言的一个创新。它使得动态安装和更新软件组件成为可能。

Java 虚拟机是如何判定两个Java类是相同的:Java 虚拟机不仅要看类的全名是否相同,还要看加载此类的类加载器是否一样。只有两者都相同的情况,才认为两个类是相同的。

类加载器的代理模式

类加载器在尝试自己去查找某个类的字节代码并定义它时,会先代理给其父类加载器,由父类加载器先去尝试加载这个类,依次类推。

代理模式是为了保证 Java 核心库的类型安全。所有 Java 应用都至少需要引用 java.lang.Object类,也就是说在运行的时候,java.lang.Object这个类需要被加载到 Java 虚拟机中。如果这个加载过程由 Java 应用自己的类加载器来完成的话,很可能就存在多个版本的 java.lang.Object类,而且这些类之间是不兼容的。通过代理模式,对于 Java 核心库的类的加载工作由引导类加载器来统一完成,保证了 Java 应用所使用的都是同一个版本的 Java 核心库的类,是互相兼容的。

不同的类加载器为相同名称的类创建了额外的名称空间。相同名称的类可以并存在 Java 虚拟机中,只需要用不同的类加载器来加载它们即可。不同类加载器加载的类之间是不兼容的,这就相当于在 Java 虚拟机内部创建了一个个相互隔离的 Java 类空间。

ClassLoader特点:遵循双亲委派模型

ClassLoader在加载一个class文件时:会询问当前ClassLoader是否已经加载过此类,如果已经加载过则直接返回,不再重复加载。如果没有加载过,会去查询当前ClassLoader的parent是否已经加载过。

因为遵循双亲委派模型,Android中的classLoader具有两个特点:

  • 类加载共享
    当一个class文件被任何一个ClassLoader加载过,就不会再被其他ClassLoader加载。
  • 类加载隔离
    不同ClassLoader加载的class文件肯定不是一个。举个栗子,一些系统层级的class文件在系统初始化的时候被加载,比如java.net.String,这个是在应用启动前就被系统加载好的。如果在一个应用里能简单地用一个自定义的String类把这个String类替换掉的话,将有严重的安全问题。

线程上下文类加载器

线程上下文类加载器(context class loader)是从 JDK 1.2 开始引入的。类 java.lang.Thread中的方法 getContextClassLoader()和 setContextClassLoader(ClassLoader cl)用来获取和设置线程的上下文类加载器。如果没有通过 setContextClassLoader(ClassLoader cl)方法进行设置的话,线程将继承其父线程的上下文类加载器。Java 应用运行的初始线程的上下文类加载器是系统类加载器。在线程中运行的代码可以通过此类加载器来加载类和资源。

类加载器与 OSGi

OSGi(开放服务网关协议,Open Service Gateway Initiative)是 Java 上的动态模块系统。它为开发人员提供了面向服务和基于组件的运行环境,并提供标准的方式用来管理软件的生命周期。OSGi 已经被实现和部署在很多产品上,在开源社区也得到了广泛的支持。Eclipse 就是基于 OSGi 技术来构建的。

OSGi 中的每个模块(bundle)都包含 Java 包和类。模块可以声明它所依赖的需要导入(import)的其它模块的 Java 包和类(通过 Import-Package),也可以声明导出(export)自己的包和类,供其它模块使用(通过 Export-Package)。也就是说需要能够隐藏和共享一个模块中的某些 Java 包和类。这是通过 OSGi 特有的类加载器机制来实现的。OSGi 中的每个模块都有对应的一个类加载器。它负责加载模块自己包含的 Java 包和类。当它需要加载 Java 核心库的类时(以 java开头的包和类),它会代理给父类加载器(通常是启动类加载器)来完成。当它需要加载所导入的 Java 类时,它会代理给导出此 Java 类的模块来完成加载。模块也可以显式的声明某些 Java 包和类,必须由父类加载器来加载。只需要设置系统属性 org.osgi.framework.bootdelegation的值即可。

OSGi 模块的这种类加载器结构,使得一个类的不同版本可以共存在 Java 虚拟机中,带来了很大的灵活性。不过它的这种不同,也会给开发人员带来一些麻烦,尤其当模块需要使用第三方提供的库的时候。下面提供几条比较好的建议:

  • 如果一个类库只有一个模块使用,把该类库的 jar 包放在模块中,在 Bundle-ClassPath中指明即可。
  • 如果一个类库被多个模块共用,可以为这个类库单独的创建一个模块,把其它模块需要用到的 Java 包声明为导出的。其它模块声明导入这些类。
  • 如果类库提供了 SPI 接口,并且利用线程上下文类加载器来加载 SPI 实现的 Java 类,有可能会找不到 Java 类。如果出现了 NoClassDefFoundError异常,首先检查当前线程的上下文类加载器是否正确。通过 Thread.currentThread().getContextClassLoader()就可以得到该类加载器。该类加载器应该是该模块对应的类加载器。如果不是的话,可以首先通过 class.getClassLoader()来得到模块对应的类加载器,再通过 Thread.currentThread().setContextClassLoader()来设置当前线程的上下文类加载器。

ClassLoader种类

  • BootClassLoader(Java的BootStrap ClassLoader)
    用于加载Android Framework层class文件。
  • PathClassLoader(Java的App ClassLoader)
    用于加载已经安装到系统中的apk中的class文件(要传入系统中apk的存放Path,所以只能加载已经安装的apk文件)。
  • DexClassLoader(Java的Custom ClassLoader)
    用于加载指定目录中的class文件(可以加载jar/apk/dex,可以从SD卡中加载未安装的apk)。
  • BaseDexClassLoader
    是PathClassLoader和DexClassLoader的父类。

为了解决65535这个问题,Google提出了multidex方案,即一个apk文件可以包含多个dex文件。
不过值得注意的是,除了第一个dex文件以外,其他的dex文件都是以资源的形式被加载的, 换句话说就是在Application初始化前将dex文件注入到系统的ClassLoader中的。
根据Android虚拟机的类加载机制,同一个类只会被加载一次,所以热修复也使用了这样的机制,要让修复后的类替换原有的类就必须让补丁包的类被优先加载,也就是插入到原有dex之前。

PathClassLoader加载已安装的apk插件

使用PathClassLoader加载已安装的apk插件。sharedUserId要一致,简单的说,应用从一开始安装在Android系统上时,系统都会给它分配一个linux user id,之后该应用在今后都将运行在独立的一个进程中,其它应用程序不能访问它的资源,那么如果两个应用的sharedUserId相同,那么它们将共同运行在相同的linux进程中,从而便可以数据共享、资源访问了。所以我们在宿主app和插件app的manifest上都定义一个相同的sharedUserId。

下面看一个样例:加载包名为packageName的插件,然后获得插件内名为one.png的图片的资源id,进而供宿主app使用该图片

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/**
* 加载已安装的apk
* @param packageName 应用的包名
* @param pluginContext 插件app的上下文
* @return 对应资源的id
*/
private int dynamicLoadApk(String packageName, Context pluginContext) throws Exception {
//第一个参数为包含dex的apk或者jar的路径,第二个参数为父加载器
PathClassLoader pathClassLoader = new PathClassLoader(pluginContext.getPackageResourcePath(),ClassLoader.getSystemClassLoader());
//Class<?> clazz = pathClassLoader.loadClass(packageName + ".R$mipmap");//通过使用自身的加载器反射出mipmap类进而使用该类的功能
//参数:1、类的全名,2、是否初始化类,3、加载时使用的类加载器
Class<?> clazz = Class.forName(packageName + ".R$mipmap", true, pathClassLoader);
//使用上述两种方式都可以,这里我们得到R类中的内部类mipmap,通过它得到对应的图片id,进而给我们使用
Field field = clazz.getDeclaredField("one");
int resourceId = field.getInt(R.mipmap.class);
return resourceId;
}

  • 首先就是new出一个PathClassLoader对象,它的构造方法为:public PathClassLoader(String dexPath, ClassLoader parent)。其中第一个参数是通过插件的上下文来获取插件apk的路径,其实获取到的就是/data/app/apkthemeplugin.apk,那么插件的上下文怎么获取呢?在宿主app中我们只有本app的上下文啊,答案就是为插件app创建一个上下文:Context plugnContext = this.createPackageContext(packageName, CONTEXT_IGNORE_SECURITY | CONTEXT_INCLUDE_CODE。 通过插件的包名来创建上下文,不过这种方法只适合获取已安装的app上下文。或者不需要通过反射直接通过插件上下文getResource().getxxx(R..);也行,而这里用的是反射方法。第二个参数是父加载器,都是ClassLoader.getSystemClassLoader()。

DexClassLoader加载未安装的apk插件

关于动态加载未安装的apk,先描述下思路:首先我们得到事先知道我们的插件apk存放在哪个目录下,然后分别得到插件apk的信息(名称、包名等),然后显示可用的插件,最后动态加载apk获得资源。

按照上面这个思路,我们需要解决几个问题:
1、怎么得到未安装的apk的信息
2、怎么得到插件的context或者Resource,因为它是未安装的不可能通过createPackageContext(…);方法来构建出一个context,所以这时只有在Resource上下功夫。

现在我们就一一来解答这些问题吧:
1、得到未安装的apk信息可以通过mPackageManager.getPackageArchiveInfo()方法获得

/**
 * 获取未安装apk的信息
 * @param context
 * @param archiveFilePath apk文件的path
 * @return
 */
private String[] getUninstallApkInfo(Context context, String archiveFilePath) {
    String[] info = new String[2];
    PackageManager pm = context.getPackageManager();
    PackageInfo pkgInfo = pm.getPackageArchiveInfo(archiveFilePath, PackageManager.GET_ACTIVITIES);
    if (pkgInfo != null) {
        ApplicationInfo appInfo = pkgInfo.applicationInfo;
        String versionName = pkgInfo.versionName;//版本号
        Drawable icon = pm.getApplicationIcon(appInfo);//图标
        String appName = pm.getApplicationLabel(appInfo).toString();//app名称
        String pkgName = appInfo.packageName;//包名
        info[0] = appName;
        info[1] = pkgName;
    }
    return info;
}

2、得到对应未安装apk的Resource对象,我们需要通过反射来获得:

/**
 * @param apkName 
 * @return 得到对应插件的Resource对象
 */
private Resources getPluginResources(String apkName) {
    try {
        AssetManager assetManager = AssetManager.class.newInstance();
        Method addAssetPath = assetManager.getClass().getMethod("addAssetPath", String.class);//反射调用方法addAssetPath(String path)
        //第二个参数是apk的路径:Environment.getExternalStorageDirectory().getPath()+File.separator+"plugin"+File.separator+"apkplugin.apk"
        addAssetPath.invoke(assetManager, apkDir+File.separator+apkName);//将未安装的Apk文件的添加进AssetManager中,第二个参数为apk文件的路径带apk名
        Resources superRes = this.getResources();
        Resources mResources = new Resources(assetManager, superRes.getDisplayMetrics(),
                superRes.getConfiguration());
        return mResources;
    } catch (Exception e) {
        e.printStackTrace();
    }
    return null;
}

通过得到AssetManager中的内部的方法addAssetPath,将未安装的apk路径传入从而添加进assetManager中,然后通过new Resource把assetManager传入构造方法中,进而得到未安装apk对应的Resource对象。

3、接下来就是加载未安装的apk获得它的内部资源

/**
 * 加载apk获得内部资源
 * @param apkDir apk目录
 * @param apkName apk名字,带.apk
 * @throws Exception
 */
private void dynamicLoadApk(String apkDir, String apkName, String apkPackageName) throws Exception {
    File optimizedDirectoryFile = getDir("dex", Context.MODE_PRIVATE);//在应用安装目录下创建一个名为app_dex文件夹目录,如果已经存在则不创建
    Log.v("zxy", optimizedDirectoryFile.getPath().toString());// /data/data/com.example.dynamicloadapk/app_dex
    //参数:1、包含dex的apk文件或jar文件的路径,2、apk、jar解压缩生成dex存储的目录,3、本地library库目录,一般为null,4、父ClassLoader
    DexClassLoader dexClassLoader = new DexClassLoader(apkDir+File.separator+apkName, optimizedDirectoryFile.getPath(), null, ClassLoader.getSystemClassLoader());
    Class<?> clazz = dexClassLoader.loadClass(apkPackageName + ".R$mipmap");//通过使用apk自己的类加载器,反射出R类中相应的内部类进而获取我们需要的资源id
    Field field = clazz.getDeclaredField("one");//得到名为one的这张图片字段
    int resId = field.getInt(R.id.class);//得到图片id
    Resources mResources = getPluginResources(apkName);//得到插件apk中的Resource
    if (mResources != null) {
        //通过插件apk中的Resource得到resId对应的资源
        findViewById(R.id.background).setBackgroundDrawable(mResources.getDrawable(resId));
    }
}

其中通过new DexClassLoader()来创建未安装apk的类加载器,我们来看看它的参数:

public class DexClassLoader extends BaseDexClassLoader {
    public DexClassLoader(String dexPath, String optimizedDirectory,
            String libraryPath, ClassLoader parent) {
        super(dexPath, new File(optimizedDirectory), libraryPath, parent);
    }
}

可以看到DexClassLoader的源码非常简单,只有一个构造方法。我们来看下其四个参数都是什么含义:

  • dexPath:要加载的dex文件路径。
  • optimizedDirectory:dex文件要被copy到的目录路径。此位置一定要是可读写且仅该应用可读写(安全性考虑),所以只能放在data/data下。看官方文档:
    This class loader requires an application-private, writable directory to cache optimized classes. Use Context.getDir(String, int) to create such a directory: File dexOutputDir = context.getDir(“dex”, 0);
  • libraryPath:apk文件中类要使用的c/c++代码,指向包含本地库(so)的文件夹路径,可以设为null。
  • parent:父装载器,也就是真正loadclass的装载器,一般可以通过Context.getClassLoader获取到,也可以通过ClassLoader.getSystemClassLoader()取到。
    在Android中加载class,其实最终是通过DexPathList的findClass来加载的。

单DexClassLoader与多DexClassLoader

插件和主工程的互相调用涉及到以下两个问题:

1、插件调用主工程
在构造插件的ClassLoader时会传入主工程的ClassLoader作为父加载器,所以插件是可以直接可以通过类名引用主工程的类。

2、主工程调用插件
1)若使用多ClassLoader机制,主工程引用插件中类需要先通过插件的ClassLoader加载该类再通过反射调用其方法。插件化框架一般会通过统一的入口去管理对各个插件中类的访问,并且做一定的限制。
2)若使用单ClassLoader机制,主工程则可以直接通过类名去访问插件中的类。该方式有个弊病,若两个不同的插件工程引用了一个库的不同版本,则程序可能会出错,所以要通过一些规范去避免该情况发生。

Dex文件

定义:能够被DVM或者Art虚拟机执行并且加载的文件格式。

作用:dex文件的作用是记录整个工程(通常是一个Android工程)的所有类文件的信息。

Android支持动态加载的两种方式是:DexClassLoader和PathClassLoader。DexClassLoader可加载jar/apk/dex,且支持从SD卡加载;PathClassLoader据说只能加载已经安装在Android系统内APK文件,以下这一段是摘录:PathClassLoader 的限制要更多一些,它只能加载已经安装到 Android 系统中的 apk 文件,也就是 /data/app 目录下的 apk 文件。其它位置的文件加载的时候都会出现 ClassNotFoundException。

dex文件的生成:

先生成class文件(注意执行低版本的JDK版本,否则手机无法运行),然后执行:
dx --dex --output Test.dex Test.class
然后把生成的dex文件拷贝到手机:
adb push C:\Users\Administrator\Desktop\Test.dex /storage/emulated/0
adb shell
dalvikvm -cp /sdcard/Test.dex Test

dex文件的结构:

8位字节的二进制流文件
各个数据紧密排列,无间隙,减少了文件体积,加快加载速度
整个工程的类信息都存放在一个dex文件中(不考虑dex分包的情况下)


注意:
文件头包含了dex文件的信息,所有数据的大致分布情况
链接数据区:主要是指so库

Dex文件头格式



上图和上表就是dex的文件头的结构和各个位置的意思。其中最开始的64 65 78 0A 30 33 3500(dex.035.)表示这是按照dex解析的。

Class文件

定义:能够被JVM识别,加载并执行的文件格式。

作用:记录一个类文件的所有信息,记住所有。例如记住了当前类的引用this、父类super等等。class文件记录的信息往往比java文件多。

class文件的结构:

8位字节的二进制流文件
各个数据紧密排列,无间隙,减少了文件体积,加快加载速度
每个类或者接口单独占据一个class文件,每个类单独管理,没有交叉

class文件中的字段如下所示:

magic 加密字段,虚拟机判断当前的class文件是否被篡改过
minor_version 支持最低版本的jdk
major_version 编译使用的jdk版本
constant_pool_count 常量池的数量,一般为一个
cp_info constant_pool 常量池的结构体,数量不定(类型是cp_info结构体)
access_flags 访问级别,例如public等
this_class 当前类
super_class 父类
interfaces_count 类实现接口的数量
fields_count 类成员变量的数量
methods_count 类方法的数量
method_info methods 类方法的结构体
attributes_count 类属性的数量
attribute_info attributes 类属性的结构体

constant_pool包括:

CONSTANT_Integer_info、CONSTANT_Long_info、CONSTANT_String_info等等 
CONSTANT_Class_info:类的相关信息,包括当前类、引用到的类的信息 
CONSTANT_Fieldref_info:类的域信息 
CONSTANT_Methodref_info:类的方法信息

class文件的弊端:

内存占用大,不适合移动端
堆栈的加栈模式,加载速度慢。
文件IO操作多,类加载慢。

Class文件与Dex文件的比较

本质上都是一样的,都是二进制流文件格式,dex文件是从class文件演变而来的。
class文件存在冗余信息,dex文件则去掉了冗余,并且整合了整个工程的类信息。

参考资料

深入探讨 Java 类加载器
插件化开发—动态加载技术加载已安装和未安装的apk
Android_dex详解
ClassLoader详解
class文件和dex文件

可扩展架构

发表于 2019-05-05 | 分类于 架构

可扩展架构的基本思想是:拆

不同的拆分方式,本质上决定了系统的可扩展性。常见的拆分思路有三种:

1)面向流程拆分:分层架构
分层架构的本质:固定的内核,移动的数据。
扩展时大部分情况只需要修改其一层,少部分情况可能修改关联的两层,不会出现所有层都同时要修改。
以简单的学生信息管理系统为例:展示层–>业务层–>数据层–>存储层

2)面向服务拆分:SOA、微服务
服务是一组相似功能的集合。
对于某个服务扩展,或者要增加新的服务时,只需要扩展相关服务即可。
以简单的学生信息管理系统为例:将系统拆分为注册、登录、信息管理、安全设置等服务

3)面向功能拆分:微内核架构
以简单的学生信息管理系统为例:每个服务都可以拆分为更多细粒度的功能

当然,这几个系统架构并不是非彼既此的,而是可以组合使用。

分层架构

分层架构也叫N层架构,通常情况下,N至少是两层。

分层架构的本质在于隔离关注点(separation of concerns),即每个层中的组件只会处理本层的逻辑,核心就是需要保证各层之间的差异足够清晰,边界足够明显,让人看到架构图后就能看懂整个架构。

根据不同的划分维度和对象,可以得到多种不同的分层架构:
1)C/S、B/S架构
2)MVC、MVP架构
3)逻辑分层架构
逻辑分层架构中的层是自顶向下依赖的,如andoid操作系统的架构

SOA架构

SOA(Service Oriented Architecture)提出来三个关键概念:

1)服务
所有业务功能都是一项服务,服务意味着要对外提供开发的能力,当其他系统需要使用这项功能时,无须定制化开发。

2)ESB(Enterprise Service Bus)
ESB是将企业中各个不同的服务连接到一起。SOA使用ESB来屏蔽异构系统对外提供各种不同的接口方式,以此来达到服务间高效的互联互通。

3)松耦合
目的是减少各个服务间的依赖和相互影响。

SOA架构是集成的思想,是解决服务孤岛打通链条,是无奈之举。ESB集中化的管理带来了性能不佳、厚重等问题,也无法快速扩展。所以不适合互联网的业务特点。

微服务架构

微服务是一种和SOA相似但本质上不同的架构理念。两者都关注于“服务”,都是通过服务的拆分来解决可扩展性问题。本质上不同在于几个核心理念的差异:是否有ESB、服务的粒度、架构设计的目标(small、lightweight、automated)等。

微服务架构其实相当复杂,可以分成几个阶段理解:
1)第一阶段,微服务架构就是去掉了ESB的SOA架构,只不过是通信的方式和结构变了。对于初级的使用者而言,这样理解没有太大问题。
2)第二阶段,没有了ESB,原本很多由ESB组件做的事儿,转到服务的提供者和调用者这里了。他们需要考虑服务的拆分粒。大体仍然算是SOA架构。
3)第三阶段,随着服务的数量大幅增加,服务的管理越来越困难,此时DevOps出现了。这个阶段的微服务架构,已经是跟SOA架构完全不同的东西了。
要逐步演进和迭代,不要过于激进,更不要拆分过细,拆分的粒度,要与团队的架构相互匹配。(康威定律)

SOA和微服务的区别:
1)服务粒度
2)服务通讯
微服务推荐使用统一的协议和格式。
3)服务交付
SOA更多的是考虑兼容已有的系统;微服务的架构理念要求“快速交付”,相应的要求自动化测试、持续集成、自动化部署等敏捷开发相关的最佳实践。
4)应用场景
SOA更适合庞大、复杂、异构的企业级系统,这也是SOA诞生的背景。
微服务更适合快速、轻量级、基于Web的物联网系统。

微服务的陷阱及问题

1)服务划分过细,服务间关系复杂
2)服务数量太多,团队效率急剧下降
3)调用链太长,性能下降
4)调用链太长,问题定位困难
5)没有自动化支撑,无法快速交付
6)没有服务治理,数量多了之后管理混乱

微服务架构实践

1.服务粒度
三个火枪手原则。亚马逊CEO Jeff Bezos有个一个经验法则:如果两个披萨对于一个团队来说不够,那么这个团队就太大了。

2.拆分方法
1)基于业务逻辑拆分

2)基于可扩展拆分:区分稳定服务、可变服务

3)基于可靠性拆分
好处:避免非核心业务故障影响核心业务;核心服务高可用方案可以更简单;能够降低高可用成本

4)基于性能拆分
将性能要求高或者性能压力大的模块拆分出来,避免性能压力大的服务影响其他服务。

以上方案可自由排列组合。

3.基础设施
1)服务发现、服务路由、服务容错:这是最基本的微服务基础设施

2)接口框架、API网关:主要是为了提升开发效率

3)自动化部署、自动化测试、配置中心:主要为了提升测试和运维效率

4)服务监控、服务跟踪、服务安全:主要是为了进一步提升运维效率

以上3、4会随着微服务节点数量增加而越来越重要,当节点较少时,可以通过人工支撑,虽然效率不高,但也基本能够顶得住。

微内核架构

微内核架构也被称为插件化架构,是一种面向功能进行拆分的可扩展性架构。

微内核架构包含两类组件:核心系统和插件模块。核心模块负责和具体业务功能无关的通用功能,如模块加载、模块间通信等;插件模块负责实现具体的业务逻辑。

微内核的核心系统设计的关键技术有:插件管理、插件链接和插件通信。

常见架构有:OSGI、规则引擎架构、Atlas容器化框架等。

android架构模式参考:
1.Atlas:手淘Native容器化框架和思考
2.微信 Android 客户端架构演进之路

康威定律

微服务很多核心理念其实在半个世纪前的一篇文章中就被阐述过了,而且这篇文章中的很多论点在软件开发飞速发展的这半个世纪中竟然一再被验证,这就是康威定律(Conway’s Law)。

在康威的这篇文章中,最有名的一句话就是:

Organizations which design systems are constrained to produce designs which are copies of the communication structures of these organizations. - Melvin Conway(1967)

中文直译大概的意思就是:设计系统的组织,其产生的设计等同于组织之内、组织之间的沟通结构。

Mike从他的角度归纳这篇论文中的其他一些核心观点,如下:

  • 第一定律:企业沟通方式会通过系统设计表达出来——Communication dictates design
  • 第二定律:再多的时间也没办法让任务完美至极,但总有时间能将它完成——There is never enough time to do something right, but there is always enough time to do it over
  • 第三定律:线型系统和线型组织架构间有潜在的异质同态特性——There is a homomorphism from the linear graph of a system to the linear graph of its design organization
  • 第四定律:大系统比小系统更适用于任务分解——The structures of large systems tend to disintegrate during development, qualitatively more so than with small systems

康威第一定律

“人类是复杂的社会动物。”

《The Mythical Man-Month》 这本书里有一句令人难忘的话:在应用项目后期加大人员的投资,会更加拖慢它的速度。——Fred Brooks(1975)

沟通的问题会影响系统设计,进而影响整个系统的开发效率以及最终结果。

康威第二定律

罗马不是一天建成的,学会先解决首要问题。

敏捷开发巨头之一Erik Hollnagel 在他的书中阐述了类似的观点:

问题太复杂?那么不妨忽略不必要的细节。
没有足够的资源?放弃无用的功能。
——Erik Hollnagel(2009)

系统的复杂性、功能数量、市场竞争以及投资人的期望值都在增加,而人的智力是有上限的,没有企业能说一定能找到合适的人,对于一个极其复杂的系统,总会有考虑不周全的地方,Erik认为这个问题最好的解决办法就是:不去管它。

最佳解决方案不是消除所有问题,而是允许它们存在,在发生故障时实现自动恢复。
在由微服务组成的系统中,每个微服务都可能停止响应,这是完全正常的,只需要确保足够的冗余和备份,这就是弹性或高可用性设计。

康威第三定律

创建独立的子系统,减少沟通成本。

团队中微服务的理念应是Inter-Operate,而不是Integrate ,Inter-Operate是指定义系统边界和接口,并为整个团队提供完整的堆栈,实现完全的自制。如此就能降低系统间的依赖性,减少通信成本。

康威第四定律

前面提到,人类是复杂的社会动物,人与人之间的交流是非常复杂的,当涉及到一个系统时,人们经常选择增加人力去减少复杂性,对于企业来说,该如何处理这样的沟通问题?答案是:分而治之。

康威定律与微服务

再来看一下康威定律是如何在半个世纪前就奠定了微服务理论基础的。

  • 人与人之间的交流很复杂,每个人的精力是有限的,因此当问题很复杂,需要协调地去解决时,需要将组织划分进而提高沟通效率。
  • 团队成员工作的系统设计依赖于成员之间的沟通,管理人员可以调整划分模式,实现团队之间的不同沟通方式,这也会影响系统的设计。
  • 如果子系统有清晰的外部通信便捷,那么就可以有效地降低通信成本,响应地设计将更加适合和有效。
  • 需要不断优化一个复杂的系统,并容错性和故障恢复率的帮助下进行优化,不要期望大而全面的设计或架构,因为它们的开发以迭代的方式发生。

以下是一些具体的实践建议:

  • 利用一切手段提高通信效率,如Slack、Github和Wiki,且只与相关人员进行沟通,每个人和每个系统必须有明确的职责,在遇到问题时,知道该找谁去解决。
  • 在MVP模式下设计一套系统,以迭代的方式优化及验证,并确保系统的弹性。
  • 采用与系统设计相一致的团队,以扁平化和以业务为基准的方式去简化团队,每个小团队之间必须有对应负责的模块,避免模糊的界限,以免在发生问题时互相推卸责任。
  • 要做小而美的团队,人员数量的增加会降低效率以及加大成本,亚马逊CEO Jeff Bezos有个一个经验法则:如果两个披萨对于一个团队来说不够,那么这个团队就太大了。一般来说,一家互联网公司的产品团队由7到8个人组成(包括前端和后端测试、交互和用户体验师,一些人可能身兼数职)。

在查看以下微服务标准时,我们可以很容易地看到微服务与康威定律之间的密切关系:

  • 由分布式服务组成的系统
  • 企业部门的业务线
  • 开发优秀的产品
  • Smart endpoints and dumb pipes
  • DevOps
  • 容错
  • 快速发展

参考资料

康威定律

1…5678
Shuming Zhao

Shuming Zhao

78 日志
14 分类
31 标签

© 2021 Shuming Zhao
访客数人, 访问量次 |
由 Hexo 强力驱动
|
主题 — NexT.Pisces v5.1.4