RxJava 从入门到全解析

转载自:http://www.apkbus.com/blog-873055-77431.html

前言

使用了RxJava有一段时间了,深深感受到了其“牛逼”之处。下面,就从RxJava的基础开始,一步一步与大家分享一下这个强大的异步库的用法!

RxJava 概念初步

RxJava 在Github Repo上给的解释是:

“RxJava is a Java VM implementation of Reactive Extensions: 

a library for composing asynchronous and event-based programs by using observable sequences.”

大概就是说RxJava是Java VM上一个灵活的、使用可观测序列来组成的一个异步的、基于事件的库。咋一看好像不知道是啥东西… … 没事,往下看~

作用 – 异步

上面 这段解释,重点就在于异步!但是它又不像 AsyncTask 这样用法简单,所以刚接触RxJava的童鞋,可能会觉得特别难,无从下手,没事,相信通过这篇文章,大伙儿可以有一个比较深刻的理解!

RxJava精华可以浓缩为异步两个字,其核心的东西不外乎两个:

1.  Observable(被观察者) 

2.  Observer/Subscriber(观察者)

Observables可以发出一系列的 事件,这里的事件可以是任何东西,例如网络请求、复杂计算处理、数据库操作、文件操作等等,事件执行结束后交给 Observer/Subscriber 的回调处理。

模式 – 观察者模式

观察者模式是一种对象的行为模式,是 Java 设计模式中很常用的一个模式。观察者模式也常称为:

发布-订阅模式(Publish/Subscribe)

模型-视图模式(Model/View)

源-监听器模式(Source/Listener)

从属者模式(Dependents)

例如用过事件总线 EventBus 库的童鞋就知道,EventBus 属于发布-订阅模式(Publish/Subscribe)。

// 事件订阅@Subscribe(threadMode = ThreadMode.MAIN)public void showDownProgress(MyEvent event) {     // TODO}// 事件发布EventBus.getDefault().post(new MyEvent());

实际上,使用 RxJava 也可以设计出一套事件总线的库,这个称为 RxBus。有兴趣的话可以在学完 RxJava 之后,可以尝试写一个。这里就不细说了~

为啥说这个呢?因为,RxJava 也是一种扩展的观察者模式!

举个栗子,Android 中 View 的点击监听器的实现,View 是被观察者,OnClickListener 对象是观察者,Activity 要如何知道 View 被点击了?那就是构造一个 OnClickListener 对象,通过 setOnClickListener 与View达成一个订阅关系,一旦 View 被点击了,就通过OnClickListener对象的 OnClick 方法传达给 Activity 。采用观察者模式可以避免去轮询检查,节约有限的cpu资源。

结构 – 响应式编程

响应式?顾名思义,就是“你变化,我响应”。举个栗子,a = b + c; 这句代码将b+c的值赋给a,而之后如果b和c的值改变了不会影响到a,然而,对于响应式编程,之后b和c的值的改变也动态影响着a,意味着a会随着b和c的变化而变化。

响应式编程的组成为Observable/Operator/Subscriber,RxJava在响应式编程中的基本流程如下:

Observable -> Operator 1 -> Operator 2 -> Operator 3 -> Subscriber

这个流程,可以简单的理解为:

  1. Observable 发出一系列事件,他是事件的产生者;
  2. Subscriber 负责处理事件,他是事件的消费者;
  3. Operator 是对 Observable 发出的事件进行修改和变换;
  4. 若事件从产生到消费不需要其他处理,则可以省略掉中间的 Operator,从而流程变为 Obsevable -> Subscriber
  5. Subscriber 通常在主线程执行,所以原则上不要去处理太多的事务,而这些复杂的事务处理则交给 Operator;

优势 – 逻辑简洁

Rx 优势可以概括为四个字,那就是 逻辑简洁。然而,逻辑简洁并不意味着代码简洁,但是,由于链式结构,一条龙,你可以从头到尾,从上到下,很清楚的看到这个连式结构的执行顺序。对于开发人员来说,代码质量并不在于代码量,而在于逻辑是否清晰简洁,可维护性如何,代码是否健壮!

另外,熟悉lambda的,还可以进一步提高代码的简洁性。举个简单栗子对比一下,暂时不需要过多理解,后面会一一道来:

// 不使用lambdaObservable.just("Hello World!")
     .map(new Func1<String, String>() {         @Override
         public String call(String s) {             return s + "I am kyrie!";
         }
     })
     .subscribeOn(Schedulers.io())
     .observeOn(AndroidSchedulers.mainThread())
     .subscribe(new Action1<String>() {         @Override
         public void call(String s) {
             Log.i(TAG, s);
         }
     });// 使用lambdaObservable.just("Hello World!")
    .map(s -> s + "I am kyrie!")
    .subscribeOn(Schedulers.io())
    .observeOn(AndroidSchedulers.mainThread())
    .subscribe(s -> {
        Log.i(TAG, s);
    });

RxJava 依赖

在 Android Studio 项目下,为 module 增加 Gradle 依赖。

// Android 平台下须引入的一个依赖,主要用于线程控制compile 'io.reactivex:rxandroid:1.1.0'// RxJavacompile 'io.reactivex:rxjava:1.1.5'

这是我项目里面用的版本,也可以到Maven/RxJava下获取*新版本。

RxJava 入门

前面讲了那么多,大家在概念上对RxJava有一个初步的认识就好,接下来,将为您解开RxJava神秘的面纱~~

无需过分纠结于“事件”这个词,暂时可以简单的把“事件”看成是一个值,或者一个对象。

  1. 事件产生,就是构造要传递的对象;
  2. 事件处理变换,就是改变传递的对象,可以改变对象的值,或是干脆创建个新对象,新对象类型也可以与源对象不一样;
  3. 事件处理,就是接收到对象后要做的事;

事件产生

RxJava创建一个事件比较简单,由 Observable 通过 create 操作符来创建。举个栗子,还是经典的 HelloWorld~~

// 创建一个ObservableObservable<String> observable = Observable.create(new Observable.OnSubscribe<String>() {    @Override
    public void call(Subscriber<? super String> subscriber) {        // 发送一个 Hello World 事件
        subscriber.onNext("Hello World!");        // 事件发送完成
        subscriber.onCompleted();
    }
});

这段代码可以理解为, Observable 发出了一个类型为 String ,值为 “Hello World!” 的事件,仅此而已。

对于 Subscriber 来说,通常onNext()可以多次调用,*后调用onCompleted()表示事件发送完成。

上面这段代码,也可以通过just操作符进行简化。RxJava常用操作符后面会详细介绍,这里先有个了解。

// 创建对象,just里面的每一个参数,相当于调用一次Subscriber#OnNext()Observable<String> observable = Observable.just("Hello World!");

这样,是不是简单了许多?

事件消费

有事件产生,自然也要有事件消费。RxJava 可以通过 subscribe 操作符,对上述事件进行消费。首先,先创建一个观察者。

// 创建一个ObserverObserver<String> observer = new Observer<String>() {    @Override
    public void onCompleted() {
        Log.i(TAG, "complete");
    }    @Override
    public void onError(Throwable e) {

    }    @Override
    public void onNext(String s) {
        Log.i(TAG, s);
    }
};

或者

// 创建一个SubscriberSubscriber<String> subscriber = new Subscriber<String>() {    @Override
    public void onCompleted() {
        Log.i(TAG, "complete");
    }    @Override
    public void onError(Throwable e) {

    }    @Override
    public void onNext(String s) {
        Log.i(TAG, s);
    }
};
  1. Observer 是观察者, Subscriber 也是观察者,Subscriber 是一个实现了Observer接口的抽象类,对 Observer 进行了部分扩展,在使用上基本没有区别;
  2. Subscriber 多了发送之前调用的 onStart() 和解除订阅关系的 unsubscribe() 方法。
  3. 并且,在 RxJava 的 subscribe 过程中,Observer 也总是会先被转换成一个 Subscriber 再使用。所以在这之后的示例代码,都使用 Subscriber 来作为观察者。

事件订阅

*后,我们可以调用 subscribe 操作符, 进行事件订阅。

// 订阅事件observable.subscribe(subscriber);

在 Subscriber 实现的三个方法中,顾名思义,对应三种不同状态:
1. onComplete(): 事件全部处理完成后回调
2. onError(Throwable t): 事件处理异常回调
3. onNext(T t): 每接收到一个事件,回调一次

区分回调动作

对于事件消费事件订阅来说,好像为了打印一个“Hello World!”要费好大的劲… 其实,RxJava 自身提供了精简回调方式,我们可以为 Subscriber 中的三种状态根据自身需要分别创建一个回调动作 Action

// onComplete()Action0 onCompleteAction = new Action0() {    @Override
    public void call() {
        Log.i(TAG, "complete");
    }
};// onNext(T t)Action1<String> onNextAction = new Action1<String>() {    @Override
    public void call(String s) {
        Log.i(TAG, s);
    }
};// onError(Throwable t)Action1<Throwable> onErrorAction = new Action1<Throwable>() {    @Override
    public void call(Throwable throwable) {

    }
};

那么,RxJava 的事件订阅支持以下三种不完整定义的回调。

observable.subscribe(onNextAction);

observable.subscribe(onNextAction, onErrorAction);

observable.subscribe(onNextAction, onErrorAction, onCompleteAction);

我们可以根据当前需要,传入对应的 Action, RxJava 会相应的自动创建 Subscriber。

  1. Action0 表示一个无回调参数的Action;
  2. Action1 表示一个含有一个回调参数的Action;
  3. 当然,还有Action2 ~ Action9,分别对应2~9个参数的Action;
  4. 每个Action,都有一个 call() 方法,通过泛型T,来指定对应参数的类型;

入门示例

前面讲解了事件的产生到消费、订阅的过程,下面就举个完整的例子。从res/mipmap中取出一张图片,显示在ImageView上。

final ImageView ivLogo = (ImageView) findViewById(R.id.ivLogo);

Observable.create(new Observable.OnSubscribe<Drawable>() {        @Override
        public void call(Subscriber<? super Drawable> subscriber) {            // 从mipmap取出一张图片作为Drawable对象
            Drawable drawable = ContextCompat.getDrawable(mContext, R.mipmap.ic_launcher);            // 把Drawable对象发送出去
            subscriber.onNext(drawable);

            subscriber.onCompleted();
        }
    })
    .subscribe(new Subscriber<Drawable>() {        @Override
        public void onCompleted() {

        }        @Override
        public void onError(Throwable e) {
            Log.i(TAG, e.toString());
        }        @Override
        public void onNext(Drawable drawable) {            // 接收到Drawable对象,显示在ImageView上
            ivLogo.setImageDrawable(drawable);
        }
    });

上面示例是RxJava*基本的一个用法。稍微消化一下,继续~~

RxJava 进阶

Scheduler线程控制

默认情况下,RxJava事件产生和消费均在同一个线程中,例如在主线程中调用,那么事件的产生和消费都在主线程。

那么问题来了,假如事件产生的过程是耗时操作,比如网络请求,结果显示在UI中,这个时候在主线程执行对于网络请求就不合适了,而在子线程执行,显示结果需要进行UI操作,同样不合适~~

所以,RxJava 的*个牛逼之处在于可以自由切换线程!那么,如何做?

在 RxJava 中,提供了一个名为 Scheduler 的线程调度器,RxJava 内部提供了4个调度器,分别是:

  1. Schedulers.io(): I/O 操作(读写文件、数据库、网络请求等),与newThread()差不多,区别在于io() 的内部实现是是用一个无数量上限的线程池,可以重用空闲的线程,因此多数情况下 io() 效率比 newThread() 更高。值得注意的是,在 io() 下,不要进行大量的计算,以免产生不必要的线程;
  2. Schedulers.newThread(): 开启新线程操作;
  3. Schedulers.immediate(): 默认指定的线程,也就是当前线程;
  4. Schedulers.computation():计算所使用的调度器。这个计算指的是 CPU 密集型计算,即不会被 I/O等操作限制性能的操作,例如图形的计算。这个 Scheduler 使用的固定的线程池,大小为 CPU 核数。值得注意的是,不要把 I/O 操作放在 computation() 中,否则 I/O 操作的等待时间会浪费 CPU;
  5. AndroidSchedulers.mainThread(): RxJava 扩展的 Android 主线程;

我们可以通过 subscribeOn() 和 observeOn() 这两个方法来进行线程调度。举个栗子:

依然还是显示一张图片,不同的是,这次是从网络上加载图片

final ImageView ivLogo = (ImageView) findViewById(R.id.ivLogo);

Observable.create(new Observable.OnSubscribe<Drawable>() {    @Override
    public void call(Subscriber<? super Drawable> subscriber) {        try {
            Drawable drawable = Drawable.createFromStream(new URL("https://ss2.baidu.com/6ONYsjip0QIZ8tyhnq/it/u=2502144641,437990411&fm=80&w=179&h=119&img.JPEG").openStream(), "src");
            subscriber.onNext(drawable);
        } catch (IOException e) {
            subscriber.onError(e);
        }
    }
})        // 指定 subscribe() 所在的线程,也就是上面call()方法调用的线程
        .subscribeOn(Schedulers.io())        // 指定 Subscriber 回调方法所在的线程,也就是onCompleted, onError, onNext回调的线程
        .observeOn(AndroidSchedulers.mainThread())
        .subscribe(new Subscriber<Drawable>() {            @Override
            public void onCompleted() {

            }            @Override
            public void onError(Throwable e) {
                Log.e(TAG, e.toString());
            }            @Override
            public void onNext(Drawable drawable) {
                ivLogo.setImageDrawable(drawable);
            }
        });

所以,这段代码就做一件事,在 io 线程加载一张网络图片,加载完毕之后在主线程中显示到ImageView上。

变换

RxJava的又一牛逼之处,在于 变换。啥意思呢? 就是将发送的事件或事件序列,加工后转换成不同的事件或事件序列。

map操作符

变换的概念不好理解吧?举个简单的栗子,我们对上述示例 进行改写。

final ImageView ivLogo = (ImageView) findViewById(R.id.ivLogo);

Observable.create(new Observable.OnSubscribe<String>() {    @Override
    public void call(Subscriber<? super String> subscriber) {

        subscriber.onNext("https://ss2.baidu.com/-vo3dSag_xI4khGko9WTAnF6hhy/image/h%3D200/sign=4db5130a073b5bb5a1d727fe06d2d523/cf1b9d16fdfaaf51965f931e885494eef11f7ad6.jpg");
    }
}).map(new Func1<String, Drawable>() {    @Override
    public Drawable call(String url) {        try {
            Drawable drawable = Drawable.createFromStream(new URL(url).openStream(), "src");            return drawable;
        } catch (IOException e) {

        }        return null;
    }
})        // 指定 subscribe() 所在的线程,也就是call()方法调用的线程
        .subscribeOn(Schedulers.io())        // 指定 Subscriber 回调方法所在的线程,也就是onCompleted, onError, onNext回调的线程
        .observeOn(AndroidSchedulers.mainThread())
        .subscribe(new Subscriber<Drawable>() {            @Override
            public void onCompleted() {

            }            @Override
            public void onError(Throwable e) {
                Log.e(TAG, e.toString());
            }            @Override
            public void onNext(Drawable drawable) {                if (drawable != null) {
                    ivLogo.setImageDrawable(drawable);
                }
            }
        });

经过改写代码后,有什么变化呢? Observable 创建了一个 String 事件,也就是产生一个url,通过 map 操作符进行变换,返回Drawable对象,这个变换指的就是通过url进行网络图片请求,返回一个Drawable。所以简单的来说就是把String事件,转换为Drawable事件。逻辑表示就是:

Observable<String> --> map变换 --> Observable<Drawable>

那么,Func1 是什么呢?与 Action1 类似,不同的是 FuncX 有返回值,而 ActionX 没有。为什么需要返回值呢?目的就在于对象的变换,由String对象转换为Drawable对象。同样,也有Func0 ~ Func9,对应不同的参数个数。

当然了,RxJava 的变换,可不止于map这么简单,继续往下!

flatMap操作符

不难发现,上述的 map 操作符,是一对一的变换,并且返回的是变换后的对象。而 flatMap 操作符可以适应一对多,并且返回的是一个 Observable 。应用场景举例:例如一个员工负责多个任务,现在要打印所有员工的所有任务。

final List<Employee> list = new ArrayList<Employee>() {
    {
        add(new Employee("jackson", mission_list1));
        add(new Employee("sunny", mission_list2));
    }
};
Observable.from(list)
        .flatMap(new Func1<Employee, Observable<Employee.Mission>>() {            @Override
            public Observable<Employee.Mission> call(Employee employee) {                return Observable.from(employee.missions);
            }
        })
        .subscribe(new Subscriber<Employee.Mission>() {            @Override
            public void onCompleted() {

            }            @Override
            public void onError(Throwable e) {

            }            @Override
            public void onNext(Employee.Mission mission) {
                Log.i(TAG, mission.desc);
            }
        });

执行结果为顺序打印出两位员工的所有任务列表。

通过上面的代码可以看出,map 与 flatMap 这两个操作符的共同点在于,他们都是把一个对象转换为另一个对象,但须注意以下这些特点:

  1. flatMap 返回的是一个Observable对象,而 map 返回的是一个普通转换后的对象;
  2. flatMap 返回的Observable对象并不是直接发送到Subscriber的回调中,而是重新创建一个Observable对象,并激活这个Observable对象,使之开始发送事件;而 map 变换后返回的对象直接发到Subscriber回调中;
  3. flatMap 变换后产生的每一个Observable对象发送的事件,*后都汇入同一个Observable,进而发送给Subscriber回调;
  4. map返回类型 与 flatMap 返回的Observable事件类型,可以与原来的事件类型一样;
  5. 可以对一个Observable多次使用 map 和 flatMap

鉴于 flatMap 自身强大的功能,这常常被用于 嵌套的异步操作,例如嵌套网络请求。传统的嵌套请求,一般都是在前一个请求的 onSuccess() 回调里面发起新的请求,这样一旦嵌套多个的话,缩进就是大问题了,而且严重的影响代码的可读性。而RxJava嵌套网络请求仍然通过链式结构,保持代码逻辑的清晰!举个栗子:

Github上的 README.md 文件,通常是 MarkDown 语法。我们要获取 README.md 内容并按 MarkDown 风格显示在UI上,就可以通过以下方式(Retrofit2 + RxJava,稍后会介绍):

new ReadmeContentClient()    // 获取md语法的Readme内容, 返回的是一个Observable<String>对象
    .getReadme()
    .flatMap(new Func1<String, Observable<String>>() {        @Override
        public Observable<String> call(String md) {            // 由于Readme的内容是md语法,需要转成html字符串通过WebView显示到UI
            // 返回的也是Observable<String>对象
            return new MarkDownStyleClient(md)
                            .formatMarkStyle();
        }
    })
    .subscribeOn(Schedulers.io())
    .observeOn(AndroidSchedulers.mainThread())
    .subscribe(new Observer<String>() {        @Override
        public void onCompleted() {

        }        @Override
        public void onError(Throwable e) {
            Log.e(TAG, "readme:" + e.toString());
        }        @Override
        public void onNext(String html) {            // html就是根据readme md格式内容,生成的html代码
            view.showReadme(html);
        }
    });

RxJava 其他常用操作符

  1. from
    接收一个集合作为输入,然后每次输出一个元素给subscriber。

    // Observable.from(T[] params)Observable.from(new Integer[]{1, 2, 3, 4, 5})
        .subscribe(new Action1<Integer>() {        @Override
            public void call(Integer number) {
                Log.i(TAG, "number:" + number);
            }
        });

    注意:如果from()里面执行了耗时操作,即使使用了subscribeOn(Schedulers.io()),仍然是在主线程执行,可能会造成界面卡顿甚至崩溃,所以耗时操作还是使用Observable.create(…);

  2. just
    接收一个可变参数作为输入,*终也是生成数组,调用from(),然后每次输出一个元素给subscriber。

    // Observable.just(T... params),params的个数为1 ~ 10Observable.just(1, 2, 3, 4, 5)
        .subscribe(new Action1<Integer>() {        @Override
            public void call(Integer number) {
                Log.i(TAG, "number:" + number);
            }
        });
  3. filter
    条件过滤,去除不符合某些条件的事件。举个栗子:

    Observable.from(new Integer[]{1, 2, 3, 4, 5})
        .filter(new Func1<Integer, Boolean>() {        @Override
            public Boolean call(Integer number) {            // 偶数返回true,则表示剔除奇数,留下偶数
                return number % 2 == 0;
            }
        })
        .subscribe(new Action1<Integer>() {        @Override
            public void call(Integer number) {
                Log.i(TAG, "number:" + number);
            }
        });
  4. take
    *多保留的事件数。
  5. doOnNext
    在处理下一个事件之前要做的事。

    Observable.from(new Integer[]{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12})
        .filter(new Func1<Integer, Boolean>() {        @Override
            public Boolean call(Integer number) {            // 偶数返回true,则表示剔除奇数
                return number % 2 == 0;
            }
        })    // *多保留三个,也就是*后剩三个偶数
        .take(3)
        .doOnNext(new Action1<Integer>() {        @Override
            public void call(Integer number) {            // 在输出偶数之前输出它的hashCode
                Log.i(TAG, "hahcode = " + number.hashCode() + "");
            }
        })
        .subscribe(new Action1<Integer>() {        @Override
            public void call(Integer number) {
                Log.i(TAG, "number = " + number);
            }
        });

    输出如下:

    hahcode = 2number = 2hahcode = 4number = 4hahcode = 6number = 6
  6. debounce
    通俗点讲,就是N个事件发生的时间间隔太近,就过滤掉前N-1个事件,保留*后一个事件。debounce可以指定这个时间间隔!可以用在SearchEditText请求关键词的地方,SearchEditText的内容变化太快,可以抵制频繁请求关键词,后面第15条15.Subject会介绍这个。为了演示效果,先举个简单栗子:

    Observable
        .create(new Observable.OnSubscribe<Integer>() {        @Override
            public void call(Subscriber<? super Integer> subscriber) {            int i = 0;            int[] times = new int[]{100, 1000};            while (true) {
                    i++;                if (i >= 100)                    break;
                    subscriber.onNext(i);                try {                    // 注意!!!!
                        // 当i为奇数时,休眠1000ms,然后才发送i+1,这时i不会被过滤掉
                        // 当i为偶数时,只休眠100ms,便发送i+1,这时i会被过滤掉
                        Thread.sleep(times[i % 2]);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                subscriber.onCompleted();
            }
        })    // 间隔400ms以内的事件将被丢弃
        .debounce(400, TimeUnit.MILLISECONDS)
        .subscribeOn(Schedulers.io())
        .observeOn(AndroidSchedulers.mainThread())
        .subscribe(new Subscriber<Integer>() {        @Override
            public void onCompleted() {
                Log.i(TAG, "complete");
            }        @Override
            public void onError(Throwable e) {
                Log.e(TAG, e.toString());
            }        @Override
            public void onNext(Integer integer) {
                Log.i(TAG, "integer = " + integer);
            }
        });

    输出结果:

    11-23 10:44:45.167 MainActivity: integer = 111-23 10:44:46.270 MainActivity: integer = 311-23 10:44:47.373 MainActivity: integer = 511-23 10:44:48.470 MainActivity: integer = 711-23 10:44:49.570 MainActivity: integer = 911-23 10:44:50.671 MainActivity: integer = 1111-23 10:44:51.772 MainActivity: integer = 1311-23 10:44:52.872 MainActivity: integer = 1511-23 10:44:53.973 MainActivity: integer = 17...

    我们设置过滤条件为400ms,可以发现,奇数正常输出,因为在它的下一个事件事件隔了1000ms,所以它不会被过滤掉;偶数被过滤掉,是因为它距离下一个事件(奇数)只隔了100ms。并且,输出的两个事件相隔大约为 100ms + 1000ms = 1100ms

  7. merge
    用于合并两个Observable为一个Observable。较为简单。

    Observable.merge(Observable1, Observable2)
        .subscribe(subscriber);
  8. concat
    顺序执行多个Observable,个数为1 ~ 9。例子稍后与first操作符一起~~
  9. compose
    与 flatMap 类似,都是进行变换,返回Observable对象,激活并发送事件。

    1. compose 是唯一一个能够从数据流中得到原始Observable的操作符,所以,那些需要对整个数据流产生作用的操作(比如,subscribeOn()和observeOn())需要使用 compose 来实现。相较而言,如果在flatMap()中使用subscribeOn()或者observeOn(),那么它仅仅对在 flatMap 中创建的Observable起作用,而不会对剩下的流产生影响。这样就可以简化subscribeOn()以及observeOn()的调用次数了。
    2. compose 是对 Observable 整体的变换,换句话说, flatMap 转换Observable里的每一个事件,而 compose 转换的是整个Observable数据流。
    3. flatMap 每发送一个事件都创建一个 Observable,所以效率较低。而 compose 操作符只在主干数据流上执行操作。
    4. 建议使用 compose 代替 flatMap
  10. first
    只发送符合条件的*个事件。可以与前面的contact操作符,做网络缓存。举个栗子:依次检查Disk与Network,如果Disk存在缓存,则不做网络请求,否则进行网络请求。

    // 从缓存获取Observable<BookList> fromDisk = Observable.create(new Observable.OnSubscribe<BookList>() {    @Override
        public void call(Subscriber<? super BookList> subscriber) {
            BookList list = getFromDisk();        if (list != null) {
                subscriber.onNext(list);
            } else {
                subscriber.onCompleted();
            }
        }
    });// 从网络获取Observable<BookList> fromNetWork = bookApi.getBookDetailDisscussionList();
    
    Observable.concat(fromDisk, fromNetWork)        // 如果缓存不为null,则不再进行网络请求。反之
            .first()
            .subscribeOn(Schedulers.io())
            .observeOn(AndroidSchedulers.mainThread())
            .subscribe(new Subscriber<BookList>() {            @Override
                public void onCompleted() {
    
                }            @Override
                public void onError(Throwable e) {
    
                }            @Override
                public void onNext(BookList discussionList) {
    
                }
            });

    网络缓存用法,具体可参见我的项目:https://github.com/JustWayward/BookReader

  11. timer
    可以做定时操作,换句话讲,就是延迟执行。事件间隔由timer控制。举个栗子:两秒后输出“Hello World!”

    Observable.timer(2, TimeUnit.SECONDS)
        .subscribe(new Subscriber<Long>() {        @Override
            public void onCompleted() {
    
            }        @Override
            public void onError(Throwable e) {
    
            }        @Override
            public void onNext(Long aLong) {
                Log.i(TAG, "Hello World!");
            }
        });
  12. interval
    定时的周期性操作,与timer的区别就在于它可以重复操作。事件间隔由interval控制。举个栗子:每隔两秒输出“Hello World!”

    Observable.interval(2, TimeUnit.SECONDS)
        .subscribe(new Subscriber<Long>() {        @Override
            public void onCompleted() {
    
            }        @Override
            public void onError(Throwable e) {
    
            }        @Override
            public void onNext(Long aLong) {
                Log.i(TAG, "Hello World!");
            }
        });
  13. throttleFirst
    与debounce类似,也是时间间隔太短,就丢弃事件。可以用于防抖操作,比如防止双击。

    RxView.clicks(button)
      .throttleFirst(1, TimeUnit.SECONDS)
      .subscribe(new Observer<Object>() {      @Override
          public void onCompleted() {
    
          }      @Override
          public void onError(Throwable e) {
    
          }      @Override
          public void onNext(Object o) {
               Log.i(TAG, "do clicked!");
          }
      });

    上面这个RxView详见:https://github.com/JakeWharton/RxBinding, 主要与RxJava结合用于一些View的事件绑定,JakeWharton大神的项目,厉害。

  14. Single
    Single与Observable类似,相当于是他的精简版。订阅者回调的不是OnNext/OnError/onCompleted,而是回调OnSuccess/OnError。

    Single.create(new Single.OnSubscribe<Object>() {    @Override
        public void call(SingleSubscriber<? super Object> subscriber) {
            subscriber.onSuccess("Hello");
        }
    }).subscribe(new SingleSubscriber<Object>() {    @Override
        public void onSuccess(Object value) {
            Log.i(TAG, value.toString());
        }    @Override
        public void onError(Throwable error) {
    
        }
    });
  15. Subject
    Subject这个类,既是Observable又是Observer,啥意思呢?就是它自身既是事件的生产者,又是事件的消费者,相当于自身是一条管道,从一端进,又从另一端出。举个栗子:PublishSubject

    Subject subject = PublishSubject.create();// 1.由于Subject是Observable,所以进行订阅subject.subscribe(new Subscriber<Object>() {    @Override
        public void onCompleted() {
    
        }    @Override
        public void onError(Throwable e) {
    
        }    @Override
        public void onNext(Object o) {
            Log.i(TAG, o.toString());
        }
    });// 2.由于Subject同时也是Observer,所以可以调用onNext发送数据subject.onNext("world");

    这个好像有点厉害的样子,哈哈。可以配合debounce,避免SearchEditText频繁请求。

    Subject subject = PublishSubject.create();
    
    subject.debounce(400, TimeUnit.MILLISECONDS)
            .subscribe(new Subscriber<Object>() {        @Override
            public void onCompleted() {
    
            }        @Override
            public void onError(Throwable e) {
    
            }        @Override
            public void onNext(Object o) {            // request
            }
        });
    
    edittext.addTextChangedListener(new TextWatcher() {    @Override 
        public void beforeTextChanged(CharSequence s, int start, int count, int after) { }    @Override 
        public void onTextChanged(CharSequence s, int start, int before, int count) {
            subject.onNext(s.toString());
        }    @Override 
        public void afterTextChanged(Editable s) { } 
    });

RxJava 应用

RxJava+Retrofit 的网络请求方式

Retrofit是一个非常适合RestAPI的网络请求库。没用过的童鞋,还是推荐学一学的。

使用Callback的请求方式:

// 1. 定义一个请求接口@GET("/match/stat")
Call<String> getMatchStat(@Query("mid") String mid, @Query("tabType") String tabType);// 2. 创建Service对象Retrofit retrofit = new Retrofit.Builder()
                        .baseUrl(BuildConfig.TENCENT_SERVER)// 加入RxJava支持
                        .addCallAdapterFactory(RxJavaCallAdapterFactory.create()) 
                        .addConverterFactory(ScalarsConverterFactory.create())
                        .client(OkHttpHelper.getTecentClient()).build();

TencentApi api = retrofit.create(TencentApi.class);// 3. 调用Call<String> call = api.getMatchStat(mid, tabType);
call.enqueue(new Callback<String>() {    @Override
    public void onResponse(Call<String> call, Response<String> response) {        if(response != null && response.body()!=null)            // 成功
        } else {            // 无数据
        }
    }    @Override
    public void onFailure(Call<String> call, Throwable t) {        // 失败
    }
});

与 RxJava 结合的方式,则是

// 1. 定义请求接口,返回的是Observable对象@GET("/user/followers")
Observable<List<User>> followers();// 2. 同样是创建api对象...// 3. 请求api.followers()
    .subscribeOn(Schedulers.io())
    .observeOn(AndroidSchedulers.mainThread())
    .subscribe(new Observer<List<User>>() {        @Override
        public void onCompleted() {

        }        @Override
        public void onError(Throwable e) {            // 请求出错。可能发生网络异常、Json解析异常等等
        }        @Override
        public void onNext(List<User> list) {            // 请求成功
            view.showMyFollowers(list);
        }
    });

若需嵌套请求,比如先获取Token再进行才能进行登录,可参考flatMap操作符*后的获取Readme内容显示在WebView上的例子。

Retrofit2 + RxJava + Dagger2: 具体可参见我的项目,里面有比较详细的用法。
https://github.com/JustWayward/BookReader

不难发现,Retrofit 把请求封装进 Observable ,在请求结束后调用 onNext() 以及 OnCompleted() 或在请求失败后调用 onError()

:RxJava形式的请求,并不能减少代码量,但是逻辑非常清晰。假如请求到数据之后需要对数据进行处理,并且是耗时操作,难道要再开一个线程,或者用AsyncTask再做一次异步?很显然,RxJava的变换很好的解决了这个问题,依然会使逻辑结构清晰。

RxBus

准确的来说,是一种基于RxJava实现事件总线的一种思想。可以替代EventBus/Otto,因为他们都依赖于观察者模式。可以参考https://github.com/AndroidKnife/RxBus这个库。

RxBinding

前面介绍过了,JakeWharton大神的项目,https://github.com/JakeWharton/RxBinding, 主要与RxJava结合用于一些View的事件绑定。

RxJava 的一些坑

未取消订阅而引起的内存泄漏

举个栗子,对于前面常用操作符12.interval做周期性操作的例子,并没有使之停下来的,没有去控制订阅的生命周期,这样,就有可能引发内存泄漏。所以,在Activity#onDestroy()的时候或者不需要继续执行的时候应该取消订阅。

Subscription subscription = Observable.interval(2, TimeUnit.SECONDS)
    .subscribe(new Subscriber<Long>() {        @Override
        public void onCompleted() {

        }        @Override
        public void onError(Throwable e) {

        }        @Override
        public void onNext(Long aLong) {
            Log.i(TAG, "Hello World!");
        }
    });// 调用unsubscribe();方法进行取消订阅subscription.unsubscribe();

但是,如果有很多个数据源,那岂不是要取消很多次?当然不是的,可以利用 CompositeSubscription, 相当于一个 Subscription 集合。

CompositeSubscription list = new CompositeSubscription();
list.add(subscription1);
list.add(subscription2);
list.add(subscription3);// 统一调用一次unsubscribe,就可以把所有的订阅都取消list.unsubscribe();

总结

相信到了这里,大家对RxJava应该有了一个比较清晰的理解。当然,实践出真知,还是要去尝试,才能更深层次的体会到其强大之处。

*后,总结一下RxJava的基本使用过程。

  1. 首先是创建事件源源,也就是被观察者,可以用Observable的create/just/from等方法来创建;
  2. 通过filter/debounce等操作符,进行自定义事件过滤;
  3. 通过Schedules进行事件发送和订阅的线程控制,也就是subscribeOn() 和 observeOn();
  4. 通过map/flatMap/compose等操作符,进行事件的变换;
  5. 调用subscribe进行事件订阅;
  6. *后,不要忘了对订阅者生命周期的控制,不用的时候,记得调用unsubscribe(),以免引发内存泄漏。

Android studio 如何查看 library 间的依赖关系

一、配置环境
Android Studio中使用的 gradle 版本一般不是*新版,所以在使用其自带的 Terminal 时容易报版本过低的错误,为了方便使用,我从 gradle
官网下载了*新版的 gradle ,然后配置好 gradle 的环境变量以方便使用。

二、gradle task 相关内容
gradle 本身不提供查看 library 依赖关系的命令,幸好 Android Studio 提供了可供查看的 task ,位于各个 module 的 help 任务堆中,如下图:

%title插图%num

%title插图%num

双击 dependencies 执行该任务,可以看到在 gradle console 中均没有得到依赖关系,结果如下图所示:

%title插图%num

从上图可以看出无论我们执行哪个 module 下的 dependencies 其结果都是相当于在 Root project 执行了该任务;那么在 app project 或者 test01library project 中执行该 dependencies 任务呢?此时就需要我们通过命令行的方式执行 dependencies 了。

1.查看 app project 的依赖关系
在该路径下打开命令行工具,输入

gradle dependencies

稍等一会,便可看到 library 的依赖关系,如下图所示:

%title插图%num

但是命令行中会生成大量内容,并且无法看到全部信息,为了方便查看,我们将输出信息写入文本文件中

gradle dependencies >log.txt

在当前目录下将生成一个 log.txt 文件里面包括所有 app project 所依赖 library 的所有依赖关系(test01library 同理) 。

2.查看指定类型的依赖关系
由于 dependencies 的配置类型太多这里仅以编译时 library 为例说明:

// 查看 compile 时的依赖关系
gradle dependencies –configuration compile

关于其他配置类型可以通过这条命令获得:

gradle dependencies –info

可以看到有很多参数

%title插图%num

3.在 Root project 下查看依赖关系
在项目根目录下我们可以通过下面命令达到和上面相同的效果,如:

// gradle :project name:dependencies [–configuration compile]
gradle :app:dependencies –configuration compile

三、总结
查看各 library 的依赖关系是为了避免出现java.util.zip.ZipException: duplicate entry exception android/support/vX/…/xxx.class 异常;由于 app project 必定会直接或间接引用其他所有 project, 所以,只查看这一个 project 的依赖关系即可得到全部信息。

Android 查看项目依赖关系

做android项目,module多的话,很容易遇到包冲突的问题。比如v4既有26.1.0版本,也有27.1.0版本。
这时候,要找出两个版本都是从哪个module 或者第三方包引入的,然后再用exclude命令,把不要的版本去除。
例如

implementation (‘com.google.firebase:firebase-core:15.0.0’) {
exclude group: ‘com.android.support’, module: ‘support-v4’
}

那么,怎么查真个工程的依赖呢?网上很多说,用命令行执行:gradle -q dependencies。
这个在windows下,很容易出错,原因是gradle 这个可执行文件,并不在当前工程下,而是在AndroidStudio的安装目录,没有加到环境变量,就不能执行。
其实没必要用命令行,可以用AndroidStudio集成好的入口:

在AndroidStudio的右边,以下入口,双击dependencies,就可以了。
如果是要整个app的关系,则双击的是module app下的help/dependencies。如果是只想看其他子模块,则在对应的模块,双击help/dependencies即可。

%title插图%num

依赖关系结果示例:

%title插图%num
如果duplicate的包,真的有多个版本,把以上依赖关系数据,复制到文本编辑器,搜module名字(如android-support),就能找到对应得包,以及是哪个包带入的。

异常、堆内存溢出、OOM的几种情况

1堆内存溢出
2Java异常
OOM
1、堆内存溢出
【情况一】:
java.lang.OutOfMemoryError: Java heap space:这种是java堆内存不够,一个原因是真不够,另一个原因是程序中有死循环;
如果是java堆内存不够的话,可以通过调整JVM下面的配置来解决:
< jvm-arg>-Xms3062m < / jvm-arg>
< jvm-arg>-Xmx3062m < / jvm-arg>

【情况二】
java.lang.OutOfMemoryError: GC overhead limit exceeded
【解释】:JDK6新增错误类型,当GC为释放很小空间占用大量时间时抛出;一般是因为堆太小,导致异常的原因,没有足够的内存。
【解决方案】:
1、查看系统是否有使用大内存的代码或死循环;
2、通过添加JVM配置,来限制使用内存:
< jvm-arg>-XX:-UseGCOverheadLimit< /jvm-arg>

【情况三】:
java.lang.OutOfMemoryError: PermGen space:这种是P区内存不够,可通过调整JVM的配置:
< jvm-arg>-XX:MaxPermSize=128m< /jvm-arg>
< jvm-arg>-XXermSize=128m< /jvm-arg>
【注】:
JVM的Perm区主要用于存放Class和Meta信息的,Class在被Loader时就会被放到PermGen space,这个区域成为年老代,GC在主程序运行期间不会对年老区进行清理,默认是64M大小,当程序需要加载的对象比较多时,超过64M就会报这部分内存溢出了,需要加大内存分配,一般128m足够。

【情况四】:
java.lang.OutOfMemoryError: Direct buffer memory
调整-XX:MaxDirectMemorySize= 参数,如添加JVM配置:
< jvm-arg>-XX:MaxDirectMemorySize=128m< /jvm-arg>

【情况五】:
java.lang.OutOfMemoryError: unable to create new native thread
【原因】:Stack空间不足以创建额外的线程,要么是创建的线程过多,要么是Stack空间确实小了。
【解决】:由于JVM没有提供参数设置总的stack空间大小,但可以设置单个线程栈的大小;而系统的用户空间一共是3G,除了Text/Data/BSS /MemoryMapping几个段之外,Heap和Stack空间的总量有限,是此消彼长的。因此遇到这个错误,可以通过两个途径解决:
1.通过 -Xss启动参数减少单个线程栈大小,这样便能开更多线程(当然不能太小,太小会出现StackOverflowError);
2.通过-Xms -Xmx 两参数减少Heap大小,将内存让给Stack(前提是保证Heap空间够用)。

【情况六】:
java.lang.StackOverflowError
【原因】:这也内存溢出错误的一种,即线程栈的溢出,要么是方法调用层次过多(比如存在无限递归调用),要么是线程栈太小。
【解决】:优化程序设计,减少方法调用层次;调整-Xss参数增加线程栈大小。

2、Java异常

Throwable
Throwable是 Java 语言中所有错误或异常的超类。
Throwable包含两个子类: Error 和 Exception 。它们通常用于指示发生了异常情况。
Throwable包含了其线程创建时线程执行堆栈的快照,它提供了printStackTrace()等接口用于获取堆栈跟踪数据等信息。

Exception
Exception及其子类是 Throwable 的一种形式,它指出了合理的应用程序想要捕获的条件。

RuntimeException
RuntimeException是那些可能在 Java 虚拟机正常运行期间抛出的异常的超类。
编译器不会检查RuntimeException异常。 例如,除数为零时,抛出ArithmeticException异常。RuntimeException是ArithmeticException的超类。当代码发生除数为零的情况时,倘若既”没有通过throws声明抛出ArithmeticException异常”,也”没有通过try…catch…处理该异常”,也能通过编译。这就是我们所说的”编译器不会检查RuntimeException异常”!
如果代码会产生RuntimeException异常,则需要通过修改代码进行避免。 例如,若会发生除数为零的情况,则需要通过代码避免该情况的发生!

Error
和Exception一样, Error也是Throwable的子类。 它用于指示合理的应用程序不应该试图捕获的严重问题,大多数这样的错误都是异常条件。
和RuntimeException一样, 编译器也不会检查Error。

Java将可抛出(Throwable)的结构分为三种类型: 被检查的异常(Checked Exception),运行时异常(RuntimeException)和错误(Error)。

(01) 运行时异常
定义 : RuntimeException及其子类都被称为运行时异常。
特点 : Java编译器不会检查它。 也就是说,当程序中可能出现这类异常时,倘若既”没有通过throws声明抛出它”,也”没有用try-catch语句捕获它”,还是会编译通过。例如,除数为零时产生的ArithmeticException异常,数组越界时产生的IndexOutOfBoundsException异常,fail-fail机制产生的ConcurrentModificationException异常等,都属于运行时异常。
虽然Java编译器不会检查运行时异常,但是我们也可以通过throws进行声明抛出,也可以通过try-catch对它进行捕获处理。
如果产生运行时异常,则需要通过修改代码来进行避免。 例如,若会发生除数为零的情况,则需要通过代码避免该情况的发生!

(02) 被检查的异常
定义 : Exception类本身,以及Exception的子类中除了”运行时异常”之外的其它子类都属于被检查异常。
特点 : Java编译器会检查它。 此类异常,要么通过throws进行声明抛出,要么通过try-catch进行捕获处理,否则不能通过编译。例如,CloneNotSupportedException就属于被检查异常。当通过clone()接口去克隆一个对象,而该对象对应的类没有实现Cloneable接口,就会抛出CloneNotSupportedException异常。
被检查异常通常都是可以恢复的。

(03) 错误
定义 : Error类及其子类。
特点 : 和运行时异常一样,编译器也不会对错误进行检查。
当资源不足、约束失败、或是其它程序无法继续运行的条件发生时,就产生错误。程序本身无法修复这些错误的。例如,VirtualMachineError就属于错误。
按照Java惯例,我们是不应该是实现任何新的Error子类的!

对于上面的3种结构,我们在抛出异常或错误时,到底该哪一种?《Effective Java》中给出的建议是: 对于可以恢复的条件使用被检查异常,对于程序错误使用运行时异常。

OOM
1, OutOfMemoryError异常

除了程序计数器外,虚拟机内存的其他几个运行时区域都有发生OutOfMemoryError(OOM)异常的可能,

Java Heap 溢出

一般的异常信息:java.lang.OutOfMemoryError:Java heap spacess

java堆用于存储对象实例,我们只要不断的创建对象,并且保证GC Roots到对象之间有可达路径来避免垃圾回收机制清除这些对象,就会在对象数量达到*大堆容量限制后产生内存溢出异常。

出现这种异常,一般手段是先通过内存映像分析工具(如Eclipse Memory Analyzer)对dump出来的堆转存快照进行分析,重点是确认内存中的对象是否是必要的,先分清是因为内存泄漏(Memory Leak)还是内存溢出(Memory Overflow)。

如果是内存泄漏,可进一步通过工具查看泄漏对象到GC Roots的引用链。于是就能找到泄漏对象时通过怎样的路径与GC Roots相关联并导致垃圾收集器无法自动回收。

如果不存在泄漏,那就应该检查虚拟机的参数(-Xmx与-Xms)的设置是否适当。

2, 虚拟机栈和本地方法栈溢出

如果线程请求的栈深度大于虚拟机所允许的*大深度,将抛出StackOverflowError异常。

如果虚拟机在扩展栈时无法申请到足够的内存空间,则抛出OutOfMemoryError异常

这里需要注意当栈的大小越大可分配的线程数就越少。

3, 运行时常量池溢出

异常信息:java.lang.OutOfMemoryError:PermGen space

如果要向运行时常量池中添加内容,*简单的做法就是使用String.intern()这个Native方法。该方法的作用是:如果池中已经包含一个等于此String的字符串,则返回代表池中这个字符串的String对象;否则,将此String对象包含的字符串添加到常量池中,并且返回此String对象的引用。由于常量池分配在方法区内,我们可以通过-XX:PermSize和-XX:MaxPermSize限制方法区的大小,从而间接限制其中常量池的容量。

4, 方法区溢出

方法区用于存放Class的相关信息,如类名、访问修饰符、常量池、字段描述、方法描述等。

异常信息:java.lang.OutOfMemoryError:PermGen space

方法区溢出也是一种常见的内存溢出异常,一个类如果要被垃圾收集器回收,判定条件是很苛刻的。在经常动态生成大量Class的应用中,要特别注意这点。

 

几种可能导致OOM异常的情况

本文将讲述两种可能导致OOM的案例
注意:1、程序计数器不会发生OOM
2、在jdk1.8中已经取消了永久代概念,改由元空间取代,就算设置-XX:MetaspaceSize=1m;这种参数限制大小,实际操作时并没有起到多大用处,因此很难通过简单的demo复现以前老年代产生OOM的异常。

由于递归深度过长导致
jvm对递归深度有限制,具体深度由于jdk 版本等的不同有差异,下面这个案例使用jdk1.8测试,递归深度测试为7893,附测试案例:

static int stackDeep = 0;
public static void main(String[] args) {
try{
TestStackOverFlow testStakOverFlow = new TestStackOverFlow();
testStakOverFlow.foo();
}catch(Throwable t){
System.out.println(t);
System.out.println(“栈的深度为:” + stackDeep);
}
}

public void foo() {
stackDeep ++ ;
foo();
}

测试结果:

java.lang.StackOverflowError
栈的深度为:7893

堆溢出
运行时堆溢出是一种常见的溢出情况,下面展示其中的一种,有感兴趣的朋友可以使用Set来代替List重写该案例,异常堆栈信息将不同:
设置JVM参数:

-Xmx1m -XX:+PrintGCDetails

测试代码:

int i = 0;
while(true){
test.add(String.valueOf(i++).intern());
}

异常日志打印如下:

[Full GC (Allocation Failure) [Tenured: 1407K->1408K(1408K), 0.0080421 secs] 1855K->1820K(1984K), [Metaspace: 82K->82K(4480K)], 0.0080726 secs] [Times: user=0.00 sys=0.00, real=0.01 secs]
Exception in thread “main” java.lang.OutOfMemoryError: Java heap space
at java.util.Arrays.copyOf(Unknown Source)…

可以看到,*后一次GC,是full GC,异常报错在扩容的地方,由于空间不够,扩容时申请不到空间,引发OOM。

小结:OOM发生的情况很多,很多时候需要结合实际产生的问题来具体分析,比如:机器实际内存2g,设置堆大小:-Xmx与-Xms为1.8g,而使用NIO的情况很多,那么可能由于直接内存不够导致异常;另外大对象的使用不当也可能导致OOM;再比如,大量数据直接使用内存作为缓存,也可能导致OOM…很多时候,可以根据JVM参数(比如设置堆栈等的大小,GC收集器等)或者增加机器配置来进行调优。实际上,很多OOM,都是程序不当引起的,因此出现该情况,应首先考虑程序问题。

Android Things APP版本更新的解决方案

Android Things中如果使用传统的Intent来安装APK,你将永远安装不上

常见的应用内APK安装方法有以下两种方法

1.Intent安装APK的方法如下:

Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
Uri contentUri = FileProvider.getUriForFile(this, BuildConfig.APPLICATION_ID + “.fileProvider”, new File(path + “app-debug.apk”));
intent.setDataAndType(contentUri, “application/vnd.android.package-archive”);
startActivity(intent);

同时添加权限:

<uses-permission android:name=”android.permission.REQUEST_INSTALL_PACKAGES”/>
2.通过install命令来安装

命令如下:

pm install -i 当前包名 –user 0 XX.apk
运行方法:

Runtime.getRuntime().exec(“”)
注意:以上两种方法在android 手机中没有问题,但截止目前在Android Things 1.0.1中是行不通的

为了能在Android Things系统中更新APP,故经过不断的探索发现,*终采用插件化的方案来解决此问题

%title插图%num
经过评估测试,*终发现360出品的RePlugin框架满足我们的需求

https://github.com/Qihoo360/RePlugin

在RePlugin壳子中,必须申请完所有权限,因为壳子是不会更新的。我整理了所有的权限,在下面列出来

<!–Android Things所有权限–>
<uses-permission android:name=”com.google.android.things.permission.MANAGE_BLUETOOTH” />
<uses-permission android:name=”com.google.android.things.permission.PERFORM_UPDATE_NOW” />
<uses-permission android:name=”com.google.android.things.permission.USE_PERIPHERAL_IO” />
<uses-permission android:name=”com.google.android.things.permission.SET_TIME” />
<uses-permission android:name=”com.google.android.things.permission.REBOOT” />

<!–Android所有权限–>
<uses-permission android:name=”andriod.permission.ACCESS_CHECKIN_PROPERTIES”></uses-permission>
<uses-permission android:name=”android.permission.ACCESS_COARSE_LOCATION” />
<uses-permission android:name=”android.permission.ACCESS_FINE_LOCATION”/>
<uses-permission android:name=”android.permission.ACCESS_LOCATION_EXTRA_COMMANDS”/>
<uses-permission android:name=”android.permission.ACCESS_NETWORK_STATE”/>
<uses-permission android:name=”android.permission.ACCESS_NOTIFICATION_POLICY”/>
<uses-permission android:name=”android.permission.ACCESS_WIFI_STATE”/>
<uses-permission android:name=”android.permission.ADD_VOICEMAIL”/>
<uses-permission android:name=”android.permission.BLUETOOTH”/>
<uses-permission android:name=”android.permission.BLUETOOTH_ADMIN”/>
<uses-permission android:name=”android.permission.BODY_SENSORS”/>
<uses-permission android:name=”android.permission.BROADCAST_STICKY”/>
<uses-permission android:name=”android.permission.CALL_PHONE”/>
<uses-permission android:name=”android.permission.CAMERA”/>
<uses-permission android:name=”android.permission.CHANGE_NETWORK_STATE”/>
<uses-permission android:name=”android.permission.CHANGE_WIFI_MULTICAST_STATE”/>
<uses-permission android:name=”android.permission.CHANGE_WIFI_STATE”/>
<uses-permission android:name=”android.permission.DISABLE_KEYGUARD”></uses-permission>
<uses-permission android:name=”android.permission.EXPAND_STATUS_BAR”></uses-permission>
<uses-permission android:name=”android.permission.GET_ACCOUNTS”/>
<uses-permission android:name=”android.permission.GET_PACKAGE_SIZE”/>
<uses-permission android:name=”android.permission.GET_TASKS”/>
<uses-permission android:name=”android.permission.INSTALL_SHORTCUT”/>
<uses-permission android:name=”android.permission.INTERNET”/>
<uses-permission android:name=”android.permission.KILL_BACKGROUND_PROCESSES”/>
<uses-permission android:name=”android.permission.MODIFY_AUDIO_SETTINGS”/>
<uses-permission android:name=”android.permission.NFC”></uses-permission>
<uses-permission android:name=”android.permission.PERSISTENT_ACTIVITY”></uses-permission>
<uses-permission android:name=”android.permission.PROCESS_OUTGOING_CALLS”></uses-permission>
<uses-permission android:name=”android.permission.READ_CALENDAR”/>
<uses-permission android:name=”android.permission.READ_CALL_LOG”/>
<uses-permission android:name=”android.permission.READ_CONTACTS”/>
<uses-permission android:name=”android.permission.READ_EXTERNAL_STORAGE”/>
<uses-permission android:name=”android.permission.READ_PHONE_STATE”/>
<uses-permission android:name=”android.permission.READ_SMS”/>
<uses-permission android:name=”android.permission.READ_SYNC_SETTINGS”/>
<uses-permission android:name=”android.permission.READ_SYNC_STATS”/>
<uses-permission android:name=”android.permission.READ_VOICEMAIL”/>
<uses-permission android:name=”android.permission.RECEIVE_BOOT_COMPLETED”/>
<uses-permission android:name=”android.permission.RECEIVE_MMS”/>
<uses-permission android:name=”android.permission.RECEIVE_SMS”/>
<uses-permission android:name=”android.permission.RECEIVE_WAP_PUSH”/>
<uses-permission android:name=”android.permission.RECORD_AUDIO”/>
<uses-permission android:name=”android.permission.REORDER_TASKS”/>
<uses-permission android:name=”android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS”/>
<uses-permission android:name=”android.permission.REQUEST_INSTALL_PACKAGES”/>
<uses-permission android:name=”android.permission.RESTART_PACKAGES”/>
<uses-permission android:name=”android.permission.SEND_SMS”/>
<uses-permission android:name=”android.permission.SET_ALARM”/>
<uses-permission android:name=”android.permission.SET_WALLPAPER”/>
<uses-permission android:name=”android.permission.SET_WALLPAPER_HINTS”/>
<uses-permission android:name=”android.permission.SYSTEM_ALERT_WINDOW”/>
<uses-permission android:name=”android.permission.TRANSMIT_IR”/>
<uses-permission android:name=”android.permission.UNINSTALL_SHORTCUT”/>
<uses-permission android:name=”android.permission.USE_FINGERPRINT”/>
<uses-permission android:name=”android.permission.USE_SIP”/>
<uses-permission android:name=”android.permission.VIBRATE”/>
<uses-permission android:name=”android.permission.WAKE_LOCK”/>
<uses-permission android:name=”android.permission.WRITE_CALENDAR”/>
<uses-permission android:name=”android.permission.WRITE_CALL_LOG”/>
<uses-permission android:name=”android.permission.WRITE_CONTACTS”/>
<uses-permission android:name=”android.permission.WRITE_EXTERNAL_STORAGE”/>
<uses-permission android:name=”android.permission.WRITE_SYNC_SETTINGS”/>
<uses-permission android:name=”android.permission.WRITE_VOICEMAIL”/>

<!–所有系统权限–>
<uses-permission android:name=”android.permission.ACCOUNT_MANAGER”/>
<uses-permission android:name=”android.permission.BATTERY_STATS”/>
<uses-permission android:name=”android.permission.BIND_ACCESSIBILITY_SERVICE”/>
<uses-permission android:name=”android.permission.BIND_CARRIER_MESSAGING_SERVICE”/>
<uses-permission android:name=”android.permission.BIND_CARRIER_SERVICES”/>
<uses-permission android:name=”android.permission.BIND_CHOOSER_TARGET_SERVICE”/>
<uses-permission android:name=”android.permission.BIND_DEVICE_ADMIN”/>
<uses-permission android:name=”android.permission.BIND_CONDITION_PROVIDER_SERVICE”/>
<uses-permission android:name=”android.permission.BIND_DREAM_SERVICE”/>
<uses-permission android:name=”android.permission.BIND_INCALL_SERVICE”/>
<uses-permission android:name=”android.permission.BIND_INPUT_METHOD”/>
<uses-permission android:name=”android.permission.BIND_MIDI_DEVICE_SERVICE”/>
<uses-permission android:name=”android.permission.BIND_NFC_SERVICE”/>
<uses-permission android:name=”android.permission.BIND_NOTIFICATION_LISTENER_SERVICE”/>
<uses-permission android:name=”android.permission.BIND_PRINT_SERVICE”/>
<uses-permission android:name=”android.permission.BIND_QUICK_SETTINGS_TILE”/>
<uses-permission android:name=”android.permission.BIND_REMOTEVIEWS”/>
<uses-permission android:name=”android.permission.BIND_SCREENING_SERVICE”/>
<uses-permission android:name=”android.permission.BIND_TELECOM_CONNECTION_SERVICE”/>
<uses-permission android:name=”android.permission.BIND_TEXT_SERVICE”></uses-permission>
<uses-permission android:name=”android.permission.BIND_TV_INPUT”></uses-permission>
<uses-permission android:name=”android.permission.BIND_VOICE_INTERACTION”></uses-permission>
<uses-permission android:name=”android.permission.BIND_*_SERVICE”></uses-permission>
<uses-permission android:name=”android.permission.BIND_VR_LISTENER_SERVICE”/>
<uses-permission android:name=”android.permission.BIND_WALLPAPER”/>
<uses-permission android:name=”android.permission.BLUETOOTH_PRIVILEGED”/>
<uses-permission android:name=”android.permission.BROADCAST_PACKAGE_REMOVED”/>
<uses-permission android:name=”android.permission.BROADCAST_SMS”/>
<uses-permission android:name=”android.permission.BROADCAST_WAP_PUSH”/>
<uses-permission android:name=”android.permission.CALL_PRIVILEGED”/>
<uses-permission android:name=”android.permission.CAPTURE_AUDIO_OUTPUT”/>
<uses-permission android:name=”android.permission.CAPTURE_SECURE_VIDEO_OUTPUT”/>
<uses-permission android:name=”android.permission.CAPTURE_VIDEO_OUTPUT”/>
<uses-permission android:name=”android.permission.CHANGE_COMPONENT_ENABLED_STATE”/>
<uses-permission android:name=”android.permission.CHANGE_CONFIGURATION”/>
<uses-permission android:name=”android.permission.CLEAR_APP_CACHE”/>
<uses-permission android:name=”android.permission.CONTROL_LOCATION_UPDATES”/>
<uses-permission android:name=”android.permission.DELETE_CACHE_FILES”/>
<uses-permission android:name=”android.permission.DELETE_PACKAGES”/>
<uses-permission android:name=”android.permission.DIAGNOSTIC”/>
<uses-permission android:name=”android.permission.DUMP”></uses-permission>
<uses-permission android:name=”android.permission.FACTORY_TEST”></uses-permission>
<uses-permission android:name=”android.permission.GET_ACCOUNTS_PRIVILEGED”/>
<uses-permission android:name=”android.permission.GLOBAL_SEARCH”/>
<uses-permission android:name=”android.permission.INSTALL_LOCATION_PROVIDER”/>
<uses-permission android:name=”android.permission.INSTALL_PACKAGES”/>
<uses-permission android:name=”android.permission.LOCATION_HARDWARE”/>
<uses-permission android:name=”android.permission.MANAGE_DOCUMENTS”/>
<uses-permission android:name=”android.permission.MASTER_CLEAR”/>
<uses-permission android:name=”android.permission.MEDIA_CONTENT_CONTROL”/>
<uses-permission android:name=”android.permission.MODIFY_PHONE_STATE”/>
<uses-permission android:name=”android.permission.MOUNT_FORMAT_FILESYSTEMS”/>
<uses-permission android:name=”android.permission.MOUNT_UNMOUNT_FILESYSTEMS”/>
<uses-permission android:name=”android.permission.PACKAGE_USAGE_STATS”></uses-permission>
<uses-permission android:name=”android.permission.READ_FRAME_BUFFER”/>
<uses-permission android:name=”android.permission.READ_INPUT_STATE”/>
<uses-permission android:name=”android.permission.READ_LOGS”/>
<uses-permission android:name=”android.permission.REBOOT”/>
<uses-permission android:name=”android.permission.SEND_RESPOND_VIA_MESSAGE”/>
<uses-permission android:name=”android.permission.SET_ALWAYS_FINISH”/>
<uses-permission android:name=”android.permission.SET_ANIMATION_SCALE”/>
<uses-permission android:name=”android.permission.SET_DEBUG_APP”/>
<uses-permission android:name=”android.permission.SET_PREFERRED_APPLICATIONS”></uses-permission>
<uses-permission android:name=”android.permission.SET_PROCESS_LIMIT”></uses-permission>
<uses-permission android:name=”android.permission.SET_TIME”></uses-permission>
<uses-permission android:name=”android.permission.SET_TIME_ZONE”></uses-permission>
<uses-permission android:name=”android.permission.SIGNAL_PERSISTENT_PROCESSES”/>
<uses-permission android:name=”android.permission.STATUS_BAR”/>
<uses-permission android:name=”android.permission.UPDATE_DEVICE_STATS”/>
<uses-permission android:name=”android.permission.WRITE_APN_SETTINGS”/>
<uses-permission android:name=”android.permission.WRITE_GSERVICES”/>
<uses-permission android:name=”android.permission.WRITE_SECURE_SETTINGS”/>
<uses-permission android:name=”android.permission.WRITE_SETTINGS”/>
但是,光这些还不够,还需要解决两个坑

1.关于FileProvider的问题

我们需要提前在RePlugin中申请注册,Replugin是不会动态申请FileProvider

所以,需要在宿主程序中添加:

<provider
android:name=”android.support.v4.content.FileProvider”
android:authorities=”宿主包名.fileProvider”
android:exported=”false”
android:grantUriPermissions=”true”>
<meta-data
android:name=”android.support.FILE_PROVIDER_PATHS”
android:resource=”@xml/file_paths” />
</provider>
注意一点:此处填写宿主包名,当我们在插件中使用context.getPackageName()的时候,拿到的是宿主包名

2.在Android Things中APP默认是横屏,但当我们加载打开插件时,会发现APP被强制改为了竖屏

在这里,尝试反编译宿主APK,打开AndroidManifest文件后,发现screenOrientation的值为1,故RePlugin框架默认是将所有的Activity坑位默认设置的portrait竖屏

<activity
android:theme=”@ref/0x01030006″
android:name=”com.xx.a.ActivityP2NRNTS1″
android:exported=”false”
android:process=”:p2″
android:screenOrientation=”1″
android:configChanges=”0x4b0″ />
解决方案如下,修改RePlugin源码,找到replugin-host-gradle源码,修改ComponentsGenerator类,添加oriL变量,然后将所有的oriV改为oriL

 

Android studio安装与配置

Android studio安装与配置

1、首先下载Android studio安装包,可以从http://www.android-studio.org/下载*新版本,这里采用3.0版本进行演示,对应安装包为android-studio-ide-171.4408382-windows.exe,安装包大小681 MB,安装包不带SDK

 

 

%title插图%num

2、下载好该安装包之后,点击进行安装,依次出现以下界面

%title插图%num%title插图%num

 

%title插图%num

在这里自己选择程序安装路径

%title插图%num

%title插图%num%title插图%num

%title插图%num

这里Android studio程序安装完毕,但是还需要继续对其进行配置;勾选Start Android Studio,然后点击finish启动AS,出现下图

%title插图%num

 

 

 

 

选择第二项,然后点击ok,出现下面的启动界面

%title插图%num

在启动的时候会弹出下图

%title插图%num

点击cancel,然后进入到了AS的安装向导界面

%title插图%num

点击next进入UI界面主题选择界面,可以选择自己喜欢的风格,这里选择Darcula风格

%title插图%num

%title插图%num

这里需要指定SDK的本地路径,如果之前电脑中已经存在SDK,可以指定该路径,后续就可以不用下载SDK;我这里演示本地没有安装过SDK的场景,这里暂时可以指定一个后续将保存SDK的路径;

%title插图%num

%title插图%num

点击Finish后,开始自动下载SDK(注意,此时需要保证电脑联网)

%title插图%num%title插图%num

 

 

 

下载完成SDK后,点击Finish进入AS的欢迎界面

%title插图%num

3、配置AS*次运行环境,并且能成功编译运行一个APP,以helloworld为例。

点击上图中的Start a new Android Studio project新建一个工程,进入下面的界面

%title插图%num

%title插图%num%title插图%num

 

%title插图%num%title插图%num

到此,一个工程建立完成,*次建立的工程会发现卡在下面的启动界面

%title插图%num

*次建立工程卡在该界面的时候,是因为在从网上下载gradle构建工具,由于从国外站点下载,网速很慢,这里点击取消关闭该界面,采用手动配置gradle;

首先找到.gradle文件夹的路径,一般是在用户账号文件夹下,比如我这里是C:\Users\issuser\.gradle

会发现该文件夹下生成了下图中的文件

%title插图%num

 

 

这里的gradle-4.1,指的是版本,它会根据你的AS版本自动生成,此时我们可以去网上下载一个gradle-4.1-all.zip压缩包,然后放到该路径下并且进行解压,注意一定要放到这个随机生成的一长串字符的文件夹下面,如下图

%title插图%num

%title插图%num

此时点击图中下方的链接进行SDK下载,这里可能一次下载之后,执行Try again之后这里还是会显示报错,那么就再点击下载一次,然后再点击Try again,直到报错解除。(除了该解决办法,还可以手动更改build.gradle文件中的compileSdkVersion,buildToolsVersion
targetSdkVersion为对应的27也可以进行解决,这个后续再讲)

%title插图%num%title插图%num

%title插图%num%title插图%num

上述gradle构建完成之后,就可以点击下图中的build apk编译打包apk文件了,生成的apk文件路径如下图所示

%title插图%num

生成apk文件之后,导出该apk文件到模拟器或者真机上面进行安装,运行效果图如下

%title插图%num

 

 

 

 

至此,Android Studio的安装以及开发环境就配置好了。

 

CentOS配置简单的MC服务器配置

首先自己的硬件要求有:

%title插图%num

需要的东西我全部例举出来了

安装服务器操作系统:
这个我不想演示了,很简单,百度上教程一大堆,下面直接跳到配置环节。

(1)安装SSH服务:
yum -y install openssh-server
如果提示以下情况:

未安装”yum”请执行
sudo apt install yum

就直接按照上面的提示直接安装yum,(这种情况出现在*个别的老版本CentOS里面)

安装完毕之后查看自己的服务器地址:
ifconfig //查看本机内网IPV4地址

在SSH管理软件中输入本机的登陆账号以及密码之后安装vsftpd

yum -y install vsftpd //安装FTP组件

XFTP软件连接之后自己找一个文件夹做游戏的根目录,作者使用/var/mcserver目录

在Windows管理机下用.txt文件编辑

java -Xms 6144M -Xmx 1024M -jar XXXX.jar //XXXX.jar是你的核心文件名字

把后缀从txt改成.sh

在控制台输入:

cd /var/mcserver
ls -al
sh ./start.sh

直接启动核心文件选择语言版本,此服务器启动成功!!!!

服务器连接问题:
不用什么软件,直接使用一个很简单的
用路由器自带的ddns功能,直接设置xxxxx.AP厂商.cn
当然,公网的IPV4地址还是重要滴!
没有公网地址是无法正常解析的,否则就需要内网穿透,不穿透无效。

如果使用的是内网联机用的服务器,只需要内网IPV4地址+服务器开启端口号
内网服务器的配置方式和上面一样,如果想要开启某些氪金系统,就搭配MySQL,Mircosoft SQL就算了,我之前尝试过,答案是不能(不知道是我的设置的问题还是其他的**总之这个目前用的人很少,所以不做教程,想折腾的自己去研究研究)

android studio和jdk环境设置

文章目录
1、Jdk安装及环境设置
1.1、 安装jdk一步步安装就可以 了,接着是设置环境变量
1.2、 新增JAVA_HOME(名字大小写一定要一致)
1.3、 新增classpath .;%JAVA_HOME%\lib
1.4、 path字段下变量值新增%JAVA_HOME%\bin
1.5、判断jdk是否安装成功标志
2、Android studio 安装及环境设置
2.1、当新建项目后,编译后面没有自动显示app那个运行项的话,那么只要选择

jdk的下载地址
https://www.oracle.com/java/technologies/javase-downloads.html

 

1、Jdk安装及环境设置
1.1、 安装jdk一步步安装就可以 了,接着是设置环境变量

%title插图%num
1.2、 新增JAVA_HOME(名字大小写一定要一致)

%title插图%num
1.3、 新增classpath .;%JAVA_HOME%\lib

%title插图%num
1.4、 path字段下变量值新增%JAVA_HOME%\bin

%title插图%num
1.5、判断jdk是否安装成功标志
Cmd窗口输入java, javac都能提示正确信息

%title插图%num
Android studio 下载地址
http://www.android-studio.org/

2、Android studio 安装及环境设置
全部一步步单击往下安装就可以,

2.1、当新建项目后,编译后面没有自动显示app那个运行项的话,那么只要选择
File–>sync project with Gradle file 让其自动去下载文件就可以了。

%title插图%num