《第一行代码》阅读笔记(三十五))——进阶开发

全局获取Context

《第一行代码》阅读笔记(三十五))——进阶开发_第1张图片

不难看出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,否则将会出错。

第一步:自定义Application

public class MyApplication extends Application {

    private static Context mContext;

    @Override
    public void onCreate() {
        super.onCreate();
        mContext = getApplicationContext();
    }

    public static Context getContext() {
        return mContext;
    }
}

首先就是重写父类的onCreate(),然后获得一个应用程序级别的context,并赋值给成员变量。再编写一个getContext()函数,返回成员变量的值,就是刚刚获取的应用程序级别的context。

第二步:在Manifest.xml中注册

 

之后在任何地方需要使用Context,只需要使用MyApplication.getContext();即可获得。

注意:因为一个程序只能有一个Application,所以这种全局获得Context的方式和之前使用的Litepal发生了冲突。

《第一行代码》阅读笔记(三十五))——进阶开发_第2张图片

如何处理呢?
只需要添加下面这句话即可:LitePal.initialize(mContext);

    @Override
    public void onCreate() {
        super.onCreate();
        mContext = getApplicationContext();
        LitePal.initialize(mContext);
    }

使用Intent传递对象

方法一:Serializable

首先需要让对象实现Serializable接口。
然后使用intent.putExtra()进行发送。
最后使用 (强制转化)getIntent().getSerializableExtra(“xxx”);的方式进行接收。

方法二:Parcelable

public class Person implements Parcelable {

    private String name;
    private int age;

    
    /*
    直接返回0就可以了
     */
    @Override
    public int describeContents() {
        return 0;
    }

    /*
    需要调用Parcel中的readXXX方法将成员变量一一写出
     */
    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeString(name);//写出name
        dest.writeInt(age);//写出age
    }
    

    public static final Creator CREATOR = new Creator() {
        @Override
        public Person createFromParcel(Parcel in) {
            //创建一个新的Person类,将成员变量一一读取,并返回
            Person person = new Person();
            person.name = in.readString();//读取name
            person.age = in.readInt();//读取age
            return person;
        }

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

}

同样首先需要让对象实现Parcelable接口,只不过相较于Serializable接口更加复杂,需要实现几个方法。这是因为Parcelable实现传递对象的方法是将对象拆分,一一传递。
发送方法相同。
最后进行接收的方式也由getSerializableExtra变成getParcelableExtra。

其中Serializable比较简单,但是需要将整个实体类序列化,而Parcelable不需要,虽然比较复杂,但是效率比实现Serializable接口高。

定制日志工具

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 ASSERT = 6;
    public static final int NOTHING = 7;
    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.v(tag, msg);
        }
    }
    
    public static void i(String tag, String msg) {
        if (level <= INFO) {
            Log.v(tag, msg);
        }
    }
    
    public static void w(String tag, String msg) {
        if (level <= WARN) {
            Log.v(tag, msg);
        }
    }
    
    public static void e(String tag, String msg) {
        if (level <= ERROR) {
            Log.v(tag, msg);
        }
    }
}

非常简单的一个封装,在需要打印时可以调用自定义日志工具的相应函数。在不需要打印的时候,可以调高level等级,这样就不会打印了。

其实自定义日志工具,还可以进行很多个性化的操作。这里就赘述了,推荐大家一个好用的日志工具——Timber。

调试

没什么特殊的,看书跟着操作就OK了

定时任务

主要使用Alarm机制,通过AlarmManager类来实现。

先获得AlarmManager类实例:

AlarmManager manager = (AlarmManager)getSystemService(Context.ALARM_SERVISE);

接下来调用AlarmManager的set()方法就可以设置一个定时任务 了,比如说想要设定一个任务在10秒钟后执行,就可以写成:

long triggerAtTime = SystemClock.elapsedRealtime() + 10 * 1000;
manager.set (AlarmManager.ELAPSED_ REALTIME_WAKEUP, triggerAtTime, pendingIntent);

set()方法中需要传入的3个参数。
第一个参数是一个整型参数, 用于指定AlarmManager的工作类型,有4种值可选,分别是ELAPSED_REALTIME、ELAPSED_REALTIME_WAKEUP、RTC和RTC_WAKEUP。

其中ELAPSED_REALTIME表示让定时任务的触发时间从系统开机开始算起,但不会唤醒CPU。ELAPSED_REALTIME_WAKEUP 同样表示让定时任务的触发时间从系统开机开始算起,但会唤醒CPU。RTC表示让定时任务的触发时间从1970年1月1日0点开始算起,但不会唤醒CPU。RTC_ WAKEUP同样表示让定时任务的触发时间从1970年1月1日0点开始算起,但会唤醒CPU。

使用SystemClock.elapsedRealtime ( )方法可以获取到系统开机至今所经历时间的毫秒数,使用System.currentTimeMillis()方法可以获取到1970年1月1日0点至今所经历时间的毫秒数。

然后看一下第二个参数,这个参数就好理解多了,就是定时任务触发的时间,以毫秒为单位。如果第一个参数使用的是ELAPSED_REALTIME 或ELAPSED_ REALTIME_WAKEUP, 则这里传入开机至今的时间再加上延迟执行的时间。如果第一个参数使用的是RTC或RTC__WAKEUP,则这里传入1970年1月1日0点至今的时间再加上延迟执行的时间。
第三个参数是一个PendingIntent,这里我们一般会调用getService()方法或者getBroadcast()方法来获取一个能够执行服务或广播的PendingIntent。这样当定时任务被触发的时候,服务的onStartCommand()方法或广播接收器的onReceive( )方法就可以得到执行。

如果我们要实现一个长时间后台定时运行的服务,我们需要新建一个普通的服务

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

    @Override
    public IBinder onBind(Intent intent) {
        // TODO: Return the communication channel to the service.
        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;
        long triggerAtTime = SystemClock.elapsedRealtime() + anHour;
        Intent i = new Intent(this, LongTimeService.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);

书中还介绍了很多方法,例如Android4.4之后如果想要准确无误的获取时间,而不被保护电池而睡眠CPU,就需要使用setExact()代替set()。在Android7.0之后Doze()模式下,需要调用AlarmManager的setAndAllowWhileIdle()或setExactAndAllowWhileIdle()方法让定时任务即使在Doze模式下也能正常执行。这两个方法之间的区别和set()、setExact()方法之间的区别是一样的。

多窗口模式编程

多窗口模式下的生命周期

多窗口模式并不会改变活动原有的生命周期,只是会将最近交互过的那个活动(即刚开启窗口的那个活动)设置为运行状态,而将多窗口模式下另一个可见的活动设置为暂停状态,这时用户又去和暂停的活动进行交互,那么该活动就编程运行状态,之前处于运行状态的活动编程暂停状态。
————————————————————————————————————————————————
作者:小徐andorid
链接:https://www.jianshu.com/p/c47ea055f604
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

打开一个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的或者标签中加入
android:resizeableActivity=[“true”|“false”]
其中true表示支持多窗口模式false表示不支持.(默认值是true即支持多窗口模式)

但存在一个问题也就是低版本这个属性对低版本不支持,这个属性只有当项目的targetSdkVersion指定成24或者更高的时候才会有用,否则这个属性是无效的.针对这种情况Android提供了一种解决方案:如果项目指定的targetSDKVersion低于24,并且活动是不允许横竖屏切换的,那么应用也就不支持多窗口模式.

想让应用不允许横竖屏切换,需要在AndroidManifest.xml文件中的标签中加入如下配置:
android:screenOrientation=[“portrait”|“landscape”]
portrait表示活动只支持竖屏,landscape表示活动只支持横屏

Lambda表达式

有兴趣可以了解,能看明白就行,其实目前来看使用的还是比较少的。

你可能感兴趣的:(《第一行代码》阅读笔记)