10、Android--技巧

 10.1、全局获取Context的技巧

在实践中有很多的地方都可以使用到Context

弹出Toast的时候需要,启动活动的时候需要、发送广播的时候需要、

操作数据库的时候需要、使用通知的时候需要.......

 

现在的开发中基本上都在使用Context而发愁过

因为操作都是再活动中进行的,活动的本身就是一个Context对象

当应用程序的架构逐渐开始复杂起来的时候

很多的逻辑代码都将脱离Activity类

但是此时由需要使用Context,也许这个时候会感到废脑子

 

如封装好的网络操作:

此时再方法中添加一个Context参数

并且假设有一个isNetworkAvaiable()方法来判断网路是否可用

 

虽说上述是一种解决方案,但是却有点推卸责任的嫌疑

因为我们将获取Context的任务转移给sendHttpRequest()方法的调用方

至于用什么方法能不能得到Context对象,那不是我们需要考虑的问题

 

再某些情况下,获取Context并非是一件容易的事情

有时候还是挺伤脑筋的。

 

Android提供了一个Application类,每当应用程序启动的时候

系统就会自动将这个类进行初始化

我们可以指定一个自己的Application类

以便于管理程序内一些全局状态的信息

比如全局的Context。

 

定义自己的Application其实不复杂

首先创建一个MyApplication类继承Application

public class MyApplication extends Application {

    private static Context context;

    @Override
    public void onCreate() {
        super.onCreate();
        context = getApplicationContext();
    }

    public  static Context getContext(){
        return context;
    }
}

 

重写父类的onCreate()方法

通过调用getApplicationContext()方法得到一个应用级别的Context

然后提供一个静态的getContext()方法,在这里将获取到的Context进行返回

 

接下来需要告知系统,当程序启动的时候应该初始化MyApplication类

而不是默认的Application类

再AndroidManifest.xml文件中的<application>标签下进行注册

        <activity android:name="com.example.ccrr.material.MyApplication">

        </activity>

 指定的时候一定要加上完整的包名,不然系统将无法找到这个类

 

 这样就有一套全局获取Context的机制

不管咋任何地放进行使用Context,只需要调用一些:MyAppcation.getContext()方法即可

此时再sendHttpRequest()方法中优化如下:

这里的sendHttpRequest()方法不需要传入参数的方式来得到Context对象

而是调用MyApplication.getContext()方法即可

 

之前使用的LitePal就能再内部自动获取到Context

 

已经配置过Application是否会和LitePalApplication产生冲突

任何一个项目都只能配置一个Application

对于这种情况,LitePal提供了更加简单的使用方法

如下:

使用这种写法就相当于把全局的Context对象通过参数传给LitePal

效果和AndroidManifest.xml配置LitePalApplication是一样的

 

10.2、使用Intent传递对象

 Intent在之前的使用已经相当熟悉了

可以借助他来启动活动、发送广播、启动服务等

同时还可以在Intent中添加一些附带的数据,已达到传值的效果

在FirstActivity中

这调用Intent的putExtra()方法来添加要传递的数据

之后再SecondActivity中

但是putExtra()方法中所支持的数据类型是有限的

对常用一些数据类型都是支持的

若是定义一些自定义的对象的时候就会无从下手

下方的有一些技巧

 

10.2.1、Seriallizable

serializable是序列化的意思

表示将一个对象转换成可存储或者可传输的状态

序列化后的对象可以在网络上进行传输,也可以存储再本地

至于序列化的方法很简单,只需要让一个类实现Serializable这个接口即可

 

实例:

定义一个Person类,其中包含了name和age这两个字段,实现序列化实现接口即可

其中get、set方法都是用于赋值和读取字段的数据

最重要的一部分再第一行

这里让Person类去实现Serializable接口

这样所有的Person对象都是看可序列化的

 

然后再FirstActivity中:

这里新建的一个Personde实例

然后就直接将他们传入到putExtra()方法

由于再Person类实现了Serializable接口,所以是这个写法

 

再SecondActivity中

调用getSerializableExtra()方法获取通过参数传递过来的序列化对象

接着将它转为Person对象

此时成功实现了使用Intent来传递对象的功能

 

10.2.2、Parcelable方式

 除了Serializable之外,使用Parcelable也可以实现相同的效果

不过不同于将对象序列化

Parceleable方式实现的原理是一个将完整的对象进行分家

而后分解的每一部分都是Intent所支持的数据类型,这样就实现了传递对象的功能。

修改Person中的代码

Parcleable的实现方式稍微复杂一些

首先让类实现Parcleable接口,因此必须实现describeContents()方法和writeToParcel()方法

describeContents() 方法直接返回为0即可

writeToParcel() 方法中需要调用Parcel的writeXxx()方法,将Person类中的字段一一写相互

注意:根据数据类型进行调用,string、int....

 

还需要再Person类中提供一个名为CREATOR的常量

这里创建一个Paraelable.Creator接口的一个实现

接着重写createFromParcel()和newArray()这两个方法

createFromParcel()方法中读取刚刚写出的name、age字段,并且创建一个Person对象进行返回

其中name个age都是需要调用Parcel的readXxx()方法读取到,这里也需要注意类型string、int,以及读顺序和写顺序相同

再newArray()方法中只需要new一个Person数组,使用方法中传入size作为数组的大小即可

 

再SecondActivity中:

这里使用getParcelableExtra()方法来获取数据传递过来的对象

其他地方一致

 

10.3、定制自己的日志工具

对于日志的使用

并且再开发中经常使用做数据测试操作

但是这种使用再日志的控制方面还是做的不是很好

 

再大型项目中会再很多地方使用日志进行打印

再项目完成时就是再代码中调式的日志问题

在项目上线时还会进行打印,很容易降低程序的运行效率、还有可能将数据泄露出

 

解决最简单的方式是能够自由地控制日志的打印

当程序处于开发阶段就让日志打印出来

程序上线之后舅把日志屏幕掉

 

需要定制一个自己的日志工具:

 

 

10.4、创建定时任务 

 定时任务由两种实现方式:

1、使用Java Api里提供的Timer类

2、使用Android的Alarm机制

两种方式在多数情况下都能实现类似的效果

 

但Timer有一个明显的短板

不适用于那些长期在后台运行的定时任务

为了能让电池更加耐用,每种手机都会由自己的休眠策略

Android手机就会长期在不操作的情况下自动让CPU进入到休眠状态

这就有可能导致Timer中的定时任务无法正常执行

而Alarm具有唤醒CPU的功能

可以保证在大多数情况下需要请求执行任务的时候CPU都能正常工作

唤醒CPU和唤醒屏幕不是一个概念的问题

 

10.4.1、Alarm机制

主要借助了AlarmManager类来实现的

这个类和NotificationManager有点类似

都是通过调用Context的getSystemService()方法来获取实例

这里需要传入的的参数是Context.ALARM_SERVICE

因此获取一个AlarmManage的实例:

 

接下来调用AlarmManager的set()方法就可以设置一个定时任务

比如设置一个10秒中后执行,就可以写成:

 

set()方法需要传入三个参数

1、整形参数,用于指定AlarmManager的工作类型有四个可选值

  ELAPSED_REALTIME、ELAPSED_REALTIME_WARKUP、RTC和RTC_WAKEUP

  ELAPSED_REALTIME:表示让定时任务的触发时间从系统开机开始算起,但是不会唤醒CPU

  ELAPSED_REALTIME_WARKUP:表示让定时任务的触发时间从系统时间开始算起,但是不会唤醒CPU

  RTC:表示任务的触发时间是从1970年1月1日0点开始算起,但是会唤醒CPU

  RTC_WAKEUP:表示任务的触发时间是从1970年1月1日0点开始算起,但是会唤醒CPU

  使用SystemClock.elapsedRealtime()方法可以获取到系统开机至今所经历时间的毫秒数

  使用System.currentTimeMills()方法可以获取到1970年1月1日0点至今所经历的毫秒数

2、这个参数是定时任务触发的事件,以毫秒为单位

  如果第一个参数使用的是ELAPSED_REALTIME、ELAPSED_REALTIME_WARKUP

  这里传入开机至今的事件加上延迟执行的时间

  如果第一个参数使用的是RTC、RTC_WAKEUP

  这里传入1970年1月1日0点至今的时间在加上延迟的时间

3、此时的参数是一个PendingIntent,这里一般会调用getService()方法或者getBroadcast方法来获取一个能够

  执行服务或广播的PendingIntent,这样当任务被触发的时候,服务的onStratCommand()方法或广播接收器

  onReceive()方法就可以得到执行

 

所以此时的set()方法,设定一个任务在10秒钟后执行可以:

 

如果要实现一个长时间在后台定时运行的服务

首先创建一个普通的服务

再将触发的定时任务代码写在onStratCommand()方法

首先在onStratCommand()方法中开启一个子线程

在这里可以执行一些具体的操作

之所以在子线程中执行逻辑操作

是因为逻辑操作也需要耗时的

在主线程中执行可能会对定时的准确性造成影响

 

创建线程之后的代码就是Alarm机制的使用

首先获取了AlarmManager的实例

然后在定义任务的触发时间为一小时之后

在使用PendingIntent执行处理定时任务的服务为LongRunningSevice

最后调用set()方法完成设定

 

这样就定义了一个 长时间在后台运行的服务实现

因为一旦启动了LongRunningService

就会在onStartCommand()方法里设定一个定时任务

这样一个小时后将会再次启动LongRunningServie

从而形成了一个永久的循环

保证LongRuningService的onStratCommand()方法可以每隔一小时就执行一次

 

注意的是:

4.4系统开始,Alarm任务的触发时间将会变得不准确

有时会延迟一段时间之后任务才能得到执行

这不是一个bug,而是系统对耗电方面性能的优化

系统会自动检测目前有多少个Alarm任务存在

然后触发时间相近的几个任务放在一起执行

这样就可以大幅度减少CPU被唤醒的次数,从而提高电池的使用时间

 

如果要求Alarm任务的执行时间必须准确无误

Android提供了解决方案

使用AlarmManager的setExact()方法来代替set()方法

基本上就可以保证任务能够准确的执行

 

10.4.2、Doze模式

虽然Android的每个版本都在手机电量方面的努力进行优化

不过一直没有解决后台服务泛滥、手机电量消耗过快的问题

于是在Android6.0系统中,谷歌加入了一个全新的Doze模式

从而及大幅度的延长电池的使用寿命

 

首先Doze模式:

当用户设备是一个Android6.0或以上的系统时

如果设备未插电源,处于静止状态,且屏幕关闭了一段时间之后,就会进入Doze模式

早Doze模式下:系统会对CPU、网络、Alarm等活动进行限制,从而延长了电池的使用寿命

 

系统并不会一直处于Doze模式,而是间接性的退出Doze模式一小段时间

在这段时间中,应用可以完成他们的同部操作、Alarm任务等等

 

随着设备进入Doze模式的时间越长,间接性的退出Doze模式的时间间隔也会越来越长

因为如果设备长时间不使用的话,是没必要频繁的退出Doze模式来执行同部操作的

Android在这些细节上的把控使得电池寿命进一步得到了延长

 

Doze模式下会有一些功能会受到限制:

最后一条:

在Doze模式下,我们的Alarm任务将会变得不准时

当然,这在大多情况下是合理的,因为当用户长时间不使用手机才会进入Doze模式

通常这种情况下对ALarm任务的准确性要求没有那么高

 

真的有特殊需求

需求对Alarm任务模式即使在Doze模式情况下也能必须正常执行

Android还提供了解决方案

调用AlarmManage的setAndAllowWhileIdle()或setExactAndAllowWhileIdle()方法

能够让定时任务即使在Doze模式下也能进行正常执行

这两个方法之间的区别和set()、setExact()方法之间的区别一致。

 

posted @ 2019-04-20 19:11  MrChengs  阅读(300)  评论(0编辑  收藏  举报