Android高级技巧
获取全局Context的技巧
Context类的结构
Context类结构
不难看出Context
一共有三种类型,分别是Application
、Activity
和Service
。这三个类虽然分别各种承担着不同的作用,但它们都属于Context
的一种,而它们具体Context的功能则是由ContextImpl
类去实现的.由于Context
的具体能力是由ContextImpl
类去实现的,因此在绝大多数场景下,Activity
、Service
和Application
这三种类型的Context
都是可以通用的。不过有几种场景比较特殊,比如启动Activity
,还有弹出Dialog
。出于安全原因的考虑,Android
是不允许Activity
或Dialog
凭空出现的,一个Activity
的启动必须要建立在另一个Activity
的基础之上,也就是以此形成的返回栈。而Dialog
则必须在一个Activity
上面弹出(除非是System Alert
类型的Dialog
),因此在这种场景下,我们只能使用Activity
类型的Context
,否则将会出错。
public class MyApplication extends Application {
public static Context mContext;
@Override
public void onCreate() {
super.onCreate();
mContext=getApplicationContext();
}
public static Context getContext(){
return mContext;
}
}
然后再配置一下Android,Manifest.xml
文件
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="org.crazyit.myapptest">
<application
android:name=".MyUtils.MyApplication"
......
</application>
</manifest>
想在项目的哪个地方使用Context
,只需要调用MyApplication.getContext()
即可
注意:如果像LitePal
这样的开源框架,因为LitePal
也需要在AndroidManifest.xml
中<application>
声明android:name
因为只有这样声明之后LitePal
才会在内部自动获取到Context
,这样便和我们自己声明的MyApplication
产生冲突了,因为任何一个项目只能有一个Application
.这种情况的解决方案是,在我们自己的MyApplication
中调用LitePal
的初始化方法就可以了.
public class MyApplication extends Application {
public static Context mContext;
@Override
public void onCreate() {
super.onCreate();
mContext=getApplicationContext();
//LitePal的处理办法
LitePal.initialize(mContext);
}
public static Context getContext(){
return mContext;
}
}
使用Intent传递对象
使用Intent传递对象Intent
可以用来启动活动,发送广播,启动服务等,我们还可以在进行上述操作的时候进行传递数据,进行通信.
使用Intent
来传递对象的技巧Serializable
/Parcelable
Serializable方式
Serializable
是序列化的意思,表示将一个对象转换成可存储或可传输的状态.序列化后的对象可以在网络上进行传输,也可以存储到本地.序列化的方法很简单,只需要让一个类去实现Serializable
这个接口就可以了.
public class Person implements Serializable {
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;
}
}
FirstActivity中代码
Person person=new Person();
person.setAge(88);
person.setName("张针");
Intent intent=new Intent(this,SerializableTestActivity.class);
intent.putExtra("person_value",person);
startActivity(intent);
SecondActivity中的代码
Person person= (Person) getIntent().getSerializableExtra("person_value");
mTvText.setText(person.getName());
mTvText1.setText("年龄:"+person.getAge());
Parcelable方式
不同于将对象进行序列化,Parcelable
方式的实现原理是将一个完整的对象进行分解,而分解后的每一部分都是Intent
所支持的数据类型,这样也就实现了传递对象的功能了.
public class Boy implements Parcelable {
private int age;
private String name;
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
//写入的顺序要和读取的顺序相同
//比如先写入的name 读取的时候就要先读取name
dest.writeString(name);
dest.writeInt(age);
}
//创建Parcelable.Creator接口的一个实现,并将泛型指定为Boy
public static final Parcelable.Creator<Boy> CREATOR=new Parcelable.Creator<Boy>(){
@Override
public Boy createFromParcel(Parcel source) {
Boy boy=new Boy();
//注意这里的读取顺序要和刚才写入的顺序完全相同
boy.name=source.readString();
boy.age=source.readInt();
return boy;
}
@Override
public Boy[] newArray(int size) {
return new Boy[size];
}
};
}
FirstActivity中的代码
Boy boy=new Boy();
boy.setAge(18);
boy.setName("徐冬磊");
Intent intent1=new Intent(this,thirdActivity.class);
intent1.putExtra("boy_value",boy);
startActivity(intent1);
ThirdActivity中的代码
Boy boy=getIntent().getParcelableExtra("boy_value");
mTvText.setText(boy.getName());
mTvText1.setText("年龄:"+boy.getAge());
Serializable
的方法较为简单,但是由于会把整个对象都序列化,因此效率会比Parcelable
方式低一些,通常情况下推介使用Parcelable
方式来实现Intent
传递对象的功能
定制自己的日志工具
定制自己的日志工具主要为了解决当我们编写一个庞大的项目的时候我们可能期间为了方便调试,在代码的很多地方都打印了大量的日志,最近项目基本完成了,但是有一个很头疼的问题,之前用于调试的那些日志,在正式上线之后仍然会照常打印,这样不仅会降低程序的运行效率,还可能将一些机密的数据泄露出去.最理想的情况是能够自由控制日志的打印,当程序处于开发阶段时就让日志打印出来,当程序上线了之后就把日志屏蔽掉.
public class LogUtil {
public static final int VERBOSE=0;
public static final int DEBUGE=1;
public static final int INFO=2;
public static final int WARN=3;
public static final int ERROR=4;
public static final int NOTHING=5;
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<=DEBUGE){
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);
}
}
}
只需要修改level
变量的值,就可以自由地控制日志的打印行为了.比如让level
等于VERBOSE
就可以将所有的日志都打印出来,让level
等于WARN
就可以只打印警告以上级别的日志,让level
等于NOTHING
就可以把所有日志都屏蔽掉.
解决刚开始我们提到的那个问题我们只需要在开发阶段将level
指定成VERBOSE
,当项目正式上线的时候将level
指定成NOTHING
就可以了
调试Android程序
调试Android程序设置断点,启动Debug调试程序
1.添加断点--------在相应的代码行的左边点击一下便可以了
若想取消这个断点-------对着它再点击一下就可以了
2.在Android studio顶部工具栏中的Debug按钮,就会使用调试模式来启动程序
3.接下来每按一次F8键,代码便会向下执行一行,并且通过Variables视图还可以看到内存中的数据
4.调试完成之后点击Debug窗口中的Stop按钮来结束调试即可
缺点:这种调试模式下,程序的运行效率大大降低,如果添加的断点在比较靠后的位置,需要执行很多的操作才能运行到这个断点,那么前面这些操作便会有卡顿的效果
Attach debugger to Android process 模式(推介使用这种模式)
正常方式启动程序,先将待调试的程序部分的准备工作做好(比如说我们如果要调试输入账号密码那么需要提前输入账号密码)然后点击Android Studio顶部的工具栏中的Attach debugger to Android process按钮,会弹出一个进程选择提示框,选中这个进程,然后点击OK按钮,就可以让这个进程进入到调试模式了.
接下来进入调试模式之后和上一种调试方法便一样了,Android Studio同样会自动打开Debug窗口,之后流程都是相同的了,第二种调试方式会比第一种更加灵活,也更常用.(第二种调试存在问题待解决......)
创建定时任务
创建定时任务JAVA
中的Timer
类
Timer
类有一个短板,它并不适合用于那些需要长期在后台运行的定时任务.我们知道为了让电池更加耐用,每种手机都会有自己的休眠策略,Android
手机就会在长时间不操作的情况下自动让CPU
进入睡眠状态,这就有可能导致Timer
的定时任务无法正常运行.
Alarm机制
Alarm
具有唤醒CPU
的功能,它可以保证大多数情况下需要执行定时任务的时候CPU
都能正常工作.需要注意的是唤醒CPU
和唤醒屏幕是两种不同的概念不要混淆.
Alarm
机制的用法:
1.借用AlarmManager
类来获取一个AlarmManager
的实例,这个类和NotificationManager
有点类似,都是通过Context
的getSystemService()
方法来获取实例的,只是这里需要传入的参数是Context.ALARM_SERVICE
.获取一个AlarmManager
的实例可以写成
AlarmManager manager=(AlarmManager)getSystemService(Context.ALARM_SERVICE);
2.接着调用AlarmManager
的set()
方法就可以设置一个定时任务了.
long triggerAtTime= SystemClock.elapsedRealtime()+10*1000;
manager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP,triggerAtTime,pendingIntent);
第一个方法中解释:
使用SystemClock.elapsedRealtime()
方法可以获取到系统开机至今所经历时间的毫秒数
使用System.currentTimeMillis()
方法可以获取到1970年1月1日0点至今所经历的时间的毫秒数
第二个方法的各个参数解释
第一个参数是整形参数用于指定AlarmManager
的工作类型,有四种值可选.
AlarmManager.ELAPSED_REALTIME
:让定时任务的触发时间从系统开机开始算起,但不会唤醒CPU
AlarmManager.ELAPSED_REALTIME_WAKEUP
:让定时任务的触发时间从系统开机开始算起,会唤醒CPU
AlarmManager.RTC
:让定时任务的触发时间从1970年1月1日开始算起,但是不会唤醒CPU
AlarmManager.RTC_WAKEUP
:让定时任务的触发时间从1970年1月1日开始算起,会唤醒CPU
第二个参数是定时任务的触发时间,以毫秒为单位.
第三个参数是一个PendingIntent
.一般我们用getService()
方法或者getBroadcast()
方法来获取一个能够执行广播或服务的PendingIntent
.
需要注意的是:从android4.4
系统开始,Alarm
任务的触发时间变得不是很准确,可能会延迟一段时间后任务才能得到执行,这并不是bug
而是系统在耗电性方面进行的优化.系统会自动检测目前有多少alarm
任务存在,然后将触发时间相近的几个任务放在一起执行,这就可以大幅度减少CPU
被唤醒的次数,从而有效延长电池的使用时间.
如果你要求alarm
任务的执行时间准确无误,android
仍然提供了解决方案,使用AlarmManager
的setExact()
方法来代替set()
方法,基本上就能保证任务能够准时执行了.
Doze模式
背景:虽然Android
的每个系统版本都在手机电量方面努力进行优化,不过一直没有能够解决后台服务泛滥,手机电量消耗过快的问题.在Android6.0
系统中谷歌加入了一个新的Doze
模式,从而可以极大幅度的延长手机电池的使用寿命.
Doze
模式:当用户的设备是Android6.0
或以上系统时,如果该设备未插接电源,处于静止状态(Android7.0
中删除了处于静止状态这一条件),且屏幕关闭了一段时间之后,就会进入到Doze
模式.在Doze
模式下,系统会对CPU
,网络,Alarm
等活动进行限制,从而延长了电池的使用寿命.
系统并不会一直处于Doze
模式,而是会间歇性退出Doze
模式一小段时间,在这段时间中,应用就可以去完成它们的同步操作,Alarm
任务等.
Doze模式解读
如果你对
Alarm
任务即使在Doze
模式下也必须正常运行,Android
还是提供了解决方案的,调用AlarmManager
的setAndAllowWhileIdle()
或setExactAndAllowWhileIdle()
方法让定时任务即使在Doze
模式下也能正常执行了.这两个方法之间的区别和set()
,setExact()
方法之间的区别是一样的.
多窗口模式编程
多窗口模式编程Android7.0
系统中引入了多窗口模式,它允许我们在同一个屏幕中同时打开两个应用程序.
在多窗口模式下,整个应用的界面会缩小很多,那么编写程序时就应该多考虑使用match_parent
属性,RecyclerView
,ListView
,ScrollView
等控件,来让应用的界面能够更好地适配各种不同尺寸的屏幕,尽量不要出现屏幕尺寸变化过大使界面无法正常显示的情况.
多窗口模式下的生命周期
多窗口模式并不会改变活动原有的生命周期,只是会将最近交互过的那个活动(即刚开启窗口的那个活动)设置为运行状态,而将多窗口模式下另一个可见的活动设置为暂停状态,这时用户又去和暂停的活动进行交互,那么该活动就编程运行状态,之前处于运行状态的活动编程暂停状态.
打开一个MaterialTest
项目首先onCreate onStart onResum
方法启动,然后进入多窗口模式后onPause onStop onDestory onCreate onStart onResum onPause
方法启动
进入多窗口模式后活动的大小发生了比较大的变化,此时默认是会重新创建活动的.除此之外,像横竖屏切换也是会重新创建活动的.进入多窗口模式后,MaterialTest
变成暂停状态.在Overview
界面选中LBSTest
程序,LBSTest
的onCreate onStart onResum
方法依次得到执行说明LBSTest
变成了运行状态.然后我们再操作一下MaterialTest
程序,发现LBSTest
的onPause
方法执行,MaterialTest
的onResum
方法得到了执行,说明LBSTest
变成了暂停状态,MaterialTest
变成了运行状态.
了解了多窗口模式的生命周期的作用:在多窗口模式下,用户仍然可以看到处于暂停状态下的应用,那么像视频播放之类的应用此时就应该能播放视频才对,我们最好不要在活动的onPause
方法中处理视频播放器的暂停逻辑,而是应该在onStop
方法中去处理,并且在onStart
方法中恢复视频的播放.
针对进入多窗口模式时程序会被重新创建,如果我们想改变这一行为,我们可以在AndroidManifest.xml
文件中对活动(在活动标签中配置)进行配置
android:configChanges="orientation|keyboardHidden|screenSize|screenLayout"
然后不管进入多窗口,还是横竖屏切换,活动都不会被重新创建,而是将屏幕发生变化的事件通知到Activity
的onConfigurationChanged()
方法当中.如果想在屏幕发生变化的时候进行相应的逻辑处理,那么在活动中重写onConfigurationChanged()
方法即可.
禁用多窗口模式
在androidManifest.xml
的<application>
或者<activity>
标签中加入
android:resizeableActivity=["true"|"false"]
其中true
表示支持多窗口模式false
表示不支持.(默认值是true
即支持多窗口模式)
但存在一个问题也就是低版本这个属性对低版本不支持,这个属性只有当项目的targetSdkVersion
指定成24
或者更高的时候才会有用,否则这个属性是无效的.针对这种情况Android提供了一种解决方案:如果项目指定的targetSDKVersion
低于24
,并且活动是不允许横竖屏切换的,那么应用也就不支持多窗口模式.
想让应用不允许横竖屏切换,需要在AndroidManifest.xml
文件中的<activity>
标签中加入如下配置:
android:screenOrientation=["portrait"|"landscape"]
portrait
表示活动只支持竖屏,landscape
表示活动只支持横屏
Lambda表达式
Lambda表达式Lanmbda
表达式本质是一种匿名方法,它没有方法名,也没有访问修饰符和返回值类型,使用它来编写代码将会变得简洁,也更加易读.
首先在app/build.gradle
中添加如下配置:
android{
defaultConfig{
android.compileOptions.sourceCompatibility 1.8
android.compileOptions.targetCompatibility 1.8}}
注意:第一行代码中写的那个已经过时了!!!!
但凡是这种只有一个待实现方法的接口都可以使用Lambda
表达式,
Lambda 当接口的待实现方法有且只有一个参数的时候,我们还可以进行另一步简化,将参数外面的括号去掉.
Lambda