全局获取Context技巧
主要就是自定义 Application ,在 Application 中实现全局获取Context,代码如下:
1 | public class MyApplication extends Application{ |
使用Intent传递对象
Intent的putExtra()方法传递数据的时候,支持的数据类型是有限的,虽然常用的一些数据类型它都支持,但是当你想传递一些自定义对象的时候,就会无从下手。其实使用Intent来传递对象通常有两种实现方式:Serializable和Parcelable。
Serializable 方式
Serializable是序列化的意思,表示将一个对象转换成可存储或可传输的状态。至于序列化的方法也很简单,只需要实现Serializable这个接口就行,如以下的Person类:
1 | public class Person implements Serializable{ |
如果要在Activity之间传递的话,只需要简单的几行代码即可:
1 | Person person = new Person(); |
可以看到我们穿件了Person实例,之后直接将它传入putExtra()方法中了,只是因为Person实现了Serializable接口,所以才可以这么写。接下来要在SecondActivity中获取这个对象也很简单:
1 | Person person = (Person)getIntent().getSerializableExtra("person_data"); |
这里调用了getSerializableExtra方法来获取通过参数传递过来的序列化对象,接着再向下转型得到Person对象,就成功实现了Intent传递对象。
Parcelable方式
除了Serializable之外,使用Parcelable也可以实现相同的效果,不过不同于将对象进行序列化,Parcelable方式的实现原理是将一个完整的对象进行分解,而分解后的每一部分都是Intent所支持的数据类型,这样也就实现了传递对象的功能了。下面修改下Person类的代码:
1 | public class Person implements Parcelable{ |
Parcelable 的实现方式要稍微复杂些,首先继承 Parcelable 接口,这样就必须重写 describeContents() 和 writeToParcel() 两个方法。 describeContents 中直接返回0即可,writeToParcel中需要将Person类中的字段一一写出。除此之外,还得再Person类中提供一个 CREATOR 常量。接下来,我们仍然可以通过前面相同的代码来传递Person对象,只不过在SecondActivity中获取对象的时候需要稍加改动:
1 | Person person = (Person)getIntent().getParcelableExtra("person_data"); |
一般来说,Serializable方式比较简单,但是这会把整个对象序列化,因此效率比Parcelable低一些,所以更加推荐Parcelable方式。
定制自己的日志工具
实用性不太强,公司项目有更强大的日志工具,因此 略
调试Android程序
已经掌握,略
创建定时任务
Android中的定时任务一般有两种实现方式:一是Java API中的Timer类,二是Android中的Alarm机制。但Timer类不太适用于哪些需要长期在后台运行的定时任务,因为为了能让电池更加耐用,每种手机都会有自己的休眠策略,Android手机会在长时间不操作的情况下自动让CPU进入睡眠状态,这可能导致Timer中的定时任务无法正常运行。而Alarm则具有唤醒CPU功能,可以保证大多数情况下需要执行定时任务的时候CPU都能正常工作。注意,这里唤醒CPU和唤醒屏幕完全不是一个概念,千万不要混淆。
Alarm机制
Alarm机制用法并不复杂,主要借助于AlarmManager实现,跟NotificationManager有点类似,获取实例的方法如下所示:
1 | AlarmManager manager = (AlarmManager)getSystemService(Context.ALARM_SERVICE); |
接下来使用set()方法就可以设置一个定时任务了,比如想设定一个任务在10秒钟后执行:
1 | long triggerAtTime = SystemClock.elapsedRealtime() + 10 * 1000; |
第一个参数是整型参数,用于指定AlarmManager的工作类型,有4种值可选:ELAPSED_REALTIME(让定时任务的触发时间从系统开机开始算,但不会唤醒CPU)、ELAPSED_REALTIME_WAKEUP(表示定时任务从系统开机开始算起,但会唤醒CPU)、RTC(让定时任务触发时间从1970年1月1日0点开始算起,但不会唤醒CPU)、RTC_WAKEUP(让定时任务触发时间从1970年1月1日0点开始算起,但会唤醒CPU)。可以使用SystemClock.elapsedRealtime()获取到系统开机至今所经历的时间的毫秒数。Systemt.currentTimeMillis()可以获取到1970年1月1日0点至今所经历的毫秒数。第二个参数就是定时任务触发的时间,如果第一个参数是ELAPSED_REALTIME或者ELAPSED_REALTIME_WAKEUP,则传入开机至今时间加上延迟执行的时间;如果是RTC或者RTC_WAKEUP,则传入1970年1月1日0点至今的时间再加上延迟执行的时间。第三个参数不多说。
那么如果要实现一个长时间在后台定时运行的服务要如何做呢,其实只要建立一个普通服务,然后将触发定时任务的代码写到onStartCommand()方法中,如下所示:
1 | public class LongRuningService extends Service{ |
这样,一旦启动了LongRuningService,就会设定一个定时任务,一个小时后会再次启动LongRuningService,形成一个永久循环。
Doze模式
在Android 6.0中,google加入了Doze模式,及大幅度延长电池使用寿命。主要表现为:如果设备未插电源,处于静止(7.0后删除这条件),并且屏幕关闭了一段时间之后,就会进入Doze模式,系统会对CPU、网络、Alarm等活动限制,当然,系统还会间歇性地退出Doze一小段时间,在这段时间,应用可以去完成他们的同步操作、Alarm任务等。如下图所示:
可以看到,随着设备进入Doze模式的时间越长,间歇性退出Doze模式的时间间隔也会越来越长,因为如果设备长时间不使用的话,没必要频繁退出Doze。以下列出Doze模式下有具体哪些功能会收到限制:
- 网络访问被禁止
- 系统忽略唤醒CPU或者屏幕操作
- 系统不再执行WIFI扫描
- 系统不再执行同步服务
- Alarm任务将会在下次退出Doze模式时候执行
不过,如果你真有非常特殊需求,要求Alarm任务在Doze模式也必须正常执行,可以调用AlarmManager的setAndAllowWhileIdle()或setExactAcnAllowWhileIdle()方法就能让定时任务即使在Doze模式下也能正常执行了,这两个方法之间的区别和set()、setExact()方法之间的区别一样。
*** 多窗口模式
在一个屏幕上,同时显示两个app界面。切换到多窗口模式,Activity会经历重新创建的过程。其实这是正常现象,进入多窗口模式后,Activity的大小发生了比较大的变化,此时默认会重新创建活动的。除此之外,像横竖屏也会重新创建活动。如果此时去操作另一个窗口,则当前窗口会执行onPause,而另一个窗口会执行onResume,这很好理解,因为两个窗口都是可见的,所以只会执行到onPause即可。因此,在考虑多窗口模式下,用户仍然可以看到处于暂停状态的应用,那么像视频播放器之类的应用应该在此时能够继续播放视频才对,因此最好不要在Activity的onPause()方法中去处理播放器的暂停逻辑,而应该在onStop()方法中处理,并且在onStart()方法中恢复视频播放。另外,针对进入多窗口时活动会被重新重新创建,如果想改变这一默认行为,可以在AndroidManifest.xml中进行配置:
1 | <activity |
这样不管进入多窗口模式还是横竖屏切换,Activity都不会被重新创建,而是会将屏幕发生变化的事件通知到Activity的onConfigurationChanged()方法中,因此,如果有这方面的需求,只需要重写onConfigurationChanged()即可。
当然,如果想禁用多窗口模式,只需要在 AndroidManifest.xml 中的
1 | android:resizeableActivity=["true" | "false"] |
需要注意的是,这个属性只有在项目的targetSdkVersion指定成24或者更高的时候才会有用。不过Android规定,如果targetSdkVersion小于24,并且Activity不允许横竖屏切换,那么应用也将不支持多窗口模式,如果不允许应用横竖屏切换,只需要在AndroidManifest.xml中添加如下配置:
1 | android:screenOrientation=["portrait" | "landscape"] |