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


    全局获取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,否则将会出错。

    第一步:自定义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中注册

     <application
            android:name="com.firstcode.advanceproject.MyApplication"
    

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

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

    如何处理呢?
    只需要添加下面这句话即可: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<Person> CREATOR = new Creator<Person>() {
            @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表达式

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

  • 相关阅读:
    linux_ssh用户枚举猜测
    Nginx 主配置文件参数详解
    OSI七层模型
    linux-Python升级安装
    qt多线程
    python 对串口的操作
    keil 下模拟u-boot的cmd功能
    <转载>CentOS 6.3下Samba服务器的安装与配置
    Magento开发完整指南
    飞书信(Facebook Messenger)是什么?
  • 原文地址:https://www.cnblogs.com/zllk/p/13424577.html
Copyright © 2020-2023  润新知