获取全局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,否则将会出错。
我们可能有的时候会为获取不到Context而发愁,因为很多地方都会用到Context,比如说启动服务,启动活动,发送广播,操作数据库,使用通知,弹出Toast等等.当我们操作在activity中使因为活动本身就是一个Context类对象,所以我们不会发愁,但是当应用程序的架构逐渐开始复杂起来的时候,很多的逻辑代码都将脱离Activity类,而你又恰恰需要使用Context,也许这个时候你会感到有点伤脑筋了.
我们来学习一种技巧,让我们在项目的任何地方都能轻松获取到Context
Android提供了一个Application类,每当程序启动的时候,系统会自动将这个类进行初始化.我们可以定制一个自己的Application类以便于管理程序内一些全局的状态信息,比如全局Context.方法为:
1.创建一个MyApplication类来继承Application
2.重写父类的onCreate()方法,并通过调用getApplicationContext()方法得到了一个应用程序级别的Context
3.提供一个静态方法public static Context getContext()来返回刚才获取到的Context
4.告知系统,当程序启动的时候应该初始化MyApplication类,而不是默认的Application类.方法是-----在AndroidManifest.xml文件的<application>标签下进行指定就可以了.注意在指定MyApplication的时候一定要加上完整的包名,不然系统无法找到这个类.
5.想在项目的哪个地方使用Context,只需要调用MyApplication.getContext()就可以了.
注意:如果像LitePal这样的开源框架,因为LitePal也需要在AndroidManifest.xml中<application>声明android:name 因为只有这样声明之后LitePal才会在内部自动获取到Context,这样便和我们自己声明的MyApplication产生冲突了,因为任何一个项目只能有一个Application.这种情况的解决方案是,在我们自己的MyApplication中调用LitePal的初始化方法就可以了.
public void onCreate() {
super.onCreate();
mContext = getApplicationContext();
//LitePal的处理办法
//LitePal.initialize(mContext);
}
相当于我们把全局的Context对象通过参数传递给了LitePal,效果和在AndroidManifest.xml中配置LitePalApplication是一摸一样的.
使用Intent传递对象
Intent可以用来启动活动,发送广播,启动服务等,我们还可以在进行上述操作的时候进行传递数据,进行通信.
我们学习一下使用Intent来传递对象的技巧---Serializable/Parcelable
Serializable方式
Serializable是序列化的意思,表示将一个对象转换成可存储或可传输的状态.序列化后的对象可以在网络上进行传输,也可以存储到本地.序列化的方法很简单,只需要让一个类去实现Serializable这个接口就可以了.
Parcelable方式
不同于将对象进行序列化,Parcelable方式的实现原理是将一个完整的对象进行分解,而分解后的每一部分都是Intent所支持的数据类型,这样也就实现了传递对象的功能了.
下面那两个方法在as中很简单,选中Person然后alt+enter便出来了
定制自己的日志工具
主要为了解决当我们编写一个庞大的项目的时候我们可能期间为了方便调试,在代码的很多地方都打印了大量的日志,最近项目基本完成了,但是有一个很头疼的问题,之前用于调试的那些日志,在正式上线之后仍然会照常打印,这样不仅会降低程序的运行效率,还可能将一些机密的数据泄露出去.最理想的情况是能够自由控制日志的打印,当程序处于开发阶段时就让日志打印出来,当程序上线了之后就把日志屏蔽掉.
只需要修改level变量的值,就可以自由地控制日志的打印行为了.比如让level等于VERBOSE就可以将所有的日志都打印出来,让level等于WARN就可以只打印警告以上级别的日志,让level等于NOTHING就可以把所有日志都屏蔽掉.
解决刚开始我们提到的那个问题我们只需要在开发阶段将level指定成VERBOSE,当项目正式上线的时候将level指定成NOTHING就可以了
调试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任务等.
如果你对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表达式
Lanmbda表达式本质是一种匿名方法,它没有方法名,也没有访问修饰符和返回值类型,使用它来编写代码将会变得简洁,也更加易读.
首先在app/build.gradle中添加如下配置:
android{
defaultConfig{
android.compileOptions.sourceCompatibility 1.8
android.compileOptions.targetCompatibility 1.8}}
注意:第一行代码中写的那个已经过时了!!!!
但凡是这种只有一个待实现方法的接口都可以使用Lambda表达式,
另外Java还可以根据上下文自动推断出Lambda表达式中的参数类型.
当接口的待实现方法有且只有一个参数的时候,我们还可以进行另一步简化,将参数外面的括号去掉.