目录:
知识点:
13.1 全局获取 Context的技巧
当应用程序的架构逐渐开始复杂起来的时候,很多的逻辑代码都将脱离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 文件的<application>标签下进行指定就可以了,如下:
<application
android:name=".MyApplication"
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:supportsRtl="true"
android:theme="@style/AppTheme">
. . .
</application>
这样就实现了一种全局获取 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;
}
}
13.2 使用 Intent传递对象
使用 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。
13.2.1 Serializable 方式
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<Person> CREATOR = new Creator<Person>() {
@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 传递对象了。
13.2.2 Parcelable 方式
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<Person> CREATOR = new Creator<Person>() {
@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 传递对象的功能。
13.3 定制自己的日志工具
虽然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);
}
}
}
13.4 调试Android 程序
当开发过程中遇到一些奇怪的bug,但又迟迟定位不出来,最好的解决方法就是调试了。
不用多说,调试的第一步肯定是添加断点,添加断点的方式也很多简单,只需要在响应的代码行左边点击一下就可以了。
如果想要取消这个断点,再次点击即可。
功能:
F7 跳入 看到方法往里走
F8 跳过 每按一次代码就会向下执行一行
F9 跨断点调试
等等。
推荐链接:https://blog.csdn.net/dd864140130/article/details/51560664
13.5 创建定时任务
Android 中的定时任务一般有两种实现方式,一种是使用 Java API 里提供的 Timer 类(不太适用于需要长期在后台运行的定时任务),一种是使用 Android 的 Alarm 机制(具有唤醒 CPU 功能,可以保证大多数情况下执行定时任务时 CPU 能正常工作)。
13.5.1 Alarm 机制
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() 方法,基本上可以保证任务准时执行。
13.5.2 Doze模式
在 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() 方法。
13.6 多窗口模式编程
Android 7.0 系统中引入了一个非常有特色的功能——多窗口模式,它允许我们在同一个屏幕中打开两个应用程序。
13.6.1 进入多窗口模式
在 OverView 列表界面长按任意一个活动的标题,将该活动拖到屏幕突出显示的区域,则可以进入多窗口模式。
打开任意一个程序,长按 OverView 按钮,也可以进入多窗口模式。
13.6.2 多窗口模式下的生命周期
这一块是比较重要的。
多窗口模式并不会改变活动原有的生命周期,只是会将用户最近交互过的那个活动设置为运行状态,而将多窗口模式下另外一个可见活动设置为暂停状态。若这时用户又去和暂停的活动进行交互,那么该活动就变成运行状态,之前处于运行状态的活动变成暂停状态。
进入多窗口模式时活动会被重新创建,若要改变这一默认行为,可以在 AndroidManifest.xml 中对活动添加如下配置:
android:configChanges="orientation|keyboardHidden|screenSize|screenLayout"
添加这行配置后,不管是进入多窗口模式还是横竖屏切换,活动都不会被重新创建,而是会将屏幕发生变化的事件通知到 Activity 的 onConfigurationChanged() 方法中。因此,若要在屏幕发生变化时进行相应的逻辑处理,那么在活动中重写 onConfigurationChanged() 方法即可。
13.6.3 禁用多窗口模式
在 AndroidManifest.xml 中,只要在Activity 或者 application中添加 android:resizeableActivity="false" 即可。
13.7 Lambda 表达式
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.8 总结
郭霖总结:
整整13章的内容你已经学完了!本书的所有知识点也到此结束,是不是感觉有些激动呢,下面就让我们来回顾和总结一下这就么久以来学过的所有东西吧。
这13章的内容不算很多,但却已经把Adroid中绝大部分比较重要的知识点都覆盖到了。我们从搭建开发环境开始学起,后面逐步学习了四大组件、UI、碎片、数据存储、多媒体、网络、定位服务、Material Design等内容,本章中又学习了如全局获取Context.定制日志工具、调试程序、多窗口模式编程、Lambda表达式等高级技巧,相信你已经从一名初学者蜕变成为Android开发好手了。
不过,虽然你已经储备了足够多的知识,并掌握了很多的最佳实践技巧,但是你还从来没有真正开发过一一 个完整的项目,也许在将所有学到的知识混合到-一起使用的时候,你会感到有些手足无措。因此,前进的脚步仍然不能停下,下一章中我们会结合前面章节所学的内容,-一起开发-一个天气预报程序。锻炼的机会可千万不能错过,赶快进人到下一章吧。
我的总结:
本专栏到此结束,下一步将阅读Andorid 开发艺术探索,希望各位,届时给个赞,关注我一下,让我可以更努力去学习,然后分享给大家。
第一行代码最后两章分别是 酷派天气 和 上架App的内容,对此就不作为相关知识点来记录了。以下给出我多个章节的代码链接,如有需要,欢迎下载。
多章代码链接:https://download.csdn.net/download/lhk147852369/10812367