《第一行代码》总结之进阶高级技巧(八)

                                                   第十三章—继续进阶,你还应该掌握的高级技巧
       本章主要学习了如何在全局获取Context,使用Intent来传递复杂的数据对象(有Serialiazable和Parcelable两种方式),定制自己的调试工具(可以选择显示或者不显示)、调试安卓程序(Attach debugger的用法)、创建定时任务(Alarm的用法与Doze模式)、多窗口模式编程因为没有接触过应采用场景,所以大致看了下理论、Lambda应用于Jdk1.8上,追求极简主义的可以使用它。至此,我们已经学习了环境搭建、四大组件、UI、碎片、数据存储、多媒体、网络、定位服务、Material Design等等。下章将以此为基础开发一个真实的APP。
13.1全局获取Context的技巧
(1)如何获取到Context对象?
         解决方法:在方法体的参数列表中增加final Context context,但存在推卸责任的问题。
         我们的方法:(1)定制自己的Applicatoin类,用于管理程序内部的状态信息;(2)告知系统启动时是初始化自定义MyApplication类,而非Application;具体做法是在AndroidManifest.xml中添加完整包名类名。(3)如果在哪里需要使用Context,则使用MyApplication.getContext来实现。

《第一行代码》总结之进阶高级技巧(八)_第1张图片

《第一行代码》总结之进阶高级技巧(八)_第2张图片

(2)Context是什么?
        Context是个抽象类,通过类的结构可以看到:Activity、Service、Application都是Context的子类;如果从Android系统的角度来理解:Context是一个场景,描述的是一个应用程序环境的信息,即上下文,代表与操作系统的交互的一种过程。如果从程序的角度上来理解:Context是个抽象类,而Activity、Service、Application等都是该类的一个实现。有一个公式:

总Context实例个数 = Service个数 + Activity个数 + 1(Application对应的Context对象) 

(3)如何获取?
       通常我们想要获取Context对象,主要有以下四种方法 
       1:View.getContext,返回当前View对象的Context对象,通常是当前正在展示的Activity对象。 
       2:Activity.getApplicationContext,获取当前Activity所在的(应用)进程的Context对象,通常我们使用Context对象时,要优先考虑这个全局的进程Context。 
       3:ContextWrapper.getBaseContext():用来获取一个ContextWrapper进行装饰之前的Context,可以使用这个方法,这个方法在实际开发中使用并不多,也不建议使用。 
       4:Activity.this 返回当前的Activity实例,如果是UI控件需要使用Activity作为Context对象,但是默认的Toast实际上使用ApplicationContext也可以。

13.2使用Intent传递对象
       使用Intent我们之前是使用putExtra方法来实现传递对象,但其所支持的类型是有限的,当自定义数据结构时,往往不能实现数据的传递。我们可以使用以下两种方式:Serializable和Parcelable来实现。
(1)Serializable方式
      序列化,将对象转换为可存储可传输的状态,如何序列化,继承Serializable接口。示例:

      1.建立Person类实现Serializable接口。

public class Person implements Serializable {
    //让Person类去实现Serializable,进而Person对象就是可序列化的了。
    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;
    }
}

         2.在MainActivy中构建Intent对象。

Person person = new Person();
person.setName("kevin");
person.setAge(12);
Intent intent = new Intent(MainActivity.this,Main2Activity.class);
intent.putExtra("person_data",person);
startActivity(intent);

          3.在Main2Activity中接受对象。

protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main2);
        Person person = (Person) getIntent().getSerializableExtra("person_data");
        Log.i(TAG, person.getName());
        Log.i(TAG, String.valueOf(person.getAge()));
}

(2)Parcelable方式
       1.首先继承并实现Parcelable接口,重写describeContents和writeToParcel两个方法,这是writeToParcel调用Parcel的writeXX方法,将字段一一写出。此外,我们必须要在Person类中提供一个CREATOR的变量,泛型指定为Person,重写createFromPacel和newArray两个方法,createFromPacel为读取刚才写的name和age字段,并创建PEerson对象进行返回。读取顺序与写入顺序要完全相同。而new Array则只需要返回数组大小即可。

public class Person implements Parcelable {
    //让Person类去实现Serializable,进而Person对象就是可序列化的了。
    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;
    }

    @Override
    public int describeContents() {
        return 0;
    }

    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeString(name);//写出name
        dest.writeInt(age);//写出Age
    }

    public static final Parcelable.Creator CREATOR = new Parcelable.Creator(){

        @Override
        public Person createFromParcel(Parcel source) {
            Person person = new Person();
            person.name = source.readString();
            person.age = source.readInt();
            return person;
        }

        @Override
        public Person[] newArray(int size) {
            return new Person[size];
        }
    };
}

          2.在ManiActivy中获取刚才传递的Intent中的数据。

protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main2);
        Person person = (Person) getIntent().getParcelableExtra("person_data");
        Log.i(TAG, person.getName());
        Log.i(TAG, String.valueOf(person.getAge()));
}

13.3定制自己的日志工具
         应用场景:调试时打印了较多日志,现在上线了要把日志删除掉,如果不删,效率降低,安全性降低。一行一行删除,费时费力,而且以后可能还会在用的到。达到效果:开发阶段将日志打印出来,程序上线之后将日志屏蔽掉。
       (1)定义留个整形变量,对应调试等级。定义静态变量level,将它指定为六个中的一个。定义五个自定义日志方法,加入判断条件,只有当level小于等于对应日志级别时才会被打印出来。 

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);
        }
}

       (2)使用LogUtil来进行日志的输出。在这里我们只需要修改level的值,来自由控制日志输出行为。让level等于VERBOSE可以将所有日志输出,当等于NOthing所有都会被屏蔽掉。

LogUtil.i(TAG, person.getName());

         可以,蛮有意思的。
13.4调试安卓程序:
        调试可以逐行执行代码,并适时观察内存内的数据,轻易查出问题。
        1.下断点;2.F8下一步走;
        改进方法:动态调试按钮也没啥太大区别,只是选择进程进行调试,能更为灵活、随机进入调试状态。更快更方便。
       此处需要提到:20个常用的AS快捷键:(参考链接:https://blog.csdn.net/xuaho0907/article/details/72026869)

1.格式化代码:Ctrl+Alt+L
2.自动导入包路径:Ctrl+Alt+O
3.重命名文件、类名、变量名:Shift+F6
4.撤销操作:Ctrl+Z
5.反撤销操作:Ctrl+Shift+Z
6.全局搜索文件:双击Shift
7.查找:Ctrl+F
8.双击类名、变量、方法选中,查看调用的地方:Alt+F7
9.强制提示代码:Ctrl+Alt+空格
10.按关键字全局搜索:Ctrl+Shift+F
11.代码自动修正,鼠标点中出错的代码:Alt+Enter
12.在类中查看继承:Ctrl+O
13. Shift + Enter任意位置换行(往下添加空行
14. Alt + /代码提示
15.Ctrl + Y 删除当前行
16. Ctrl + D粘贴当前行到下一行​
17. Shift + Alt + Up/Down当前行、选中行向上/向下移动 
18. Ctrl+Q:把光标移至方法处,按此组合键可快速查看方法的说明文档
19.Alt + Left/Right 切换代码视图
20. Ctrl + Enter在当前行的上一行插入新行,光标在行首时有效

13.4创建定时任务:
       Android中目前有两种实现定时任务的方式,一个是Java API中的Timer类;另一个是Android的Alarm机制。Timer不适合用于长期在后台运行的定时任务。因为Android手机在长时间不操作时会将CPU转入睡眠状态。Alarm具有唤醒CPU功能。可以执行定时任务时CPU可以正常工作。
      (1)Alarm机制
     1.借助于AlarmManager来实现。获取相应的实例;
     2.利用AlarmManager的set方法来设置定时任务,第一个参数是整形参数,用于指定工作类型,有分唤醒CPU与否、任务触发时间之分,共四种类型。第二个参数任务触发时间,这里有开机时间+延迟执行时间。第三个参数,pendingIntent。设定完触发时间。
     3.使用PendingIntent指定处理定时任务的服务,使用在onStartCommand中开启一个子线程,在这里处理耗时逻辑。
     4.在MAinActivty启动定时服务。正常启动Service的写法。

public class LongRunningService extends Service {
    public LongRunningService() {
    }

    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        new Thread(new Runnable() {
            @Override
            public void run() {
                Log.i("testing", "run: ");
            }
        }).start();
        AlarmManager  manager = (AlarmManager) getSystemService(ALARM_SERVICE);
        int minute = 5 *1000;//定时时间为5s
        long triggerAtTime = SystemClock.elapsedRealtime()+minute;
        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(MainActivity.this,LongRunningService.class);
startService(intent);

            若Alarm执行时间必须准确无误,则将set替换为setExact即可。
(2)Doze模式。
         Android6.0之后加入,未插电源、处于静止状态、屏幕关闭一段时间。自动进入Doze模式。此时,系统对CPU、网络、Alarm活动进行限制。延长电池寿命。系统不会一直处于Doze模式下,会间歇性的退出Doze模式去处理同步操作、Alarm任务等。工作过程如下图所示:

《第一行代码》总结之进阶高级技巧(八)_第3张图片

《第一行代码》总结之进阶高级技巧(八)_第4张图片

       此时,Alarm任务不准时了,要使得其在Doze模式下也能正常运行,则调用AlarmManager中的setAndAllowwhileIdle()和setExtractAndAllowWhileIdle方法。

13.6多窗口模式编程。
       Addroid7.0之后引入,作者从如何进入多窗口模式、多窗口模式下的生命周期、禁用多窗口模式(若SDKVersion大于24,则在Application属性中指定android.resizeableActivity为false,若小于,则在xml中的MainActivity中设置 android:screenOrientiation=”portrait”(设置只能为竖屏,Android规定,若SDK小于24,且活动不允许横竖屏切换,则不支持多窗口模式))
       因为我没见过类似的功能,这块就大致看了下,不难,等以后有了相似的需求再看吧。
13.7 Lambda表达式
       Java8引入了LAmbda、Stream API、接口默认实现等。因为后两者都必须在7.0以上,前者可以兼容至2.3,因此介绍Lambda。
      1.配置Java8,在app中的build.gradle中:

defaultConfig {
        ....
        versionName "1.0"
        jackOptions.enabled = true
    }
    compileOptions{
        sourceCompatibility org.gradle.api.JavaVersion.VERSION_1_8
        targetCompatibility org.gradle.api.JavaVersion.VERSION_1_8
}  

        2.new Thread可以这样去写。精简许多。若接口只有一种待实现方法,则可以使用Lanbda表达式的写法。

new Thread(()->{
            
        }).start();

       3.button的响应事件缩写。

btn_click.setOnClickListener(v -> {
            Intent intent = new Intent(MainActivity.this, LongRunningService.class);
            startService(intent);
        });

      说实话,没多大用处,只是缩写。

你可能感兴趣的:(安卓开发,第一行代码,安卓进阶技巧,定时任务,全局获取Context)