编程环境

  • Android Studio 3.0.1

问题

  • MyPrivacy 在android M上闪退,在模拟器中复现.提示
    1
    android.util.AndroidRuntimeException: Calling startActivity() from outside of an Activity  context requires the FLAG_ACTIVITY_NEW_TASK flag. Is this really what you want?

过程

  • 进入第二个Activity应用设置时,才出现,而且 7.1无问题.模拟器复现,抓log.

解决

  • log的意思是启动activity的context不是 activity.
  • 对应代码
    1
    2
    3
    Intent intent = new Intent(MyApplicantion.getContext(),   AppSettingActivity.class);
    intent.putExtra("PackageName", AppId);
    MyApplicantion.getContext().startActivity(intent);
  • 需要对intent声明 FLAG_ACTIVITY_NEW_TASK
    1
    intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);

备注

  • 网上的原因分析:
  • 因为standard模式的Activity默认会进入启动它的Activity所属的任务栈中,但是由于非Activity类型的context(ApplicationContext)并没有所谓的任务栈,所以就出现问题了。需要指定Activity为FLAG_ACTIVITY_NEW_TASK标记位,这样启动的时候,就会为它创建一个新的任务栈了。–android开发艺术探究


编程环境

  • Android Studio 3.0

问题

  • 横屏模式下,显示内容被遮挡

过程

  • Google😂

解决

  • ScrollView 只允许嵌套一个子布局.超出范围部分会自动增加滚动条

备注

  • ScrollView 只能添加竖直方向滚动条.


资料来源如下
给初学者的RxJava2.0教程(demo代码来源)

http://www.jianshu.com/u/c50b715ccaeb

编程环境

  • Android Studio 2.2.3

  • 在Gradle配置:
    1
    2
    compile 'io.reactivex.rxjava2:rxjava:2.0.1'
    compile 'io.reactivex.rxjava2:rxandroid:2.0.1'

观察者模式

  • 在Eventbus中亦涉及了相关概念,比较简单.包括Observable(被观察者)、Observer(观察者)、subscribe().事件由Observable(被观察者)开始发出,通过subscribe()最终被传递到Observer(观察者).而整个过程中你是站在Observer(观察者)的位置,也就是事件的末尾,观察Observable(被观察者).

  • 上图

  • demo

    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
    Observable.create(new ObservableOnSubscribe<Integer>() {
    @Override
    public void subscribe(ObservableEmitter<Integer> emitter) throws Exception {
    emitter.onNext(1);
    emitter.onComplete();
    }
    }).subscribe(new Observer<Integer>() {
    @Override
    public void onSubscribe(Disposable d) {
    Log.d(TAG, "subscribe");
    }

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

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

    @Override
    public void onComplete() {
    Log.d(TAG, "complete");
    }
    });
  • 事件由emitter.onNext(1);开始,最终被public void onNext(Integer value)相应.被观察者事件发送结束调用emitter.onComplete();,同时观察者最终以public void onComplete()相应.


  • note: 上下游以.subscribe建立连接后,事件才会开始发送.

Observable(被观察者) Observer(观察者)

  • ObservableEmitter
    ObservableEmitter: Emitter意为发射器,事件发送.onNext(T value)、onComplete()和onError(Throwable error)分别对应next事件、complete事件和error事件。
  • Observer中onNext(Integer value)、onError(Throwable e)、onComplete()对应接受next事件、complete事件和error事件
  • 被观察者发送complete事件和error事件后,观察者接受后不再继续响应事件,即使被观察者还在发送事件.complete事件和error事件互斥.
  • 在Observer中,调用Disposable.dispose(),切断管道.被观察者继续发送,但观察者不再响应.

subscribe

  • 建立Observable(被观察者) Observer(观察者)之间的管道.有多个重载.
  • 重载
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    public final Disposable subscribe() {}

    public final Disposable subscribe(Consumer<? super T> onNext) {}

    public final Disposable subscribe(Consumer<? super T> onNext, Consumer<? super Throwable> onError) {}

    public final Disposable subscribe(Consumer<? super T> onNext, Consumer<? super Throwable> onError, Action onComplete) {}

    public final Disposable subscribe(Consumer<? super T> onNext, Consumer<? super Throwable> onError, Action onComplete, Consumer<? super Disposable> onSubscribe) {}

    public final void subscribe(Observer<? super T> observer) {}
  • 重载说明
    • 不带参数 : 观察者不关心任何事件(有卵用😵)
    • 只带onNext : 观察者只响应next事件.
    • 其他类似….演绎推理….
    • 最后一个是传入完整的Observer对象.(demo就是🙃)

操作符

  • 基于Rxjava的观察者模式可以拆分大多数的业务逻辑,即使再增加很多功能整体也不会过于混乱.
  • 但Rxjava的强大并不局限在拆分逻辑.由被观察者到观察者的整个事件传递过程,基于Rxjava我们可以任意拆分 合并 转换 事件、切换线程等.

  • note: 操作符搭配 Lambda 表达式食用更佳 🤣

创建

  • 产生并发送 Obserable 事件.
  • 仅常用,详细在 RxJava 2.x 使用详解(二) 创建操作符

    .creat

  • 前面demo中已经实际使用过了
  • 用于产生一个 Obserable 被观察者对象,demo如上所示.

.just

  • 对于简单的几个数据,直接使用just发送即可,无需创建 Obserable 对象.just最多可以接收 10 个参数.
  • demo
    1
    2
    Observable.just("test","test2")
    .subscribe(str -> Log.i("tag", str));
    相当于顺序调用onNext(“test”)和onNext(“test2”),最后调用onComplete方法。

.fromArray

  • 功能与just类似但fromArray来接收任意长度的数据数组,也可以直接传入数组fromArray(new int[]{1, 2, 3}) 
  • demo
    1
    2
    Observable.fromArray(1, 2, 3, 4, 5)
    .subscribe(integer -> Log.i("tag", String.valueOf(integer)));
    fromArray不支持直接传入list进,list会被当作一个整体发送.

.fromIterable

  • 功能与fromArray类似,但是可以接收 list 类型,遍历可迭代数据集合.
  • demo
    1
    2
    3
    4
    5
    6
    7
    8
    List<String> list = new ArrayList<>();
    list.add("a");
    list.add("b");
    list.add("c");

    Flowable.fromIterable(list).subscribe(
    s -> Log.i("tag", s)
    );

.timer

  • 指定一段时间间隔后发送数据(一次性),不太常用.

线程切换

  • 默认事件传递的双方在同一线程工作.

  • demo

    1
    2
    3
    4
    5
    observable.subscribeOn(Schedulers.newThread())
    .subscribeOn(Schedulers.io())
    .observeOn(AndroidSchedulers.mainThread())
    .observeOn(Schedulers.io())
    .subscribe(consumer);
  • 方法:

    • .subscribeOn() : 指定被观察者发送事件线程,仅第一次调用时有效!
    • .observeOn() : 指定观察者/流变换(对发送的事件处理🖖)线程,多次调用,多次切换有效
  • 参数:

    • Schedulers.io() : 适用于io密集型操作,网络通信、磁盘操作等.
    • Schedulers.computation() : CPU密集操作,需要大量CPU计算的操作.
    • Schedulers.newThread() : 创建新线程.
    • AndroidSchedulers.mainThread() : Android主线程,通常为更新UI等.

过滤

  • 对 Obserable 事件筛选.
  • 仅常用,详细在RxJava 2.x 使用详解(三) 过滤操作符

.filter

  • 基本过滤操作符,按照任意自定规则过滤.

组合

转换

.map

  • 处理前后事件数量之比1:1,事件变换前后顺序不变
  • map作用是对Observable发送的每一个事件,应用处理变换函数,再继续像下游发送.中间过程可以转换事件类型、改变事件内容等等.只需要变换后的事件类型与下游接收的类型匹配即可.
  • demo
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    Observable.create(new ObservableOnSubscribe<Integer>() {
    @Override
    public void subscribe(ObservableEmitter<Integer> emitter) throws Exception {
    emitter.onNext(1);
    emitter.onNext(2);
    emitter.onNext(3);
    }
    }).map(new Function<Integer, String>() {
    @Override
    public String apply(Integer integer) throws Exception {
    return "This is result " + integer;
    }
    }).subscribe(new Consumer<String>() {
    @Override
    public void accept(String s) throws Exception {
    Log.d(TAG, s);
    }
    });

    这里是把int类型转换为了string类型,与观察者接收的类型匹配即可.

.flatMap

  • 处理前后事件数量之比 1:n,事件变换前后顺序不保证
  • flatMap,通俗点就是把Observable发送的事件拆散变换再,继续像下游发送.1个Observable事件可拆成任意个.只需要变换后的事件类型与下游接收的类型匹配即可.
  • demo
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    Observable.create(new ObservableOnSubscribe<Integer>() {
    @Override
    public void subscribe(ObservableEmitter<Integer> emitter) throws Exception {
    emitter.onNext(1);
    emitter.onNext(2);
    emitter.onNext(3);
    }
    }).flatMap(new Function<Integer, ObservableSource<String>>() {
    @Override
    public ObservableSource<String> apply(Integer integer) throws Exception {
    final List<String> list = new ArrayList<>();
    for (int i = 0; i < 3; i++) {
    list.add("I am value " + integer);
    }
    return Observable.fromIterable(list).delay(10,TimeUnit.MILLISECONDS);
    }
    }).subscribe(new Consumer<String>() {
    @Override
    public void accept(String s) throws Exception {
    Log.d(TAG, s);
    }
    });

    这里是flatMap把一个int类型事件拆成了3个String类型,运行结果看,最终事件到达顺序与onNext(1);onNext(2);的发送顺序无关

.concatMap

  • 处理前后事件数量之比 1:n,事件变换前后顺序按顺序
  • 与flatMap作用相同,只是保证了事件严格按顺序达到下游.
  • demo 就不上了,直接替换flatMap的位置就好.

.zip

  • 处理前后事件数量之比 n:1,事件变换前后顺序严格按顺序
  • zip.最常见的压缩文件格式,在这里也是类似的意思,zip可以严格按照顺序合并多个 不同类型 Observable发送的事件.总的发送事件数量与上游Observable发送最少的那个数量相同.
  • demo
    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
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    Observable<Integer> observable1 = Observable.create(new ObservableOnSubscribe<Integer>() {
    @Override
    public void subscribe(ObservableEmitter<Integer> emitter) throws Exception {
    Log.d(TAG, "emit 1");
    emitter.onNext(1);
    Thread.sleep(1000);

    Log.d(TAG, "emit 2");
    emitter.onNext(2);
    Thread.sleep(1000);

    Log.d(TAG, "emit 3");
    emitter.onNext(3);
    Thread.sleep(1000);

    Log.d(TAG, "emit 4");
    emitter.onNext(4);
    Thread.sleep(1000);

    Log.d(TAG, "emit complete1");
    emitter.onComplete();
    }
    }).subscribeOn(Schedulers.io());

    Observable<String> observable2 = Observable.create(new ObservableOnSubscribe<String>() {
    @Override public void subscribe(ObservableEmitter<String> emitter) throws Exception {
    Log.d(TAG, "emit A");
    emitter.onNext("A");
    Thread.sleep(1000);

    Log.d(TAG, "emit B");
    emitter.onNext("B");
    Thread.sleep(1000);

    Log.d(TAG, "emit C");
    emitter.onNext("C");
    Thread.sleep(1000);

    Log.d(TAG, "emit complete2");
    emitter.onComplete();
    }
    }).subscribeOn(Schedulers.io());

    Observable.zip(observable1, observable2, new BiFunction<Integer, String, String>() {
    @Override
    public String apply(Integer integer, String s) throws Exception {
    return integer + s;
    }
    }).subscribe(new Observer<String>() {
    @Override
    public void onSubscribe(Disposable d) {
    Log.d(TAG, "onSubscribe");
    }

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

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

    @Override
    public void onComplete() {
    Log.d(TAG, "onComplete");
    }
    });
    这里两个事件发送在同一线程中.当两个事件发送不再同一线程时,情况类似,不过当异步时,数量较少的事件发送完成,发送Complete事件后,通道随即被切断.

.Concat

  • 处理前后事件数量之比 n:1,事件变换前后顺序严格按顺序
  • Concat,可以严格按照顺序合并 相同类型 Observable发送的事件.

Backpressure


  • 被翻译为背压…(如此文不达意的直译,能忍?往下都是因为原文..😈)

  • 其实概念有够简单:将整个事件产生/传递/处理的过程想象为一条河流由上而下, Backpressure 指的是上游产生的事件太快,远远超过了下游的处理速度,以至于缓冲区溢出.上游来了洪水,下游径流量不够,以至于中间河道跨过了堤岸,溢出.

Flowable基础

  • Rxjava 1.x中需要自行通过操作符处理,到了2.0中,则有了专门对付发洪水上游的被观察者- Flowable .我们常用的 observable 在2.x中一般用于不涉及 Backpressure 的情况.而对应与 observable 的 Observer ,改为了 Subscriber .

  • demo

    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
    Flowable<Integer> upstream = Flowable.create(new FlowableOnSubscribe<Integer>() {
    @Override
    public void subscribe(FlowableEmitter<Integer> emitter) throws Exception {
    Log.d(TAG, "emit 1");
    emitter.onNext(1);
    Log.d(TAG, "emit 2");
    emitter.onNext(2);
    Log.d(TAG, "emit 3");
    emitter.onNext(3);
    Log.d(TAG, "emit complete");
    emitter.onComplete();
    }
    }, BackpressureStrategy.ERROR); //增加了一个参数

    Subscriber<Integer> downstream = new Subscriber<Integer>() {

    @Override
    public void onSubscribe(Subscription s) {
    Log.d(TAG, "onSubscribe");
    s.request(Long.MAX_VALUE); //注意这句代码
    }

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

    }

    @Override
    public void onError(Throwable t) {
    Log.w(TAG, "onError: ", t);
    }

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

    upstream.subscribe(downstream);
  • 注意两个地方

    • Flowable创建时比 observable 多了一个参数.(参数作用下节说明)
    • Subscriber中调用了 s.request .
  • Flowable 与 Observable 最大的不同就是 Flowable再次发送事件需要等待 Subscriber 中调用 .request

  • .request() 实质上是下游告知上游自己的处理能力,使得上游根据下游处理能力发送事件.多次调用,上游表示处理能力的数字会叠加,上游每发送一个事件,该数字减一,到0抛出异常

    • 上下游在同一线程时,下游没有或没有及时调用 .request ,上游会抛出异常
    • 异步线程时,下游即使没有调用 .request 会有128个事件的缓存区.上游可继续发出事件,缓存区超出128个事件后,抛出异常.

Flowable拓展

  • 这里对 Flowable 多的参数进行说明.

  • 参数
    • BackpressureStrategy.BUFFER : 默认缓存区128,这个参数极大拓展了缓存区,使得 Flowable 表现与 Observable 差不多.
    • BackpressureStrategy.DROP : 128缓存区满了,就丢弃上游事件,直到下游处理了一个事件,缓存区 -1 ,再允许存入新的上游事件.
    • BackpressureStrategy.LATEST : 永远保存最后达到的128个上游事件,上游有新的事件到达满载的缓存区时,丢弃第一个存入缓存区的上游事件.

  • 对于不是由我们编写的 Flowable 也可以通过 interval 操作符来加工.
    1
    2
    Flowable.interval(1, TimeUnit.MICROSECONDS)
    .onBackpressureDrop() //加上 Backpressure 策略
  • 对应上文,指定参数有3,意思同上.
    • onBackpressureBuffer()
    • onBackpressureDrop()
    • onBackpressureLatest()


编程环境

  • Android Studio 3.0

问题

  • Glide是一个通用的图片缓存框架,但是在MyPrivacy显示appIcon时,传入 一个Drawable对象,提示类型不匹配.

  • (注意: 这里是直接传入Drawable对象,不是经过 R.xx 引用 !)

过程

  • 查阅资料后,确认Glide不支持直接加载传入的Drawable对象,转换为bitDrawable类型也不可.

  • 解决思路来自

    https://github.com/bumptech/glide/issues/588

  • 不支持直接加载,但Glide的.error(Icon)错误时显示 .placeholder(Icon)占位符,支持Drawable对象

解决

  • 不再直接加载 .load 传入空字符串, 通过 .placeholder 简洁加载.
  • demo
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    Drawable Icon = xxx;

    RequestOptions options = new RequestOptions()
    .error(Icon)
    .placeholder(Icon);

    Glide.with(context)
    .load("")
    .apply(options)
    .into(imageView);

    备注

  • placeholder 在Glide 4.x版本中,移入了 RequestOptions 对象中.


  • ubuntu16.04
  • 使用busybox编译最小文件系统,使用qemu运行起来。
  • 内容来自 奔跑吧linux内核第6章
  • 这里将输入代码过程集合到了几个.sh文件,不做重复的工作 !
  • 当然网好是前提,最好挂代理.

安装工具

  • 首先需要安装qemu gcc, ubuntu16.04中自带的gcc版本较低,这里我们安装书中推荐的gcc-arm-linux-gnueabi

    1
    sudo apt-get install qemu libncurses5-dev gcc-arm-linux-gnueabi build-essential
  • 下载busybox源码

    • 书中推荐版本是1.24,但最新版本已经到了busybox-1.27.2.这里我们使用最新版

      1
      wget https://busybox.net/downloads/busybox-1.27.2.tar.bz2
    • 解压到 busybox 文件夹

      1
      tar -jxvf busybox-1.27.2.tar.bz2
  • 下载linux内核源码

    • 还是以配套的4.0源码为例,(提醒:内核解压后大约占800MB,请预留出足够空间)

      1
      wget https://www.kernel.org/pub/linux/kernel/v4.x/linux-4.0.tar.gz
    • 解压到linux文件夹

      1
      tar -jxvf linux-4.0.tar.gz

编译最小文件系统

  • 别问我最小文件系统是什么,我也有点😵,但是先用起来.

  • 首先利用 busybox 手工编译一个最小文件系统。
    在busybox文件夹下

    1
    2
    3
    4
    export ARCH=ARM
    export CROSS_COMPILE=arm-linux-gnueabi-
    make menuconfig
    make install
  • 进入menuconfig后,配置静态编译

    1
    2
    3
    Busybox Settings --->
    Build Options --->
    [*] Build BusyBox as a static binary (no shared libs)
  • 然后 make install 编译完成。编译完成后,把 busybox 根目录下面的_install 目录拷贝到 linux-4.0 下。

  • 进入_install 目录,创建 etc、dev 等目录。

    1
    2
    3
    4
    mkdir etc
    mkdir dev
    mkdir mnt
    mkdir -p etc/init.d/
  • 在_install /etc/init.d/目录下创建 文件名rcS 的文件,写入以下内容:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    mkdir –p /proc
    mkdir –p /tmp
    mkdir -p /sys
    mkdir –p /mnt
    /bin/mount -a
    mkdir -p /dev/pts
    mount -t devpts devpts /dev/pts
    echo /sbin/mdev > /proc/sys/kernel/hotplug
    mdev –s

    同时使用 chmod +x rcS修改rcS的可执行权限.

  • 在_install /etc 目录创建文件名 fstab 的文件,并写入以下内容。

    1
    2
    3
    4
    5
    proc /proc proc defaults 0 0
    tmpfs /tmp tmpfs defaults 0 0
    sysfs /sys sysfs defaults 0 0
    tmpfs /dev tmpfs defaults 0 0
    debugfs /sys/kernel/debug debugfs defaults 0 0
  • 在_install /etc 目录创建文件名 inittab 的文件,并写入如下内容。

    1
    2
    3
    4
    ::sysinit:/etc/init.d/rcS
    ::respawn:-/bin/sh
    ::askfirst:-/bin/sh
    ::ctrlaltdel:/bin/umount -a –r
  • 在_install/dev 目录下创建如下设备节点,以root权限执行

    1
    2
    3
    cd _install/dev/
    sudo mknod console c 5 1
    sudo mknod null c 1 3

.sh(配合chmod +x使用)

  • build.sh: 编译busybox

    1
    2
    3
    4
    export ARCH=ARM
    export CROSS_COMPILE=arm-linux-gnueabi-
    make menuconfig
    make install
  • creat.sh: _install文件夹下处理

    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
    rm -rf etc
    rm -rf dev
    rm -rf mnt
    mkdir etc
    mkdir dev
    mkdir mnt
    mkdir -p etc/init.d/

    echo "mkdir -p /proc
    mkdir -p /tmp
    mkdir -p /sys
    mkdir -p /mnt
    /bin/mount -a
    mkdir -p /dev/pts
    mount -t devpts devpts /dev/pts
    echo /sbin/mdev > /proc/sys/kernel/hotplug
    mdev -s
    " > etc/init.d/rcS
    chmod +x etc/init.d/rcS

    echo "proc /proc proc defaults 0 0
    tmpfs /tmp tmpfs defaults 0 0
    sysfs /sys sysfs defaults 0 0
    tmpfs /dev tmpfs defaults 0 0
    debugfs /sys/kernel/debug debugfs defaults 0 0
    " > etc/fstab

    echo "::sysinit:/etc/init.d/rcS
    ::respawn:-/bin/sh
    ::askfirst:-/bin/sh
    ::ctrlaltdel:/bin/umount -a -r
    " > etc/inittab

    cd dev/
    sudo mknod console c 5 1
    sudo mknod null c 1 3

编译内核

  • 编译内核

    1
    2
    3
    4
    5
    cd linux-4.0
    export ARCH=arm
    export CROSS_COMPILE=arm-linux-gnueabi-
    make vexpress_defconfig
    make menuconfig
  • 配置 initramfs,在 initramfs source file 中填入_install。另外需要把 Default kernel command string 清空。

    1
    2
    3
    4
    5
    General setup --->
    [*] Initial RAM filesystem and RAM disk (initramfs/initrd) support
    (_install) Initramfs source file(s)
    Boot options -->
    ()Default kernel command string
  • 配置 memory split 为“3G/1G user/kernel split”以及打开高端内存。

    1
    2
    3
    Kernel Features --->
    Memory split (3G/1G user/kernel split) --->
    [ *] High Memory Support
  • 开始编译 kernel

    1
    2
    make bzImage -j4 ARCH=arm CROSS_COMPILE=arm-linux-gnueabi-
    make dtbs
  • 运行 QEMU 来模拟 4 核 Cortex-A9 的 Versatile Express 开发平台。

    1
    qemu-system-arm -M vexpress-a9 -smp 4 -m 1024M -kernel arch/arm/boot/zImage -append "rdinit=/linuxrc console=ttyAMA0 loglevel=8" -dtb arch/arm/boot/dts/vexpress-v2p-ca9.dtb -nographic

.sh(配合chmod +x)

  • build.sh : 编译内核

    1
    2
    3
    4
    5
    6
    export ARCH=arm
    export CROSS_COMPILE=arm-linux-gnueabi-
    make vexpress_defconfig
    make menuconfig
    make bzImage -j4 ARCH=arm CROSS_COMPILE=arm-linux-gnueabi-
    make dtbs
  • run.sh : 运行arm内核

    1
    qemu-system-arm -M vexpress-a9 -smp 4 -m 1024M -kernel arch/arm/boot/zImage -append "rdinit=/linuxrc console=ttyAMA0 loglevel=8" -dtb arch/arm/boot/dts/vexpress-v2p-ca9.dtb -nographicyun

  • wdt第二弹,内核API,勉强翻译下来了,还需要进一步整理

看门狗定时器驱动内核API

  • 最后更新时间 2013.2.12
  • Wim Van Sebroeck wim@iguana.be

介绍

  • 本文档没有描述看门狗设备或驱动。也不涉及那些在用户空间调用的API,对应在 Documentation/watchdog/watchdog-api.txt
  • 本文档描述的是,利用看门狗子系统框架方式进行看门狗驱动编写时所使用到的API。看门狗子系统框架提供了所有与用户空间交互的接口,不需要每次编写重复的代码。这意味这驱动程序只需要提供几个不同的操作函数,就可以控制WDT了。

API

  • 每一个想要使用看门狗核心子系统的驱动程序都必须包含#include<linux/watchdog.h>(编写驱动程序是不得不做的工作)。linux/watchdog.h包含以下两个注册/卸载函数:

  • ```shell
    extern int watchdog_register_device(struct watchdog_device *);
    extern void watchdog_unregister_device(struct watchdog_device *);

    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

    * 注册函数将wdt设备注册进wdt子系统,函数的参数是一个指向struct watchdog_device的结构体_。当注册成功时返回0.注册失败返回一个负值。
    * 卸载函数是将wdt设备从wdt子系统卸载,函数参数是一个指向struct watchdog_device的结构体_
    * 看门子系统包含了注册时间调整机制(原文是 an registration deferral mechanism 一种延期机制?上下文不太对啊),允许在开机阶段,你可以按照你设定的尽可早的注册一个看门狗设备。

    ### struct watchdog_device_

    * 这里粘贴的是linux4.1里定义的watchdog_device,原文的watchdog_device更长但跟源码对不起来,所以这里就以内核4.1里定义的为准了😑:

    ```c
    struct watchdog_device {
    int id;
    struct cdev cdev;
    struct device *dev;
    struct device *parent;
    const struct watchdog_info *info;
    const struct watchdog_ops *ops;
    unsigned int bootstatus;
    unsigned int timeout;
    unsigned int min_timeout;
    unsigned int max_timeout;
    void *driver_data;
    struct mutex lock;
    unsigned long status;
    /* Bit numbers for status flags */
    #define WDOG_ACTIVE 0 /* Is the watchdog running/active */
    #define WDOG_DEV_OPEN 1 /* Opened via /dev/watchdog ? */
    #define WDOG_ALLOW_RELEASE 2 /* Did we receive the magic char ? */
    #define WDOG_NO_WAY_OUT 3 /* Is 'nowayout' feature set ? */
    #define WDOG_UNREGISTERED 4 /* Has the device been unregistered */
    };
  • 包含以下字段:

    • id:由watchdog_register_device函数注册。id 0是一个特殊的,id 0 有/dev/watchdog0 cdev(动态主设备号,次设备号0) 和 /dev/watchdog miscdev。id 在调用到watchdog_register_device时自动注册。
      • NOTE看源码,wdt子系统是基于misc子系统的,注册wdt设备调用的是misc_register(&watchdog_miscdev);而misc设备主设备号只能为10,这里结论有冲突,等待解决中。。。
    • parent:在调用watchdog_register_device函数前,设置父设备(或者设置为null)
    • info:指向watchdog_info结构体的指针,watchdog_info提供了看门狗本身的一些附加信息(像是看门狗独有的名称之类的)
    • ops:指向watchdog_ops结构体的指针,watchdog_ops是看门狗支持操作(函数)的集合。
    • bootstatus:启动后设备状态(与看门狗WDIOF_* 标志位一同开启)_
    • timeout:看门狗超时的时间(秒为单位).如果设置了WDOG_ACTIVE_启用了看门狗,在这个时间长度后,用户空间还没有发送心跳包,看门狗会将系统复位重启.
    • min_timeout:_可设置的看门狗最小超时时间
    • max_timeout:_可设置的看门狗最大超时时间
    • driver_data:_指向看门狗设备私有数据的指针,这个data只能由watchdog_set_drvdata 和 watchdog_get_drvdata routines函数访问.
    • struct mutex lock; 原文档没有🙄
    • status:这个字段包含了许多状态位,提供有关设备的额外信息(例如:看门狗的活动状态\限制,现在nowayout 位设置与否)(括号里翻译是否准确存疑)

struct watchdog_ops

  • watchdog_ops_

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    struct watchdog_ops {
    struct module *owner;
    /* mandatory operations */
    int (*start)(struct watchdog_device *);
    int (*stop)(struct watchdog_device *);
    /* optional operations */
    int (*ping)(struct watchdog_device *);
    unsigned int (*status)(struct watchdog_device *);
    int (*set_timeout)(struct watchdog_device *, unsigned int);
    unsigned int (*get_timeleft)(struct watchdog_device *);
    void (*ref)(struct watchdog_device *);
    void (*unref)(struct watchdog_device *);
    long (*ioctl)(struct watchdog_device *, unsigned int, unsigned long);
    };
  • 你一开始定义的看门狗的module owner是非常重要的,module owner 在使用看门狗时会同时锁定看门狗设备.(这样避免了在卸载模块时/dev/watchdog依然是打开状态引起的系统崩溃)

  • 某些函数是必须实现的,其他是可选的,必须实现的函数如下:

    • *start :*指向看门狗设备启动函数的指针.这个函数参数为一个 struct watchdog_device,_成功返回0,失败返回负数.
    • *stop :*通过这个函数关闭看门狗设备.这个函数参数为一个 struct watchdog_device,_成功返回0,失败返回负数.
      • 一些硬件看门狗设备只能启动,没有关闭选项.对应这些设备的驱动,不用实现 *stop ,*如果驱动程序没有关闭设备功能,看门狗核心层会在设备关闭后设置 WDOG_HW_RUNNING 并调用驱动程序的 keepalive pings功能.
      • 如果看门狗驱动没有实现stop必须设置max_hw_heartbeat_ms_
  • 不是所有的硬件看门狗都有相同的功能,这就是为什么会有可选函数了,只有但这项功能被硬件看门🐶支持时,才编写对应函数.

    • ping:这是看门狗驱动实现的喂狗函数,这个函数的参数时一个指向struct watchdog_device的指针_.函数执行成功返回0,失败返回负数.
      • 大多数的硬件不支持直接喂狗的特殊功能,一般是直接重启硬件看门狗.
      • 看门狗子系统的核心层也是这样做的:但定期喂狗的操作转发到硬件看门狗时,核心层在 ping 实现时调用喂狗函数,但喂狗函数没有实现时,使用 start 对应函数
      • ( WDIOC_KEEPALIVE对应的 ioctl命令只有在i看门狗info里的WDIOF_KEEPALIVEPING被设置为1后才会生效 )
    • status: 这个函数用来检查看门狗设备的状态,通过看门狗的WDIOF_*_状态标志位报告.WDIOF_MAGICCLOSE and WDIOF_KEEPALIVEPING由看门狗核心层报告.没有必要在驱动程序中报告.此外如果驱动程序没有实现该函数,看门狗核心层会返回 struct watchdog_device_中的bootstatus状态位.
    • set_timeout:这个函数设置和检查 超时时间的变化,返回0表示成功,返回-EINVAL表示超出范围,返回-EIO表示无法写入看门狗.设置成功后,函数应该将设定的超时时间写入看门狗.(或许与通过接口获取的超时时间不同,因为看门狗的分辨率不一定能到1s).
      • 实现了max_hw_heartbeat_ms的驱动将硬件看门狗心跳值设置为超时时间和max_hw_heartbeat_ms之间的最小值.Those drivers set the timeout value of the watchdog_device either to the requested timeout value (if it is larger than max_hw_heartbeat_ms), or to the achieved timeout value.
      • 如果看门狗驱动程序没有执行任何操作,但设置了watchdog_device.timeout_,此回掉函数忽略.
      • 如果没有设置 set_timeout,但设置了WDIOF_SETTIMEOUT,看门狗架构会将 watchdog_device中timeout值更新为请求的值.
      • 如果使用了pretimeout feature (WDIOF_PRETIMEOUT),那么set_timeout 必须同时检查 pretimeout 是否有效 ,设置相应定时器. 这些操作在核心层没有races无法完成,所以必须在驱动中完成.
    • set_pretimeout_:这个函数支持检查/改变看门狗中pretimeout值(pretimeout详情见wdt用户层api翻译).这个函数是可选支持的,因为不是所有的看门狗都支持这个功能.pretimeout 不是绝对时间,其数值是在超时时间之前几秒发生.
      • 返回0表示执行成功,返回-EINVAL,表示超出范围,返回-EIO表示未能成功向看门狗写入.
      • 设置pretimeou为0值,代表禁用pretimeout功能.(WDIOF_PRETIMEOUT_需要在看门狗的info 结构体中设置)
      • 如果驱动没有实现任何功能,但设定了 watchdog_device.pretimeout_,此回掉函数忽略.这意味这,如果没有提供set_pretimeout_函数,但设定了WDIOF_PRETIMEOUT_,看门狗架构会将pretimeout的值更新为请求值.
    • get_timeleft:_这个函数返回复位前剩余时间.
    • restart:此函数重启看门狗硬件,返回0表示成功,失败返回对应错误码.
    • ioctl:如果这个函数存在,在系统进行内部ioctl命令处理前,会首先调用此函数.如果不支持ioctrl命令,应该返回 -ENOIOCTLCMD .这个函数的参数为(watchdog_device, cmd and arg)_
    • ref和unref 已经不再被支持.
  • The status bits should (preferably) be set with the set_bit and clear_bit alike bit-operations. The status bits that are defined are:

    • WDOG_ACTIVE:_这个状态位标识着从用户空间看一个看门狗设备是否被激活,如果被激活,用户空间需要执行喂狗动作

    • WDOG_NO_WAY_OUT:_这个状态位标识这nowayout的设置,如果被激活,那看门狗启动后,无法被停止.

    • WDOG_HW_RUNNING:如果硬件看门狗运行,此状态位被置1.

      • 当硬件看门狗不能被停止时,这个状态位必须被置1.
      • 如果系统启动后自动启动看门狗,在看门狗打开前,必须设置此状态位.
      • 当此状态位被置1,即使WDOG_ACTIVE为0_.看门狗架构也会执行喂狗动作.
      • (当你直接注册设置了 WDOG_HW_RUNNING位的看门狗设备时,执行open /dev/watchdog动作,系统会直接跳过启动操作,直接执行喂狗操作 )
    • 要设置WDOG_NO_WAY_OUT_状态位(在注册看门狗设备前)你可以:

      • 在watchdog_device_的结构体中设置

        1
        .status = WATCHDOG_NOWAYOUT_INIT_STATUS,

        这样等同于将WDOG_NO_WAY_OUT_值设置为CONFIG_WATCHDOG_NOWAYOUT

      • 使用下面的函数

        1
        static inline void watchdog_set_nowayout(struct watchdog_device *wdd, int nowayout)
  • (note:) wdt驱动程序核心支持magic close和 nowayout功能.要使用magic close,需要在看门狗的 info中设置WDIOF_MAGICCLOSE_位.开启 nowayout 会覆盖magic close.

  • 获取或设定驱动程序特殊数据,应该使用以下两个函数:

    1
    2
    static inline void watchdog_set_drvdata(struct watchdog_device *wdd, void *data)
    static inline void *watchdog_get_drvdata(struct watchdog_device *wdd)
    • watchdog_set_drvdata函数允许你添加向驱动程序添加特殊数据,此函数的参数为指向看门狗设备的指针,和指向需要添加数据的指针.
    • watchdog_get_drvdata函数运行你读取驱动程序中的特殊数据,此函数的参数为指向你想读取的看门狗设备的指针.
  • 初始化 timeout ,会用到下面的函数

    1
    extern int watchdog_init_timeout(struct watchdog_device *wdd,unsigned int timeout_parm, struct device *dev);
    • watchdog_init_timeout允许使用模块的 timeout 字段初始化 timeout ,当模块的 timeout 字段无效时,设备树中的timeout-sec也可.最好的做法是将watchdog_device_中timeout的值,设置为初始默认值,然后使用到函数的用户可以分别设置自定义值.
  • 重启后禁用看门狗,需要使用以下函数:

    1
    static inline void watchdog_stop_on_reboot(struct watchdog_device *wdd);
  • 卸载看门狗设备时禁用看门狗,必须调用此函数,如果 nowayout 没有设置,这个函数只会停止看门狗.

    1
    static inline void watchdog_stop_on_unregister(struct watchdog_device *wdd);
  • 更改重启处理程序的优先级,(猜测是不同看门狗的优先级)调用下面的函数:

    1
    oid watchdog_set_restart_priority(struct watchdog_device *wdd, int priority);
    • 设定优先级时,用户需要遵循以下规则
      • 0:优先级最低,非常有限的重启能力
      • 128:重启处理的默认选择,如果没有其他的重启处理程序可用,或者没有重启整个系统的重启处理程序可用.
      • 255:最高优先级,会覆盖其他所有重启处理程序
  • 使用pretimeout 通知功能,需要利用以下函数:

    1
    void watchdog_notify_pretimeout(struct watchdog_device *wdd)
    • 这个函数可以在中断上下文中调用,如果启用了watchdog pretimeout governor 框架(kbuild CONFIG_WATCHDOG_PRETIMEOUT_GOV),就会采用进入驱动程序分配好的处理程序,如果没有启用watchdog pretimeout governor 框架,watchdog_notify_pretimeout()会将信息输出到内核日志缓存.


  • 随手翻译的文档,要看懂linux内核,内核附带的文档自然是逃不过😂,全英文配合Google翻译也得上。。。。

  • 最后更新时间 10/05/2007

  • linux看门狗设备API

  • Copyright 2002 Christer Weingel wingel@nano-system.com

  • 本文档的一些内容来自 sbc60xxwdt ,版权属于 Copyright 2000 Jakob Oestergaard jakob@ostenfeld.dk

  • 本文档基于linux内核 2.4.18


介绍

  • 你应该已经知道了,看门狗定时器(WDT)是一个硬件复位电路,在系统软件出现故障时复位系统。
  • 通常,用户空间的定期喂狗程序通过/dev/watchdog的设备文件通知内核看门狗驱动程序。驱动收到通知,会告知硬件看门狗一切正常,硬件看门狗需要等一会再复位系统。如果用户空间通知失败(RAM错误、内核漏洞等等),通知将停止。超时后,硬件看门狗将复位系统(重启)。
  • linux看门狗的API是相当特殊的结构,不同的驱动程序对API的执行各不相同,有时会碰到不兼容的情况。本文档参数记录现有的情况,以供未来的驱动开发参考。

最简单的API

  • 所有驱动都支持的基本操作模式。/dev/watchdog被打开后,看门狗被激活并复位系统,除非在一小段时间内被ping通?(喂狗),这段时间被称为超时时间或间断时间。简单的喂狗的方式是向驱动程序写入一些数据,一个非常简单的实例在see samples/watchdog/watchdog-simple.c下.
  • 更高级的驱动程序可以做到例如检查一个http服务器,在执行喂狗操作之前,做出回应。
  • /dev/下设备节点被关闭后,看门狗也同时被禁用,除非支持“Magic Close”(见下文),这并不是个好办法。因为如果看看门狗demo有bug导致系统崩溃,但系统不会重启。因为这样一些驱动支持”Disable watchdog shutdown on close”, CONFIG_WATCHDOG_NOWAYOUT”这样的配置选项。一旦编译内核时候启用这些选项,一旦看门狗程序激活,就不可能禁用看门狗。如果看门狗demo崩溃,系统会在超时后自动重启。看门狗驱动通常也支持nowayout module parameter,以便在运行时控制nowayout module。

Magic Close feature

  • 如果驱动程序支持Magic Close,那么设备将不支持禁用看门狗,除非在关闭设备文件前,向/dev/watchdog下设备文件写入特定的magic 字符 ‘V’。如果用户空间的 daemon关闭了看门的设备文件,但是没有发送那个特定的字符,驱动会认为daemon进程已经被杀,并且停止了定期喂狗行为。这样会导致超时后系统重启。

ioctl API

  • 所有的驱动程序都支持ioctl API

  • 喂狗操作使用ioctl完成

  • 所有的驱动都要支持一个KEEPALIVE的ioctl命令,这个命令可以起到向驱动写入数据一样的作用。所以watchdog daemon的主函数循环可以这样实现

    1
    2
    3
    4
    while (1) {
    ioctl(fd, WDIOC_KEEPALIVE, 0);
    sleep(10);
    }

Setting and getting the timeout

  • 某些驱动支持运行时通过SETTIMEOUT ioctl命令修改超时时间,这些驱动都有WDIOF_SETTIMEOUT的标志位_。超时时间是一个单位为秒的整数,驱动程序会返回这个变量实际设置的数值,但是由于硬件限制,返回的数值可能与设置数值不同。

    1
    2
    3
    int timeout = 45;
    ioctl(fd, WDIOC_SETTIMEOUT, &timeout);
    printf("The timeout was set to %d seconds\n", timeout);
  • 这个实例或许会输出”The timeout was set to 60 seconds”,如果设备超时时间为分钟。

  • 自从2.4.18内核开始,可以使用GETTIMEOUT ioctl命令,获取超时时间。

    1
    2
    ioctl(fd, WDIOC_GETTIMEOUT, &timeout);
    printf("The timeout was is %d seconds\n", timeout);

Pretimeouts

  • 一些看门狗定时器可以在系统重启前一段时间设置一个触发器。这样运行linux在系统从前记录一些重要的信息(像panic信息和kernel coredumps),具体可以通过NMT、中断等机制实现。

    1
    2
    pretimeout = 10;
    ioctl(fd, WDIOC_SETPRETIMEOUT, &pretimeout);
  • 请注意,pretimeout 时间是在 超时关闭系统 之前的秒数。不是直到发生pretimeout 事件的秒数。例如设定的超时时间是60s, pretimeout是10s。pretimeout事件会在50s时发生。pretimeout设置为0代表禁用pretimeout。

  • 同样存在一个获取pretimeout的ioctl命令。

    1
    2
    ioctl(fd, WDIOC_GETPRETIMEOUT, &timeout);
    printf("The pretimeout was is %d seconds\n", timeout);
  • 不是所有看门狗驱动都支持 pretimeout

获取重启前秒数

  • 一些看门狗驱动支持获取系统重启前秒数。通过WDIOC_GETTIMELEFT ioctl_命令可以返回系统重启前秒数。

    1
    2
    ioctl(fd, WDIOC_GETTIMELEFT, &timeleft);
    printf("The timeout was is %d seconds\n", timeleft);

环境监测

  • 所有的看门狗驱动都被要求返回更多关于系统最后一次重启的信息,像是温度、风扇转速、电源等。GETSUPPORT ioctl 的命令可以返回 设备可以支持的信息。

    1
    2
    struct watchdog_info ident;
    ioctl(fd, WDIOC_GETSUPPORT, &ident);
  • 返回的结构ident中的字段如下:

    1
    2
    3
    4
    5
    identity  描述watchdog driver的字符串

    firmware_version the firmware version of the card if available
    options 设备支持情况的标志位

  • options描述了GET_STATUS 和GET_BOOT_STATUS ioctl命令_可以返回那些信息。并且可以设置。()**??无法理解什么意思??**

  • WDIOF_OVERHEAT cpu过热复位_
    系统重启因为,超过了温度限制上限。

  • WDIOF_FANFAULT 风扇失效复位_

  • WDIOF_EXTERN1 External relay 1_
    外部监控电源1被触发,???Controllers intended for real world applications include external monitoring pins that will trigger a reset.??(不明白什么意思)

  • WDIOF_EXTERN2 External relay 2
    外部监控电源2被触发

  • WDIOF_POWERUNDER 电源坏了,电源带不动了
    机器显示欠压。


  • 后面一点不翻了,有时间再添坑

  • WDIOF_CARDRESET Card previously reset the CPU
    The last reboot was caused by the watchdog card

  • WDIOF_POWEROVER Power over voltage
    The machine is showing an overvoltage status. Note that if one level is under and one over both bits will be set - this may seem odd but makes sense.

  • WDIOF_KEEPALIVEPING Keep alive ping reply
    The watchdog saw a keepalive ping since it was last queried.

  • WDIOF_SETTIMEOUT Can set/get the timeout
    The watchdog can do pretimeouts.

  • WDIOF_PRETIMEOUT Pretimeout (in seconds), get/set
    For those drivers that return any bits set in the option field, the GETSTATUS and GETBOOTSTATUS ioctls can be used to ask for the current status, and the status at the last reboot, respectively.

    1
    2
    3

    int flags;
    ioctl(fd, WDIOC_GETSTATUS, &flags);

    or

    1
    ioctl(fd, WDIOC_GETBOOTSTATUS, &flags);
  • Note that not all devices support these two calls, and some only support the GETBOOTSTATUS call.

  • Some drivers can measure the temperature using the GETTEMP ioctl. The returned value is the temperature in degrees fahrenheit.

    1
    2
    int temperature;
    ioctl(fd, WDIOC_GETTEMP, &temperature);
  • Finally the SETOPTIONS ioctl can be used to control some aspects of the cards operation.

    1
    2
    int options = 0;
    ioctl(fd, WDIOC_SETOPTIONS, &options);
  • The following options are available:

    • WDIOS_DISABLECARD Turn off the watchdog timer
    • WDIOS_ENABLECARD Turn on the watchdog timer
    • WDIOS_TEMPPANIC Kernel panic on temperature trip

  • 转载自”Blog of UnicornX” (http://unicornx.github.io/)

  • 面向对象的思想在c语言中的应用,第一次看到才知道c语言还能这么用。。。


前言

  • 语言只是工具,重要的是思想。但是!!C语言用面向对象??看了内核代码,真给那些写Linux、内核的大神跪了。。。
  • 当然因为C语言的局限性,并非完全面向对象的实现,私有成员等就没有了。

封装

  • 既类在C语言中的实现,实际上是 struct + 函数体 = 函数表
    结构体里不允许存在成员函数,但是换成指向函数的指针是可以滴。

  • 示例

    1
    2
    3
    4
    5
    struct foo_operations {
    void (*op_a) (struct foo *, loff_t, int);
    void (*op_b) (struct foo *, char __user *, size_t, loff_t *);
    void (*op_c) (struct foo *, const char __user *, size_t, loff_t *);
    };

继承

  • 这个简单点, struct 里面套 struct ,警告熟悉c语言的应该都清除。。。这里没有父类子类的严格区分,实际上是完全自由的。

  • 示例

    1
    2
    3
    4
    5
    6
    struct foo {
    int a;
    int b;
    int c;
    struct foo_operations ops;
    };

多态

  • 这个是最有意思的,c语言中严格规定同一个.c文件/工程中不能存在重名函数,但是c语言的精华-指针给了思路,借助结构体,我函数名不变,每指向不同的结构体变量就行了。。。虽然还是很麻烦。。

  • 示例

    1
    2
    3
    4
    5
    6
    struct foo a;
    a->ops = ops1;
    a->ops->op_a(a);
    //切换
    a->ops = ops2;
    a->ops->op_a(a);

链表

  • 内核内大量运用链表,开始不清楚真是头晕。。

  • 示例

    1
    2
    3
    struct list_head {
    struct list_head *next, *prev;
    };

    一个指向自身的结构体,包含下一个与自身的指针。
    使用

    1
    2
    3
    4
    struct A_LIST {
    data_t data;
    struct list_head *list;
    };
  • offsetof宏

    1
    #define offsetof(type, member) (size_t)&(((type*)0)->member)

    offsetof是用来判断结构体中成员的偏移位置

  • container_of_宏

    1
    2
    3
    #define container_of(ptr, type, member) ({ \
    const typeof( ((type *)0)->member ) *__mptr = (ptr); \
    (type *)( (char *)__mptr - offsetof(type,member) );})

    根据一个结构体变量中的一个域成员变量的指针来获取指向整个结构体变量的指针..链表元素常用


资料来源如下

  • 乐联网

编程环境

  • Android Studio 2.2.3

最终效果

  • 使用okhttp上传数据。
  • Tcp长连接实现方向控制
  • 以代码为主

相关教程

  • okhttp入门

    http://blog.csdn.net/biezhihua/article/details/50603624

  • 乐联网

    https://www.lewei50.com/dev/doc/176
    https://www.lewei50.com/dev/doc/155

  • Tcp长连接

    http://ls15114843569.blog.51cto.com/11015399/1767195

  • 简易上传

    http://ls15114843569.blog.51cto.com/11015399/1767195

上传数据

  • API介绍

    https://www.lewei50.com/dev/apiinfo/3

  • API测试

    https://www.lewei50.com/dev/apitest/3

  • 地址:http://www.lewei50.com/api/v1/gateway/updatesensors/你的网关号

    POST方式

    需要配置header头 Userkey

  • 数据发送/返回方式JSON

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    [
    {
    "Name":"T1",
    "Value":"1"
    },
    {
    "Name":"01H1",
    "Value":"96.2"
    }
    ]

    返回格式

    1
    2
    3
    4
    {
    "Successful": true,
    "Message": null
    }
  • okhttp POST 传感器数据 这里使用了一个静态内部类。

    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
    42
    43
    44
    45
    46
    47
    48
    49
    	//返回数据处理
    public okhttp3.Callback callback = new okhttp3.Callback() {
    @Override
    public void onResponse(Call call, Response response) throws IOException {
    //返回服务器内容
    String responsedata = response.body().string();
    LogUtil.d("okHttp", responsedata);
    }

    @Override
    public void onFailure(Call call, IOException e) {
    //异常处理
    LogUtil.d("okHttp", "POST错误");
    }
    };

    //内部类
    public static class Http {
    public static final MediaType MEDIA_TYPE_MARKDOWN
    = MediaType.parse("text/x-markdown; charset=utf-8");
    //POST数据,指定接收回掉
    public static void postData(String sensor_name, String sensor_data, okhttp3.Callback callback) {
    OkHttpClient client = new OkHttpClient();
    final String value =
    "[" +
    " {" +
    " \"Name\":\"" + sensor_name + "\"," +
    " \"Value\":\"" + sensor_data + "\"" +
    " }" +
    "]";
    RequestBody requestBody = new RequestBody() {
    @Override
    public MediaType contentType() {
    return MEDIA_TYPE_MARKDOWN;
    }

    @Override
    public void writeTo(BufferedSink sink) throws IOException {
    sink.write(value.getBytes());
    }
    };
    Request request = new Request.Builder()
    .url("http://www.lewei50.com/api/V1/gateway/UpdateSensors/01")
    .header("userkey", "你的userkey")
    .post(requestBody)
    .build();
    client.newCall(request).enqueue(callback);
    }
    }
  • 实际使用 放在一个后台服务内,调用相当简单

    1
    Http.postData("PM2.5", "你的数据转为String", callback);

    Tcp长连接,远程控制

  • 首先参考乐联网反向控制教程,新建一个控制器,这里以开关为例。

  • 原理是与服务器保持一个TCP长连接,不到40s刷新一次,以此保持通道,与被控制段通信,发送控制信息。

  • Tcp长连接参考了@墨迹流韶的Android基于Tcp协议的Socket长链接封装

  • 地址 tcp.lewei50.com
    端口号 9960
    心跳包间隔 1min以内

  • 发送/接收数据格式 Json
    本地发送数据格式

    1
    2
    3
    4
    5
    6
    7
    8
    9
    {

    "method": "update",

    "gatewayNo": "你的网关号",

    "userkey": "你的userkey"

    }&^!

    服务器发送控制命令格式,数据处理时需要去掉字符串最后的&^!

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16

    {

    "method":"send",

    "gatewayNo":"01",

    "userkey":"6d16ddb3c58c4e448a7e15e7acxxxxxx",

    "f":" updateSensor",

    "p1":"D1",

    "p2":"1"

    }&^!

    本地响应控制命令后返回数据格式

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    {

    "method":"response",

    "result":{

    "successful":true,

    "message":"ok!",

    "data":[{

    "id":"D1",

    "value":"1"

    },

    }&^!
  • TCP连接类

    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
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    164
    165
    166
    public class TcpSocketHelper {
    private String mTag = "TcpSocketHelper";
    private Socket socket;
    private boolean _connect;
    private ReceiveThread mReceiveThread;
    private boolean receiveStop;
    private Date lastKeepAliveOkTime;
    private OnRecivedListener mRecivedListener;
    //地址
    private String mIpAddr = "http://tcp.lewei50.com";
    //端口
    private int mPort = 9960;

    /**
    * 开启链接socket
    * @param ipAddr
    * @param port
    */
    public void startConnect(String ipAddr, int port){
    LogUtil.d(mTag, "准备链接...");
    this.mIpAddr = ipAddr;
    this.mPort = port;
    InetAddress serverAddr;
    try {
    socket = new Socket(ipAddr, port);
    LogUtil.d(mTag, "准备链接...");
    _connect = true;
    mReceiveThread = new ReceiveThread();
    receiveStop = false;
    mReceiveThread.start();
    LogUtil.d(mTag, "链接成功...");

    } catch (Exception e) {
    LogUtil.d(mTag, "链接出错..." + e.getMessage());
    e.printStackTrace();
    }
    }

    /**
    * 关闭链接
    */
    public void closeConnect(){
    if (socket != null){
    try {
    socket.close();
    socket = null;
    } catch (IOException e) {
    e.printStackTrace();
    }
    }
    }

    /**
    * 保持心跳
    */
    public void KeepAlive() {
    // 判断socket是否已断开,断开就重连
    if (lastKeepAliveOkTime != null) {
    LogUtil.d(mTag, "上次心跳成功时间:"+ DateFormat.format("yyyy-MM-dd HH:mm:ss", lastKeepAliveOkTime));
    Date now = Calendar.getInstance().getTime();
    long between = (now.getTime() - lastKeepAliveOkTime.getTime());// 得到两者的毫秒数
    if (between > 60 * 1000) {
    LogUtil.d(mTag, "心跳异常超过40,重新连接:");
    lastKeepAliveOkTime = null;
    socket = null;
    }

    } else {
    lastKeepAliveOkTime = Calendar.getInstance().getTime();
    }

    if (!checkIsAlive()) {
    LogUtil.d(mTag, "链接已断开,重新连接.");
    startConnect(mIpAddr, mPort);
    }
    }

    /**
    * 此方法是检测是否连接
    * @return
    */
    public boolean checkIsAlive() {
    if (socket == null||!socket.isConnected())
    return false;
    return true;

    }

    /**
    * 发送数据的方法
    * @param msg
    */
    public void sendmessage(String msg) {
    boolean isAlive = checkIsAlive();
    if (!isAlive)
    return;
    LogUtil.d(mTag, "准备发送消息:" + msg);
    try {
    if (socket != null && socket.isConnected()) {
    if (!socket.isOutputShutdown()) {

    //2.得到socket读写流
    OutputStream os=socket.getOutputStream();
    //true:是否自动flush
    PrintWriter outStream=new PrintWriter(os, true);
    outStream.print(msg);
    outStream.flush();
    }
    }
    LogUtil.d(mTag, "发送成功!");
    } catch (Exception e) {
    e.printStackTrace();
    }
    }

    /**
    * 设置接收数据监听器
    * @param mRecivedListener
    */
    public void setmRecivedListener(OnRecivedListener mRecivedListener) {
    this.mRecivedListener = mRecivedListener;
    }

    /**
    * 数据接收线程
    */
    class ReceiveThread extends Thread{

    @Override
    public void run() {
    while (true) {
    try {
    sleep(2000);
    // 判断 Socket 是否处于连接状态
    if (socket != null && socket.isConnected()) {
    // 客户端接收服务器端的响应,读取服务器端向客户端的输入流
    InputStream isRead = socket.getInputStream();
    // 缓冲区
    byte[] buffer = new byte[isRead.available()];
    // 读取缓冲区
    isRead.read(buffer);
    // 转换为字符串
    String responseInfo = new String(buffer);
    // 日志中输出
    if(responseInfo != null&&!responseInfo.equals("")){
    LogUtil.d("TcpManager", "返回:"+responseInfo);
    mRecivedListener.onRecived(responseInfo);
    }

    lastKeepAliveOkTime = Calendar.getInstance().getTime();
    KeepAlive();

    continue;
    } else {
    if (socket != null)
    LogUtil.d(mTag, "链接状态:" + socket.isConnected());
    }

    } catch (Exception e) {
    LogUtil.d(mTag, "监听出错:" + e.toString());
    e.printStackTrace();
    }
    }
    }
    }
    }
  • 使用,包装在一个后台service中,在service中实现TcpSocketHelper的onRecived方法

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
      //tcp返回信息
    @Override
    public void onRecived(String data) {
    LogUtil.d("okHttpService", data);
    //处理服务器发回的数据
    }

    TcpSocketHelper tcpSocketHelper = new TcpSocketHelper();
    tcpSocketHelper.startConnect("tcp.lewei50.com", 9960);
    //设置监听
    tcpSocketHelper.setmRecivedListener(this);
  • 发送心跳包

    1
    2
    3
    4
    5
    6
    7
    8
    String value =
    " {" +
    " \"method\":\"update\"," +
    " \"gatewayNo\":\"01\"," +
    " \"userkey\":\"你的userkey\"" +
    " }&^!";
    //发送数据
    tcpSocketHelper.sendmessage(value);
  • 处理数据 在service的onRecived中

  • 本地处理完毕后,向服务器返回被控制器状态

    1
    2
    3
    String value5 = "{\"method\":\"response\",\"result\":{\"successful\":true,\"message\":\"ok!\",\"data\":[{\"id\":\"D1\",\"value \":\"1\"}]}}&^! ";

    tcpSocketHelper.sendmessage(value6);


  • 资料来源如下
  • 第一行代码(第二版)

Activity基础

Activity定义

  • Activity 是Android四大组件之一,用户可与其提供的屏幕进行交互,以执行操作。 每个 Activity 都会获得一个用于绘制其用户界面的窗口。窗口可以充满屏幕,也可浮动。应用通常由多个彼此联系的 Activity 组成。应用中的某个 Activity 为“主”Activity,即首次启动应用时的Activity。

创建Activity

  • 新建FirstAtivity继承自AppCompatActivity

    1
    2
    3
    4
    5
    public class FirstActivity extends AppCompatActivity{
    @Override
    protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    } }
  • 创建加载布局
    1.jpg

  • 切换到first-layout

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
      <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <Button
    android:id="@+id/button_1"
    android:text="button_1"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content" />
    </LinearLayout>
  • 在FirestActivity中加载layout
    在onCreate中加入

    1
    setContentView(R.layout.first_layout);
  • 在AndroidMainfest文件中注册。

    1
    2
    3
    4
    5
    6
    7
    8
    <activity
    android:name=".FirstActivity"
    android:label="This is FirstActivity">
    <intent-filter>
    <action android:name="android.intent.action.MAIN"/>
    <category android:name="android.intent.category.LAUNCHER"/>
    </intent-filter>
    </activity>
  • 在模拟器中启动apk
    5ea429.jpg

使用Toast

Toast google官方说明

  • 推送一个短小信息推送给用户
    如图(摘自android developer)
    toast.png

  • 使用方法

    1
    2
    Toast.makeText(context, text, duration).show();

    举例

    1
    2
    Toast.makeText(FirstActivity.this, "you clicked button 1", Toast.LENGTH_SHORT).show();
    /* activity.this 消息内容 显示时间设置 */

使用Menu

  • 创建菜单

    • 在res下新疆menu文件夹,右击menu文件夹—new—Menu resource file,创建main的菜单文件。
    • main.xml中添加如下代码
      1
      2
      3
      4
      5
      6
      7
      8
      9
       <?xml version="1.0" encoding="utf-8"?>
      <menu xmlns:android="http://schemas.android.com/apk/res/android">
      <item
      android:id="@+id/add_item"
      android:title="Add"/>
      <item
      android:id="@+id/remove_item"
      android:title="Remove"/>
      </menu>
      这里创建了两个菜单项 Add和Remove
    • 在FirestActivity中重写 onCreateOptionsMenu()方法(快捷键 Ctrl+O)
      1
      2
      3
      4
      5
        @Override
      public boolean onCreateOptionsMenu(Menu menu) {
      getMenuInflater().inflate(R.menu.main, menu);
      return true;
      }
      getMenuInflater()可以得到MenuInflater对象,再调用.inflate就可以创建菜单。.inflat接受俩个参数,一是资源文件名,二是菜单项添加至那个对象中。onCreateOptionsMenu方法中返回true表示创建菜单并显示。
    • 效果如下。
      Screenshot_1481113497.png
  • 创建菜单点击响应事件

    • 重写onOptionsItemSelected方法
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      @Override
      public boolean onOptionsItemSelected(MenuItem item) {
      switch (item.getItemId()) {
      case R.id.add_item:
      Toast.makeText(this, "Add", Toast.LENGTH_SHORT).show();
      break;
      case R.id.remove_item:
      Toast.makeText(this, "Remove", Toast.LENGTH_SHORT).show();
      break;
      default:
      }
      return true;
      }
      通过item.getItemId()判断点击选项,弹出不同的Toast

向一个activity 并传递字符串

  • 构建一个Intent
    Intent intent = new Intent(this, DisplayMessageActivity.class);
    构造方法有两个参数:
    Context 是第一个参数,这里使用了this是因为Activity是Context的子类。
    Class 类是系统将要分发的APP组件,在这里,这个Activity将会被启动。
  • ```java
    public void sendMessage(View view) {
      // 创建一个新的intent对象,绑定DisplayMessageActivity
      Intent intent = new Intent(this, DisplayMessageActivity.class);
      //创建一个editText对象,绑定xml中editText
      EditText editText = (EditText) findViewById(R.id.edit_message);
      //获取editText中输入文字,转成字符串
      String message = editText.getText().toString();
      //一个Intent对象可以携带被称为extras的键值对。
      // putExtra()方法将键放在第一个参数中,将值放在第二个参数中。
      intent.putExtra(EXTRA_MESSAGE, message);
      //启动intent对应Activity
      startActivity(intent);
      }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22

    * ```java
    protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_display_message);

    //创建本地intent
    Intent intent = getIntent();
    //取出String类型数据,如果是Int类型就是getIntExtra,Boolean类型就是getBooleanExtra
    String message = intent.getStringExtra(MainActivity.EXTRA_MESSAGE);
    //显示String
    //创建新的TextView
    TextView textView = new TextView(this);
    //设置文本大小
    textView.setTextSize(40);
    //设置显示内容
    textView.setText(message);
    //绑定xml
    ViewGroup layout = (ViewGroup) findViewById(R.id.activity_display_message);
    //ViewGroup中添加TextView
    layout.addView(textView);
    }

返回数据给上一个Activity

  • 构建Intent使用startActivityForResult()方法传入请求码,启动下一个Activity
    在下一个Activity中构建Intent,intent.putExtra存入键值-key,调用setResult()方法,传入finish()结束掉Activity
    重写第一个Activity中的onActivityResult()方法,调用.getStringExtra取出key对应键值。

  • startActivityForResult(Intent, int Bundle) Intent与单纯启动Activity的Intent相同,第二个是请求码,下一级 回调提供相同的请求码,以便您应用可以正确识别结果。

    1
    2
    Intent intent = new Intent(FirestActivity.this,ScendActivity.class);
    startActivityForResult(intent,1);
  • setResult()方法,第一个参数向上级方法处理结果,一般使用RESULT_OK或RESULT_CANCELED,第二个参数 对应Intent

    1
    2
    3
    4
    5
    6
    7
    //新建显示Intent
    Intent intent = new Intent();
    //存入key-键值
    intent.putExtra("data_return","Hello Firest");
    setResult(RESULT_OK,intent);
    //结束Activity
    finish();
  • onActivityResult()三个参数.第一个startActivityForResult() 传递的请求代码。第二个 Activity 指定的结果代码。成功是 RESULT_OK;失败,则是 RESULT_CANCELED。第三个是传送结果数据的 Intent。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
     @Override
    protected void onActivityResult(int requestCode,int resultCode,Intent data){
    //选择不同请求码对应处理逻辑
    switch(requestCode){
    case 1:
    //处理结果时候ok
    if(resultCode == RESULT_OK){
    //取出数据
    String returnedData = data.getStringExtra("data_return");
    Log.d("FirstActivity", returnedData);
    }
    }
    }

Activity生命周期

basic-lifecycle.png

  • Activity 3种状态
    • Resumed/继续
      Activity 处于前台,且用户可以与其交互
    • Paused/暂停
      Activity 被在前台中处于另一个 Activity—部分阻挡。 暂停的 Activity 不会接收用户输入并且无法执行任何代码。
    • Stopped/停止
      Activity完全隐藏,对用户完全不可见.当停止时,activity的所有状态信息比如成员变量都会被保留,但是不能再执行任何代码

启动一个Activity

  • onCreate方法
  • 主Activity:
    • 用户点击app图标,启动主Activity
    • 在AndroidManifest.xml中声明
      1
      2
      3
      4
      <intent-filter>
      <action android:name="android.intent.action.MAIN" />
      <category android:name="android.intent.category.LAUNCHER" />
      </intent-filter>
  • 通过调用onCreate方法创建一个Activity实例,onCreate方法在Activity的整个生命周期中只执行一次。
  • 之后系统会很快的调用onStart和onResume方法,Activity进入Resumed/继续模式。直到被其他activity覆盖/屏幕关闭。

    销毁Activity

  • onDestory方法
    大多数的APP不需要实现这个方法,因为本地类引用会随着Activity一起总结,不过Activity的清理工作应该放在onPause下或者onStop。
  • Note:
    在所有的情况下系统调用onDestory方法之后已经调用过onPause方法与onStop方法,不过有一个例外情况:你在onCreate方法中调用了finish方法。在一些例子中,当你的Activity临时决定要启动另一个Activity,你可能要在onCreate方法内调用finish方法来销毁这个Activity,在这种情况下,系统会立即调用onDestory方法,而不会调用其它任何生命周期方法。

暂停Activity

  • onPause()方法
  • Activity被其他Activity覆盖/失去用户焦点,系统调用onPause()方法,Activity 进入暂停状态。
    • note:android7.0及以上版本加入了多窗口模式,当Activity失去用户焦点时,可能处于多窗口模式。
  • onPause() 常用回调:
    • 检查 Activity 是否可见。不可见则停止可能消耗 CPU 的操作
    • 提交未保存的更改,仅保存用户离开时希望永久性保存此类更改(比如电子邮件草稿)。
    • 释放系统资源,GPS/Camer等
    • 示例 (释放Camer)
      1
      2
      3
      4
      5
      6
      7
      8
      9
      public void onPause() {
      super.onPause(); // Always call the superclass method first

      // Release the Camera because we don't need it when paused
      // and other activities might need to use it.
      if (mCamera != null) {
      mCamera.release();
      mCamera = null;
      }
    • 注意事项:
      • 在onPause()一般不执行永久性存储用户更改,不执行 CPU 密集型工作,这些工作一般放在onStop() 。

继续 Activity

  • onResume() 方法
  • 暂停状态回到继续状态,Activity第一次启动时也会调用这个方法。
  • onResume() 以初始化在 onPause() 期间释放的组件。
  • 示例(重新获取Camera)
    1
    2
    3
    4
    5
    6
    7
    public void onResume() {
    super.onResume(); // Always call the superclass method first

    // Get the Camera instance as the activity achieves full user focus
    if (mCamera == null) {
    initializeCamera(); // Local method to handle camera init
    }

停止 Activity

  • note:
    大多数相对简单的 Activity 而言,系统在 Activity 停止时会将Activity 实例保留在系统内存中,无需实现 onStop() 和 onRestart() 或甚至onStart() 方法。可能只需使用 onPause() 暂停正在进行的操作,并从系统资源断开连接。
  • onStop() 方法

  • 场景:

    • 用户在最近应用切换到另一个应用
    • 应用中执行开始新 Activity 的操作
    • Activity使用时,接打电话
  • 调用时,Activity不再可见,释放几乎所有用户不使用时不需要的资源。如果系统内存紧张,则可能销毁内存中的Acitivity实例。

  • onStop() 方法调用后,Activity不再可见,极端情况下,系统可能会仅终止应用进程,而不调用 onDestroy() ,因此需要使用 onStop() 释放几乎所有用户不使用时不需要的资源。

  • 尽管 onPause() 方法在 onStop()之前调用,在onStop() 执行更大、占用更多 CPU 的关闭操作,比如向数据库写入信息

  • 示例(草稿笔记内容保存在永久存储)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
      protected void onStop() {
    super.onStop(); // Always call the superclass method first

    // Save the note's current draft, because the activity is stopping
    // and we want to be sure the current note progress isn't lost.
    ContentValues values = new ContentValues();
    values.put(NotePad.Notes.COLUMN_NAME_NOTE, getCurrentNoteText());
    values.put(NotePad.Notes.COLUMN_NAME_TITLE, getCurrentNoteTitle());

    getContentResolver().update(
    mUri, // The URI for the note to update.
    values, // The map of column names and new values to apply to them.
    null, // No SELECT criteria are used.
    null // No WHERE columns are used.
    );
    }

启动/重启 Activity

  • onStart() 方法
  • Activity 停止转换为继续状态时,系统回调onRestart() 方法+ onStart() 方法.onStop() 方法清理了所有 Activity 的资源,重启 Activity 需要重新实例化它们。同时 Activity 初次创建时重新实例化它们。 出于此,经常使用 onStart() 方法作为 onStop() 方法的对应
  • 示例
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
     @Override
    protected void onStart() {
    super.onStart(); // Always call the superclass method first

    // The activity is either being restarted or started for the first time
    // so this is where we should make sure that GPS is enabled
    LocationManager locationManager =
    (LocationManager) getSystemService(Context.LOCATION_SERVICE);
    boolean gpsEnabled = locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER);

    if (!gpsEnabled) {
    // Create a dialog here that requests the user to enable GPS, and use an intent
    // with the android.provider.Settings.ACTION_LOCATION_SOURCE_SETTINGS action
    // to take the user to the Settings screen to enable GPS when they click "OK"
    }
    }

    @Override
    protected void onRestart() {
    super.onRestart(); // Always call the superclass method first

    // Activity being restarted from stopped state
    }

    保存 Activity 状态

  • onSaveInstanceState()方法
  • 默认情况下,Activity 实例被销毁时系统会使用 Bundle 实例状态保存 Activity 布局中有关每个 View 对象的信息。在Activity 重建时,布局状态便自动恢复先前的状态。
  • 默认实现保存有关 Activity 视图层次的状态信息,例如 EditText 小部件中的文本或ListView 的滚动位置
  • 要恢复的更多信息,需要重写 onSaveInstanceState()方法,将键值对添加至 Bundle 对象
  • basic-lifecycle-savestate.png
  • note:
    旋转屏幕时,Activity 将被销毁并重新创建。原因:方向更改时可能需要时加载备用资源(比如布局)
  • 示例
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    static final String STATE_SCORE = "playerScore";
    static final String STATE_LEVEL = "playerLevel";
    ...

    @Override
    public void onSaveInstanceState(Bundle savedInstanceState) {
    // Save the user's current game state
    savedInstanceState.putInt(STATE_SCORE, mCurrentScore);
    savedInstanceState.putInt(STATE_LEVEL, mCurrentLevel);

    // Always call the superclass so it can save the view hierarchy state
    super.onSaveInstanceState(savedInstanceState);
    }

恢复 Activity

  • onCreate() 和 onRestoreInstanceState() 回调方法均接收包含实例状态信息的相同 Bundle
  • onCreate() 方法
  • 调用onCreate() 方法需要区分是创建 Activity 的新实例还是恢复先前的实例,判断 Bundle 是否为 null
  • 示例
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    @Override
    protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState); // Always call the superclass first

    // Check whether we're recreating a previously destroyed instance
    if (savedInstanceState != null) {
    // Restore value of members from saved state
    mCurrentScore = savedInstanceState.getInt(STATE_SCORE);
    mCurrentLevel = savedInstanceState.getInt(STATE_LEVEL);
    } else {
    // Probably initialize members with default values for a new instance
    }
    ...
    }
  • onRestoreInstanceState()方法
  • 只需要恢复的已保存的状态
  • 示例
    1
    2
    3
    4
    5
    6
    7
    8
     public void onRestoreInstanceState(Bundle savedInstanceState) {
    // Always call the superclass so it can restore the view hierarchy
    super.onRestoreInstanceState(savedInstanceState);

    // Restore state members from saved instance
    mCurrentScore = savedInstanceState.getInt(STATE_SCORE);
    mCurrentLevel = savedInstanceState.getInt(STATE_LEVEL);
    }

重启Activity其他选择

  • 官方说明
  • 重启应用并恢复大量数据不仅成本高昂,而且会留下糟糕的使用体验,有两个其他选择

    在配置变更期间保留对象

  • Activity 因配置变更而重启,则可通过保留 Fragment 来减轻重新初始化 Activity 的负担
  • 当 Android 系统因配置变更而关闭 Activity 时,不会销毁已标记为要保留的 Activity 的片段。 您可以将此类片段添加到 Activity 以保留有状态的对象。

Activity最佳实践

知晓当前运行的活动

  • 自定义BaseActivity继承自AppCompatActivity
    重写onCreate方法,打印当前运行的Activity名
    app类所有Activity改为继承BaseActivity

  • 代码如下

    1
    2
    3
    4
    5
    6
    7
    public class BaseActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    //打印当前类名
    Log.d("BaseActivity", getClass().getSimpleName());
    }
  • 效果如下
    ScreenShot_20161208172740.png

随时退出程序

  • 新建ActivityCollector类作为活动管理器,添加/删除Activity登记,在BaseActivity中添加对应代码。
  • 代码如下
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
     public class ActivityCollector {
    //新建Activity的列表对象
    public static List<Activity> activities = new ArrayList<>();
    //添加Activity进入列表
    public static void addActivity(Activity activity) {
    activities.add(activity);
    }
    //在列表中移除Activity
    public static void removeActivity(Activity activity) {
    activities.remove(activity);
    }
    //for循环列表 结束所有Activity
    public static void finishAll() {
    for (Activity activity : activities) {
    if (!activity.isFinishing()) {
    activity.finish();
    }
    }
    }
    }
  • BaseActivity
    在onCreate()方法中添加
    1
    2
    //Activity管理器中添加新的活动
    ActivityCollector.addActivity(this);
    重写onDestroy()方法
    1
    2
    3
    4
    5
    6
    @Override
    protected void onDestroy(){
    super.onDestroy();
    //Activity管理器中移除活动
    ActivityCollector.removeActivity(this);
    }
  • 在任何地方调用finishAll()方法即可。

启动活动的最近写法

  • 启动下一个Activity并传递数据模块化
  • 在SecondActivity内增加actionStart()方法
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
     //调用的Activity,数据1,数据2
    public static void actionStart(Context context, String data1, String data2){
    //新建Intent,绑定SecondActivity
    Intent intent = new Intent(context,ScendActivity.class);
    //存入data1,data2
    intent.putExtra("param1",data1);
    intent.putExtra("param2",data2);
    //启动Activity
    context.startActivity(intent);
    }
  • 启动SecondActivity方式
    ScendActivity.actionStart(FirestActivity.this,"data1","data2");