13.1 全局获取 Context的技巧
13.2 使用 Intent传递对象
13.2.1 Serializable 方式
13.2.2 Parcelable 方式
13.3 定制自己的日志工具
13.4 调试Android 程序
13.5 创建定时任务
13.5.1 Alarm 机制
13.5.2 Doze模式
13.6 多窗口模式编程
13.6.1 进入多窗口模式
13.6.2 多窗口模式下的生命周期
13.6.3 禁用多窗口模式
13.7 Lambda 表达式
13.8 总结
当应用程序的架构逐渐开始复杂起来的时候,很多的逻辑代码都将脱离Activity类,这个时候你就要伤脑筋了。
下面就来学习一种技巧,让你在项目的任何地方都能够轻松获取到Context。
Android 提供了一个 Application 类,每当应用程序启动时,系统就会自动将这个类进行初始化。我们可以定制一个自己的 Application 类,以便管理程序内一些全局的状态信息,比如全局的 Context。
定制一个自己的 Application 并不复杂,首先需要创建一个 MyApplication 类继承自 Application,如下:
public class MyApplication extends Application {
private static Context context;
@Override
public void onCreate() {
context = getApplicationContext();
}
public static Context getContext(){
return context;
}
}
接下来在 AndroidManifest.xml 文件的
. . .
这样就实现了一种全局获取 Context 的机制,之后在项目的任何地方想要获取 Context,只需调用 MyApplication.getContext() 就可以了,如弹吐司:
Toast.makeText(MyApplication.getContext(),"提示内容",Toast.LENGTH_SHORT).show();
任何一个项目都只能配置一个 Application,当引用第三方库如 LitePal 时要配置 LitePalApplication 就会起冲突了,这种情况就要在自己的 Application 中去调用 LitePal 的初始化方法,如下:
public class MyApplication extends Application {
private static Context context;
@Override
public void onCreate() {
context = getApplicationContext();
// 调用 LitePal 的初始化方法
LitePal.initialize(context);
}
public static Context getContext(){
return context;
}
}
使用上面的写法,把 Context 对象通过参数传递给 LitePal,效果和在 AndroidManifest.xml 中配置 LitePalApplication 是一样的。
当然,我个人是更习惯于通过获取全局类实例的方法来定制自己的 Application 类,如下:
public class MyApplication extends Application {
private static MyApplication mInstance;
@Override
public void onCreate() {
super.onCreate();
mInstance = this;
// 调用 LitePal 的初始化方法
LitePal.initialize(this);
}
/**
* Singleton main method. Provides the global static instance of the helper class.
* @return The MyApplication instance.
*/
public static synchronized MyApplication getInstance() {
return mInstance;
}
}
使用 Intent 时,可以在 Intent 中添加一些附加数据,以达到传值的效果,如在第一个活动中添加如下代码:
Intent intent = new Intent(FirstActivity.this,SecondActivity.class);
intent.putExtra("string_data","hello");
intent.putExtra("int_data",100);
startActivity(intent);
然后在第二个活动中就可以获得这些值了,如下:
getIntent().getStringExtra("string_data");
getIntent().getIntExtra("int_data",0);
但上面的 putExtra() 方法中所支持的数据类型是有限的,若要传递一些自定义对象时就无从下手了,下面就来学习下用 Intent 来传递对象的技巧:Serializable 和 Parcelable。
Serializable是序列化的意思,表示将一个对象转化成可储存或可传输的状态。序列化的对象可在网络上传输也可存储到本地。将一个类序列化只要去实现 Serializable 接口就可以了。
比如一个 Person1 类,将它序列化可以这样写:
package com.dak.administrator.firstcode.more;
import android.os.Parcel;
import android.os.Parcelable;
/**
* Created by Administrator on 2018/11/13.
*/
public class Person implements Parcelable {
private int age;
private String name;
public static final Creator CREATOR = new Creator() {
@Override
public Person createFromParcel(Parcel in) {
Person person = new Person();
person.age = in.readInt(); //顺序要和写入相同
person.name = in.readString();
return person;
}
@Override
public Person[] newArray(int size) {
return new Person[size];
}
};
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel parcel, int i) {
parcel.writeInt(age);
parcel.writeString(name);
}
}
接下来在第一个活动中的写法非常简单:
Person1 person = new Person1();
person.setName("Tom");
person.setAge(18);
Intent intent = new Intent(FirstActivity.this,SecondActivity.class);
intent.putExtra("person_data",person);
startActivity(intent);
然后在第二个活动中获取对象也非常简单:
Person1 person = (Person1) getIntent().getSerializableExtra("person_data");
这样就实现了使用 Intent 传递对象了。
Parcelable 也可以实现相同的效果,不过不同于将对象进行序列化,Parcelable 方式的实现原理是将一个完整的对象进行分解,而分解后的每一部分都是Intent所支持的数据类型,这样也就实现传递对象的功能了。
新建Person类继承Parcelable:
package com.dak.administrator.firstcode.more;
import android.os.Parcel;
import android.os.Parcelable;
/**
* Created by Administrator on 2018/11/13.
*/
public class Person implements Parcelable {
private int age;
private String name;
public static final Creator CREATOR = new Creator() {
@Override
public Person createFromParcel(Parcel in) {
Person person = new Person();
person.age = in.readInt(); //顺序要和写入相同
person.name = in.readString();
return person;
}
@Override
public Person[] newArray(int size) {
return new Person[size];
}
};
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel parcel, int i) {
parcel.writeInt(age);
parcel.writeString(name);
}
}
注意:Serializable 的方式较为简单,但由于把整个对象进行序列化,效率会比 Parcelable 方式低。一般推荐使用 Parcelable 的方式来实现 Intent 传递对象的功能。
虽然Android自带的日志工具功能非常强大,但也不是没有缺点,比如,你正在编写一个比较庞大的项目,用到了大量的日志,这样在项目正式上线后,仍然会照常打印,这样不仅会降低程序的运行效率,还有可能就将一些机密性额数据泄露出去。
为此,我们自定义LogUtil 类:
public class LogUtils {
public static final int VERBOSE = 1;
public static final int DEBUG = 2;
public static final int INFO = 3;
public static final int WARN = 4;
public static final int ERROR = 5;
public static final int NOTHING = 6;
public static int level = VERBOSE;
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);
}
}
}
当开发过程中遇到一些奇怪的bug,但又迟迟定位不出来,最好的解决方法就是调试了。
不用多说,调试的第一步肯定是添加断点,添加断点的方式也很多简单,只需要在响应的代码行左边点击一下就可以了。
如果想要取消这个断点,再次点击即可。
功能:
F7 跳入 看到方法往里走
F8 跳过 每按一次代码就会向下执行一行
F9 跨断点调试
等等。
推荐链接:https://blog.csdn.net/dd864140130/article/details/51560664
Android 中的定时任务一般有两种实现方式,一种是使用 Java API 里提供的 Timer 类(不太适用于需要长期在后台运行的定时任务),一种是使用 Android 的 Alarm 机制(具有唤醒 CPU 功能,可以保证大多数情况下执行定时任务时 CPU 能正常工作)。
Alarm 机制的用法不复杂,主要是借助 AlarmManager 类来实现的。比如想要设定一个任务在 10 秒钟后执行,可写成:
// 获取 AlarmManager 的实例
AlarmManager manager = (AlarmManager) getSystemService(Context.ALARM_SERVICE);
// 设置触发时间
// SystemClock.elapsedRealtime() 获取系统开机至今所经历时间的毫秒数
// SystemClock.currentTimeMillis() 获取1970年1月1日0点至今所经历时间的毫秒数
long triggerAtTime = SystemClock.elapsedRealtime() + 10 * 1000;
// 3个参数:指定 AlarmManager 的工作类型、定时任务的触发时间、PendingIntent
// 其中AlarmManager 的工作类型有四种:
// ELAPSED_REALTIME 定时任务的触发时间从系统开机开始时算起,不会唤醒 CPU
// ELAPSED_REALTIME_WAKEUP 系统开机开始时算起,会唤醒 CPU
// RTC 从1970年1月1日0点开始算起,不会唤醒 CPU
// RTC_WAKEUP 从1970年1月1日0点开始算起,会唤醒 CPU
manager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP,triggerAtTime,pandingIntent);
举个例子,实现一个长时间在后台定时运行的服务,首先新建一个普通的服务 LongRunningService,将触发定时任务的代码写到 onStartCommand() 方法中,如下:
public class LongRunningService extends Service {
public LongRunningService() {
}
@Override
public IBinder onBind(Intent intent) {
throw new UnsupportedOperationException("Not yet implemented");
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
new Thread(new Runnable() {
@Override
public void run() {
// 在这里执行具体的逻辑操作
}
}).start();
AlarmManager manager = (AlarmManager) getSystemService(ALARM_SERVICE);
int anHour = 60 * 60 * 1000;//1小时的毫秒数
long triggerAtTime = SystemClock.elapsedRealtime() + anHour;
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(context,LongRunningService.class);
context.startService(intent);
值得注意的是,从 Android 4.4开始,由于系统在耗电方面的优化,Alarm 任务的触发时间变得不准确,可能会延迟一段时间后再执行。当然,使用 AlarmManager 的 setExact() 方法来替代 set() 方法,基本上可以保证任务准时执行。
在 Android 6.0中,谷歌加入了一个全新的 Doze 模式,可以极大幅度地延长电池的使用寿命。下面就来了解下这个模式,掌握一些编程时注意事项。
在 6.0 及以上系统的设备上,若未插接电源,处于静止状态(7.0中删除了这一条件),且屏幕关闭了一段时间之后,就会进入到 Doze 模式。在 Doze 模式下,系统会对 CPU、网络、Alarm 等活动进行限制,从而延长电池的使用寿命。
当然,系统不会一直处于 Doze 模式,而是会间歇性的退出一小段时间,在这段时间应用可以去完成它们的同步操作、Alarm 任务等,其工作过程如下:
Doze 模式的工作过程
Doze 模式下受限的功能有:
(1)网络访问被禁止
(2)系统忽略唤醒CPU或屏幕操作
(3)系统不再执行WIFI扫描
(4)系统不再执行同步任务
(5)Alarm 任务将会在下次退出 Doze 模式时执行
特殊需求,要 Alarm 任务在 Doze 模式下也必须正常执行,则可以调用 AlarmManager 的 setAndAllowWhileIdle() 或 setExactAndAllowWhileIdle() 方法。
Android 7.0 系统中引入了一个非常有特色的功能——多窗口模式,它允许我们在同一个屏幕中打开两个应用程序。
在 OverView 列表界面长按任意一个活动的标题,将该活动拖到屏幕突出显示的区域,则可以进入多窗口模式。
打开任意一个程序,长按 OverView 按钮,也可以进入多窗口模式。
这一块是比较重要的。
多窗口模式并不会改变活动原有的生命周期,只是会将用户最近交互过的那个活动设置为运行状态,而将多窗口模式下另外一个可见活动设置为暂停状态。若这时用户又去和暂停的活动进行交互,那么该活动就变成运行状态,之前处于运行状态的活动变成暂停状态。
进入多窗口模式时活动会被重新创建,若要改变这一默认行为,可以在 AndroidManifest.xml 中对活动添加如下配置:
android:configChanges="orientation|keyboardHidden|screenSize|screenLayout"
添加这行配置后,不管是进入多窗口模式还是横竖屏切换,活动都不会被重新创建,而是会将屏幕发生变化的事件通知到 Activity 的 onConfigurationChanged() 方法中。因此,若要在屏幕发生变化时进行相应的逻辑处理,那么在活动中重写 onConfigurationChanged() 方法即可。
在 AndroidManifest.xml 中,只要在Activity 或者 application中添加 android:resizeableActivity="false" 即可。
Lambda表达式本质上是一种一名方法,它既没有方法名,也没有访问修饰符和返回值类型,使用它来编写代码将会更加简介,也更加易读。
Android Studio 2.1.1之前,需要自己导入插件
dependencies {
classpath 'me.tatarka:gradle-retrolambda:3.2.0'
}
在 Module 的 build.gradle 中添加如下代码
// 应用插件
apply plugin: 'me.tatarka.retrolambda'
// 支持Java8
android {
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
}
Android Studio 2.1.1之后(支持lambda插件)
android {
defaultConfig {
jackOptions {
// 打开jack编译器
enabled true
}
}
// 编译支持Java8
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
}
相关教程:https://blog.csdn.net/ioriogami/article/details/12782141/
在Android中使用lambda表达式:https://www.jianshu.com/p/ef6cbf5ade71
郭霖总结:
整整13章的内容你已经学完了!本书的所有知识点也到此结束,是不是感觉有些激动呢,下面就让我们来回顾和总结一下这就么久以来学过的所有东西吧。
这13章的内容不算很多,但却已经把Adroid中绝大部分比较重要的知识点都覆盖到了。我们从搭建开发环境开始学起,后面逐步学习了四大组件、UI、碎片、数据存储、多媒体、网络、定位服务、Material Design等内容,本章中又学习了如全局获取Context.定制日志工具、调试程序、多窗口模式编程、Lambda表达式等高级技巧,相信你已经从一名初学者蜕变成为Android开发好手了。
不过,虽然你已经储备了足够多的知识,并掌握了很多的最佳实践技巧,但是你还从来没有真正开发过一一 个完整的项目,也许在将所有学到的知识混合到-一起使用的时候,你会感到有些手足无措。因此,前进的脚步仍然不能停下,下一章中我们会结合前面章节所学的内容,-一起开发-一个天气预报程序。锻炼的机会可千万不能错过,赶快进人到下一章吧。
我的总结:
本专栏到此结束,下一步将阅读Andorid 开发艺术探索,希望各位,届时给个赞,关注我一下,让我可以更努力去学习,然后分享给大家。
第一行代码最后两章分别是 酷派天气 和 上架App的内容,对此就不作为相关知识点来记录了。以下给出我多个章节的代码链接,如有需要,欢迎下载。
多章代码链接:https://download.csdn.net/download/lhk147852369/10812367