第十三章—继续进阶,你还应该掌握的高级技巧
本章主要学习了如何在全局获取Context,使用Intent来传递复杂的数据对象(有Serialiazable和Parcelable两种方式),定制自己的调试工具(可以选择显示或者不显示)、调试安卓程序(Attach debugger的用法)、创建定时任务(Alarm的用法与Doze模式)、多窗口模式编程因为没有接触过应采用场景,所以大致看了下理论、Lambda应用于Jdk1.8上,追求极简主义的可以使用它。至此,我们已经学习了环境搭建、四大组件、UI、碎片、数据存储、多媒体、网络、定位服务、Material Design等等。下章将以此为基础开发一个真实的APP。
13.1全局获取Context的技巧
(1)如何获取到Context对象?
解决方法:在方法体的参数列表中增加final Context context,但存在推卸责任的问题。
我们的方法:(1)定制自己的Applicatoin类,用于管理程序内部的状态信息;(2)告知系统启动时是初始化自定义MyApplication类,而非Application;具体做法是在AndroidManifest.xml中添加完整包名类名。(3)如果在哪里需要使用Context,则使用MyApplication.getContext来实现。
(2)Context是什么?
Context是个抽象类,通过类的结构可以看到:Activity、Service、Application都是Context的子类;如果从Android系统的角度来理解:Context是一个场景,描述的是一个应用程序环境的信息,即上下文,代表与操作系统的交互的一种过程。如果从程序的角度上来理解:Context是个抽象类,而Activity、Service、Application等都是该类的一个实现。有一个公式:
总Context实例个数 = Service个数 + Activity个数 + 1(Application对应的Context对象)
(3)如何获取?
通常我们想要获取Context对象,主要有以下四种方法
1:View.getContext,返回当前View对象的Context对象,通常是当前正在展示的Activity对象。
2:Activity.getApplicationContext,获取当前Activity所在的(应用)进程的Context对象,通常我们使用Context对象时,要优先考虑这个全局的进程Context。
3:ContextWrapper.getBaseContext():用来获取一个ContextWrapper进行装饰之前的Context,可以使用这个方法,这个方法在实际开发中使用并不多,也不建议使用。
4:Activity.this 返回当前的Activity实例,如果是UI控件需要使用Activity作为Context对象,但是默认的Toast实际上使用ApplicationContext也可以。
13.2使用Intent传递对象
使用Intent我们之前是使用putExtra方法来实现传递对象,但其所支持的类型是有限的,当自定义数据结构时,往往不能实现数据的传递。我们可以使用以下两种方式:Serializable和Parcelable来实现。
(1)Serializable方式
序列化,将对象转换为可存储可传输的状态,如何序列化,继承Serializable接口。示例:
1.建立Person类实现Serializable接口。
public class Person implements Serializable {
//让Person类去实现Serializable,进而Person对象就是可序列化的了。
private String name;
private int age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
2.在MainActivy中构建Intent对象。
Person person = new Person();
person.setName("kevin");
person.setAge(12);
Intent intent = new Intent(MainActivity.this,Main2Activity.class);
intent.putExtra("person_data",person);
startActivity(intent);
3.在Main2Activity中接受对象。
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main2);
Person person = (Person) getIntent().getSerializableExtra("person_data");
Log.i(TAG, person.getName());
Log.i(TAG, String.valueOf(person.getAge()));
}
(2)Parcelable方式
1.首先继承并实现Parcelable接口,重写describeContents和writeToParcel两个方法,这是writeToParcel调用Parcel的writeXX方法,将字段一一写出。此外,我们必须要在Person类中提供一个CREATOR的变量,泛型指定为Person,重写createFromPacel和newArray两个方法,createFromPacel为读取刚才写的name和age字段,并创建PEerson对象进行返回。读取顺序与写入顺序要完全相同。而new Array则只需要返回数组大小即可。
public class Person implements Parcelable {
//让Person类去实现Serializable,进而Person对象就是可序列化的了。
private String name;
private int age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(name);//写出name
dest.writeInt(age);//写出Age
}
public static final Parcelable.Creator CREATOR = new Parcelable.Creator(){
@Override
public Person createFromParcel(Parcel source) {
Person person = new Person();
person.name = source.readString();
person.age = source.readInt();
return person;
}
@Override
public Person[] newArray(int size) {
return new Person[size];
}
};
}
2.在ManiActivy中获取刚才传递的Intent中的数据。
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main2);
Person person = (Person) getIntent().getParcelableExtra("person_data");
Log.i(TAG, person.getName());
Log.i(TAG, String.valueOf(person.getAge()));
}
13.3定制自己的日志工具
应用场景:调试时打印了较多日志,现在上线了要把日志删除掉,如果不删,效率降低,安全性降低。一行一行删除,费时费力,而且以后可能还会在用的到。达到效果:开发阶段将日志打印出来,程序上线之后将日志屏蔽掉。
(1)定义留个整形变量,对应调试等级。定义静态变量level,将它指定为六个中的一个。定义五个自定义日志方法,加入判断条件,只有当level小于等于对应日志级别时才会被打印出来。
public static void v(String tag,String msg){
if(level<=VERBOSE){
Log.v(tag,msg);
}
}
public static void d(String tag,String msg){
if(level<=DEBUG){
Log.d(tag,msg);
}
}
public static void i(String tag,String msg){
if(level<=INFO){
Log.i(tag,msg);
}
}
public static void w(String tag,String msg){
if(level<=WARN){
Log.w(tag,msg);
}
}
public static void e(String tag,String msg){
if(level<=ERROR){
Log.e(tag,msg);
}
}
(2)使用LogUtil来进行日志的输出。在这里我们只需要修改level的值,来自由控制日志输出行为。让level等于VERBOSE可以将所有日志输出,当等于NOthing所有都会被屏蔽掉。
LogUtil.i(TAG, person.getName());
可以,蛮有意思的。
13.4调试安卓程序:
调试可以逐行执行代码,并适时观察内存内的数据,轻易查出问题。
1.下断点;2.F8下一步走;
改进方法:动态调试按钮也没啥太大区别,只是选择进程进行调试,能更为灵活、随机进入调试状态。更快更方便。
此处需要提到:20个常用的AS快捷键:(参考链接:https://blog.csdn.net/xuaho0907/article/details/72026869)
1.格式化代码:Ctrl+Alt+L
2.自动导入包路径:Ctrl+Alt+O
3.重命名文件、类名、变量名:Shift+F6
4.撤销操作:Ctrl+Z
5.反撤销操作:Ctrl+Shift+Z
6.全局搜索文件:双击Shift
7.查找:Ctrl+F
8.双击类名、变量、方法选中,查看调用的地方:Alt+F7
9.强制提示代码:Ctrl+Alt+空格
10.按关键字全局搜索:Ctrl+Shift+F
11.代码自动修正,鼠标点中出错的代码:Alt+Enter
12.在类中查看继承:Ctrl+O
13. Shift + Enter任意位置换行(往下添加空行
14. Alt + /代码提示
15.Ctrl + Y 删除当前行
16. Ctrl + D粘贴当前行到下一行
17. Shift + Alt + Up/Down当前行、选中行向上/向下移动
18. Ctrl+Q:把光标移至方法处,按此组合键可快速查看方法的说明文档
19.Alt + Left/Right 切换代码视图
20. Ctrl + Enter在当前行的上一行插入新行,光标在行首时有效
13.4创建定时任务:
Android中目前有两种实现定时任务的方式,一个是Java API中的Timer类;另一个是Android的Alarm机制。Timer不适合用于长期在后台运行的定时任务。因为Android手机在长时间不操作时会将CPU转入睡眠状态。Alarm具有唤醒CPU功能。可以执行定时任务时CPU可以正常工作。
(1)Alarm机制
1.借助于AlarmManager来实现。获取相应的实例;
2.利用AlarmManager的set方法来设置定时任务,第一个参数是整形参数,用于指定工作类型,有分唤醒CPU与否、任务触发时间之分,共四种类型。第二个参数任务触发时间,这里有开机时间+延迟执行时间。第三个参数,pendingIntent。设定完触发时间。
3.使用PendingIntent指定处理定时任务的服务,使用在onStartCommand中开启一个子线程,在这里处理耗时逻辑。
4.在MAinActivty启动定时服务。正常启动Service的写法。
public class LongRunningService extends Service {
public LongRunningService() {
}
@Override
public IBinder onBind(Intent intent) {
return null;
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
new Thread(new Runnable() {
@Override
public void run() {
Log.i("testing", "run: ");
}
}).start();
AlarmManager manager = (AlarmManager) getSystemService(ALARM_SERVICE);
int minute = 5 *1000;//定时时间为5s
long triggerAtTime = SystemClock.elapsedRealtime()+minute;
Intent i = new Intent(this,LongRunningService.class);
PendingIntent pi = PendingIntent.getService(this,0,i,0);
manager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP,triggerAtTime,pi);
return super.onStartCommand(intent, flags, startId);
}
}
Intent intent = new Intent(MainActivity.this,LongRunningService.class);
startService(intent);
若Alarm执行时间必须准确无误,则将set替换为setExact即可。
(2)Doze模式。
Android6.0之后加入,未插电源、处于静止状态、屏幕关闭一段时间。自动进入Doze模式。此时,系统对CPU、网络、Alarm活动进行限制。延长电池寿命。系统不会一直处于Doze模式下,会间歇性的退出Doze模式去处理同步操作、Alarm任务等。工作过程如下图所示:
此时,Alarm任务不准时了,要使得其在Doze模式下也能正常运行,则调用AlarmManager中的setAndAllowwhileIdle()和setExtractAndAllowWhileIdle方法。
13.6多窗口模式编程。
Addroid7.0之后引入,作者从如何进入多窗口模式、多窗口模式下的生命周期、禁用多窗口模式(若SDKVersion大于24,则在Application属性中指定android.resizeableActivity为false,若小于,则在xml中的MainActivity中设置 android:screenOrientiation=”portrait”(设置只能为竖屏,Android规定,若SDK小于24,且活动不允许横竖屏切换,则不支持多窗口模式))
因为我没见过类似的功能,这块就大致看了下,不难,等以后有了相似的需求再看吧。
13.7 Lambda表达式
Java8引入了LAmbda、Stream API、接口默认实现等。因为后两者都必须在7.0以上,前者可以兼容至2.3,因此介绍Lambda。
1.配置Java8,在app中的build.gradle中:
defaultConfig {
....
versionName "1.0"
jackOptions.enabled = true
}
compileOptions{
sourceCompatibility org.gradle.api.JavaVersion.VERSION_1_8
targetCompatibility org.gradle.api.JavaVersion.VERSION_1_8
}
2.new Thread可以这样去写。精简许多。若接口只有一种待实现方法,则可以使用Lanbda表达式的写法。
new Thread(()->{
}).start();
3.button的响应事件缩写。
btn_click.setOnClickListener(v -> {
Intent intent = new Intent(MainActivity.this, LongRunningService.class);
startService(intent);
});
说实话,没多大用处,只是缩写。