Android高级技巧

Android高级技巧

获取全局Context的技巧

Context类的结构


Context类结构

不难看出Context一共有三种类型,分别是ApplicationActivityService。这三个类虽然分别各种承担着不同的作用,但它们都属于Context的一种,而它们具体Context的功能则是由ContextImpl类去实现的.由于Context的具体能力是由ContextImpl类去实现的,因此在绝大多数场景下,ActivityServiceApplication这三种类型的Context都是可以通用的。不过有几种场景比较特殊,比如启动Activity,还有弹出Dialog。出于安全原因的考虑,Android是不允许ActivityDialog凭空出现的,一个Activity的启动必须要建立在另一个Activity的基础之上,也就是以此形成的返回栈。而Dialog则必须在一个Activity上面弹出(除非是System Alert类型的Dialog),因此在这种场景下,我们只能使用Activity类型的Context,否则将会出错。

获取全局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有点类似,都是通过ContextgetSystemService()方法来获取实例的,只是这里需要传入的参数是Context.ALARM_SERVICE.获取一个AlarmManager的实例可以写成
AlarmManager manager=(AlarmManager)getSystemService(Context.ALARM_SERVICE);
2.接着调用AlarmManagerset()方法就可以设置一个定时任务了.

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仍然提供了解决方案,使用AlarmManagersetExact()方法来代替set()方法,基本上就能保证任务能够准时执行了.

Doze模式

背景:虽然Android的每个系统版本都在手机电量方面努力进行优化,不过一直没有能够解决后台服务泛滥,手机电量消耗过快的问题.在Android6.0系统中谷歌加入了一个新的Doze模式,从而可以极大幅度的延长手机电池的使用寿命.

Doze模式:当用户的设备是Android6.0或以上系统时,如果该设备未插接电源,处于静止状态(Android7.0中删除了处于静止状态这一条件),且屏幕关闭了一段时间之后,就会进入到Doze模式.在Doze模式下,系统会对CPU,网络,Alarm等活动进行限制,从而延长了电池的使用寿命.

系统并不会一直处于Doze模式,而是会间歇性退出Doze模式一小段时间,在这段时间中,应用就可以去完成它们的同步操作,Alarm任务等.

Doze模式

Doze模式解读

如果你对Alarm任务即使在Doze模式下也必须正常运行,Android还是提供了解决方案的,调用AlarmManagersetAndAllowWhileIdle()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程序,LBSTestonCreate onStart onResum方法依次得到执行说明LBSTest变成了运行状态.然后我们再操作一下MaterialTest程序,发现LBSTestonPause方法执行,MaterialTestonResum方法得到了执行,说明LBSTest变成了暂停状态,MaterialTest变成了运行状态.

了解了多窗口模式的生命周期的作用:在多窗口模式下,用户仍然可以看到处于暂停状态下的应用,那么像视频播放之类的应用此时就应该能播放视频才对,我们最好不要在活动的onPause方法中处理视频播放器的暂停逻辑,而是应该在onStop方法中去处理,并且在onStart方法中恢复视频的播放.

针对进入多窗口模式时程序会被重新创建,如果我们想改变这一行为,我们可以在AndroidManifest.xml文件中对活动(在活动标签中配置)进行配置
android:configChanges="orientation|keyboardHidden|screenSize|screenLayout"
然后不管进入多窗口,还是横竖屏切换,活动都不会被重新创建,而是将屏幕发生变化的事件通知到ActivityonConfigurationChanged()方法当中.如果想在屏幕发生变化的时候进行相应的逻辑处理,那么在活动中重写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
另外Java还可以根据上下文自动推断出Lambda表达式中的参数类型.
Lambda
当接口的待实现方法有且只有一个参数的时候,我们还可以进行另一步简化,将参数外面的括号去掉.
Lambda

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 157,012评论 4 359
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,589评论 1 290
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 106,819评论 0 237
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,652评论 0 202
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 51,954评论 3 285
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,381评论 1 210
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,687评论 2 310
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,404评论 0 194
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,082评论 1 238
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,355评论 2 241
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,880评论 1 255
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,249评论 2 250
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,864评论 3 232
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,007评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,760评论 0 192
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,394评论 2 269
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,281评论 2 259

推荐阅读更多精彩内容