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()方法之间的区别一致。