Android学习(四)—— 四大组件之Service

本文目录结构:
一、服务简介
二、多线程编程
三、服务的生命周期
四、服务的基本用法
五、前台服务
六、如何保证service不被杀死

一、服务简介

定义:

服务在Android中的运用主要是实现任务在后台的运行,特别是那些不需要和用户交互而且还要求长期运行的任务,比如下载资源。服务的运行不依赖于任何用户界面,即使程序被切换到后台,或者用户打开了另外一个应用程序,服务仍然能够保持正常运行。

注意:

①服务并不是运行在一个独立的进程中的,而是依赖于创建服务时所在的应用进程。所以当该进程被杀掉时,所有依赖于该进程的服务也会停止运行。
②服务并不会自动开启线程,所有代码都是运行在主线程当中的。

二、多线程编程

Android中的多线程主要有Thread与Handler的结合使用、AsyncTask、HandlerThread以及IntentService四种方式

2.1 Thread与Handler的结合使用
2.1.1 创建方式

Thread的创建方法我就不多讲了,在我的文章《那些你所不知道的Java小知识(持续更新)》的第3点中可以看到。因为系统不建议在子线程中更新UI,但是有些时候我们必须在子线程里去执行一些耗时任务,然后根据任务的执行结果来更新相应的控件。因此Android为我们提供了一个异步消息处理机制。(至于为什么不建议,大家可以去看一下鸿洋的《Android中子线程真的不能更新UI吗?》,讲解的很详细)

2.1.2 异步消息处理机制

该机制主要由四个部分组成:Message,Handler,MessageQueue和Looper。

  • Message 是在线程之间传递的,它可以携带少量的信息,用于在不同线程之间交换数据。每个线程可以有多个Message对象

Message的创建方式有以下三种:
Message msg = new Message();
Message msg =Message.obtain();
Message msg = handler1.obtainMessage();
后两种方法都是从整个Message池中返回一个新的Message实例,能有效避免重复Message创建对象,因此更推荐后两种方法。

  • Handler 主要用于发送和处理Message。发送消息一般是使用Handler的sendMessage方法,消息经过一系列流程过后会传到Handler的handleMessage方法中。每个线程可以有多个Handler对象
  • MessageQueue 主要用于存储所有通过Handler发送的Message。每个线程只有一个MessageQueue对象
  • Looper 相当于MessageQueue的管家,当调用Looper的loop方法之后,就会进入一个无限循环当中,然后每当发现MessageQueue中存在一条消息,就会将它取出,并传递到Handler的handleMessage方法中。每个线程只有一个Looper对象
    所以它们四个的数量关系是:每个线程只能有一个Looper和一个MessageQueue,但是可以有多个Handler和多个Message。
    流程图
2.1.3 小例子

这里我们实现点击按钮之后在子线程中更新按钮的文字。

小例子.png
2.1.4 注意

① 主线程中Looper的轮询死循环为什么没有阻塞主线程?
Android是依靠事件驱动的,通过Looper.loop()不断进行消息循环,可以说activity的生命周期都是运行在Looper.loop()的控制之下,一旦退出消息循环,应用也就退出了。
② 使用Handler的postDelay()后消息队列会发生什么变化?
postDelay的Message并不是先等待一定时间再放到MessageQueue中,而是直接放入并阻塞当前线程,然后将其delay的时间和队头的进行比较,按照触发时间进行排序,更近的则放入队头,保证队头的时间最小,队尾的最大。如果此时队头的Message正是被delay的,则将当前线程堵塞一段时间,直到等待足够的时间再唤醒执行该Message,否则唤醒后直接执行。

2.2 AsyncTask
2.2.1 前言

① AsyncTask的底层原理封装了线程池和Handler,在其之中有两个线程池:SerialExecutor用于任务的排队,默认是串行的线程池,在3.0以前核心线程数为5,线程池大小为128,而3.0以后改为同一时间只能处理一个任务;THREAD_POOL_EXECUTOR用于真正执行任务。
② AsyncTask是一个抽象类,使用前要先继承。(匿名内部类的方式除外)。

2.2.2 参数和方法说明

① 在继承的时候我们需要指定三个参数,它们代表的意义如下:

  • Params:在执行AsyncTask时需要传入的参数,它可以在后台任务中使用。
  • Progress:后台任务执行时,如果需要在界面上显示当前的进度,则使用这里指定的参数作为单位。
  • Result:当后台任务执行完毕后,如果需要返回结果,则使用这里的参数作为返回类型。
    所以,一个自定义的AsyncTask可以如下所示:
public class MyTask extends AsyncTask<Void,Integer,Boolean> {
    ...
}

② 接着我们需要重写其中的几个方法:

  • onPreExecute()
    这个方法会在后台任务开始执行之前调用,我们可以在这里进行一些界面上的初始化操作,比如显示一个对话框。
  • doInBackground(Params... params)
    这个方法中的所有代码都会在子线程中执行,我们应该在这里去处理所有的耗时操作。任务完成时就可以通过return语句将执行结果返回,如果指定第三个参数为Void就不需要返回了。因为代码都是在子线程中执行的,所以注意不能再这里更新UI。如果需要反馈当前后台任务的执行进度,可以调用publishProgress(Progress... progress)方法来实现。
  • OnProgressUpdate(Progress... progress)
    当后台任务调用publishProgress方法后,该方法很快就会被调用,其中的参数就是publishProgress传过来的。该方法里面可以对UI进行操作,利用参数中的数值就可以对界面元素进行相应的更新。
  • onPostExecute(Result result)
    当后台任务执行完毕并通过return语句返回时,该方法很快就会被调用。返回的结果就会被作为参数传到该方法中。我们可以利用返回的数据来进行一些UI操作,比如提醒任务执行的结果,以及关闭对话框等。
    所以,一个完整的AsyncTask可以如下所示:
public class MyTask extends AsyncTask<Void,Integer,Boolean> {

    @Override
    protected void onPreExecute() {
        dialog.show();//显示对话框
    }

    @Override
    protected Boolean doInBackground(Void... voids) {
        try{
            while(true){
                int downloadProgress = downloadAndGetProgress();//这是一个虚构的方法
                publishProgress(downloadProgress);
                if (downloadProgress >= 100){
                    break;
                }
            }
        }catch (Exception e){
            return false;//下载失败
        }
        return true;//下载成功
    }

    @Override
    protected void onProgressUpdate(Integer... values) {
        //更新下载进度
        dialog.setMessage("Download " + values[0] + "%");
    }

    @Override
    protected void onPostExecute(Boolean result) {
        dialog.dismiss();//关闭对话框
        if (result){
            //提示下载成功
        }else{
            //提示下载失败
        }
    }
}

如果想要启动这个后台任务,只需要执行new MyTask().execute();即可。

2.2.3 注意

① 不要直接调用onPreExecute()、doInBackground()、onProgressUpdate()、onPostExecute()和onCancelled()方法。
② 一个异步对象只能调用过一次execute方法。

2.3 HandlerThread
2.3.1 前言

HandlerThread是一种具有消息循环的线程,其内部可使用Handler。通俗来讲,与子线程绑定的handler发送出去的消息,是在子线程当中执行的。

2.3.2 使用方法

整体思路:将子线程与workHandler绑定之后,在要进行耗时操作的地方通过workHandler发送消息,然后在通过mainHandler发送消息进行主线程的UI更新。

        //第零步,创建主线程的handler,这里我称之为主handler
        mainHandler = new Handler(){
            @Override
            public void handleMessage(Message msg) {
                switch (msg.what){
                    case 1:
                        btn_send.setText("after");
                        break;
                }
            }
        };

        //第一步,创建handlerThread对象
        final HandlerThread handlerThread = new HandlerThread("handlerThread");

        //第二步,启动handlerTh
        handlerThread.start();

        //第三步,创建与handlerThread绑定的handler,这里我称之为工作handler
        //其发送的消息都是在子线程中执行的
        final Handler workHandler = new Handler(handlerThread.getLooper(), new Handler.Callback() {
            @Override
            public boolean handleMessage(Message msg) {
                switch (msg.what){
                    case 0:
                        //这里做耗时操作
                        Log.i(TAG, "我做了耗时操作。");

                        //做完耗时操作通过mainHandler进行UI的更新
                        mainHandler.sendEmptyMessage(1);
                        break;
                }
                return false;
            }
        });

        //第四步,通过workHan发送消息来做耗时操作
        btn_send = findViewById(R.id.btn_send);
        btn_send.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        workHandler.sendEmptyMessage(0);
                    }
                }).start();
            }
        });

        //第五步,退出消息循环
        btn_quit = findViewById(R.id.btn_quit);
        btn_quit.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        handlerThread.quit();
                    }
                }).start();
            }
        });
2.3.3 注意

① 如何将一个Thread线程变成一个Looper线程?
通过Looper.prepare()可将一个一个Thread线程变成一个Looper线程。

② Looper线程有什么特点?
它通过MessageQueue来存放消息,Looper.loop()进行消息轮询。

③ 可以在子线程直接new一个Handler吗?不行的话要怎么做?
因为子线程的Looper需要手动去创建,所以不可以直接new。
要new一个Handler的话可以这样做:

new Thread(new Runnable() {
            @Override
            public void run() {
                Looper.prepare();//为子线程创建Looper
                new Handler(){
                    @Override
                    public void handleMessage(Message msg) {
                        //这里的消息处理是在子线程中执行的
                    }
                };
                Looper.loop();//开启消息轮询
            }
        }).start();

ActivityThread里调用了 Looper.prepareMainLooper()方法创建了主线程的 Looper ,并且调用了 loop() 方法,所以我们就可以直接在主线程使用 Handler 了

2.4 IntentService
2.4.1 前言

不同于线程,它是服务,优先级比线程高,更不容易被系统杀死,因此较适合执行一些高优先级的后台任务;
不同于普通服务,它可自动创建子线程来执行后台任务,并且任务执行完毕后会自动退出。

2.4.2 使用方法
//第一步,自定义服务
public class MyIntentService extends IntentService {

    public MyIntentService(){
        //必须调用父类的有参构造函数
        super("ha");//这里的ha是随便写的
    }

    //重写onHandleIntent方法
    @Override
    protected void onHandleIntent( Intent intent) {
        //做耗时操作,因为该方法是在子线程中执行的
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        Log.i("MyIntentService", "MyIntentService onDestroy");
    }
}

//第二步,注册服务(在AndroidManifest文件中)
<service android:name=".MyIntentService"/>

//第三步,启动服务(在activity中)
Intent intent = new Intent(MainActivity.this,MyIntentService.class);
startService(intent);
2.4.3 注意

① IntentService与Activity间通信:
Activity传值给IntentService:Intent、EventBus等
IntentService传值给Activity:广播、EventBus等(回调我试过了,竟然不行!)

② 为何不用bindService方式启动IntentService?
IntentService的工作原理是,在IntentService的onCreate里创建一个HandlerThread,并利用其内部的Looper实例化一个ServiceHandler对象;而这个ServiceHandler用于处理消息的handleMessage方法会去调用IntentService的onHandleIntent方法,这也是为什么可在该方法中去处理后台任务的原因。当有Intent任务请求时会把Intent封装成Message,然后ServiceHandler会把消息发送出去,而发送消息是在onStartCommand中完成的,只能通过startService方法才可走该生命周期方法,所以不能用bindService方式启动IntentService。

三、服务的生命周期

3.1 通过startService方式启动服务
  • 生命周期:onCreate -> onStartCommand -> onDestroy
  • 通过stopService或者stopSelf方法停止服务。
  • 一旦服务开启就跟调用者(开启者)没有任何关系了。
  • 开启者退出了,开启者挂了,服务还在后台长期的运行。
  • 开启者不能调用服务里面的方法。
3.2 通过bindService方式启动服务
  • 生命周期:onCreate -> onBind -> onUnBind -> onDestroy
  • 通过unbindService方法停止服务。
  • 该方式开启服务,绑定服务,调用者挂了,服务也会跟着挂掉。
  • 绑定者可以调用服务里面的方法。
  • 常用于Service和Activity间通信。
3.3 注意

① 当服务在启动之前还没有创建过,onCreate方法会先执行,否则不会再执行。
② 一个Activity先start一个Service之后,再bind时会回调什么方法?此时如何才能回调Service的destroy方法呢?
只会回调onBind方法。这种情况下要同时调用stopService和unbindService方法。

四、服务的基本用法

4.1 startService方式
//第一步,自定义类继承Service,必须重写onBind方法
public class MyService extends Service {

    private static final String TAG = "MyService";

    @Override
    public IBinder onBind(Intent intent) {
        Log.i(TAG, "onBind: ");
        return null;
    }

    //第二步,选择性的重写onCreate,onStartCommand和onDestroy方法

    @Override
    public void onCreate() {
        Log.i(TAG, "onCreate: ");
        super.onCreate();
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        Log.i(TAG, "onStartCommand: ");
        return super.onStartCommand(intent, flags, startId);
    }

    @Override
    public void onDestroy() {
        Log.i(TAG, "onDestroy: ");
        super.onDestroy();
    }
}

//第三步,注册服务
<service android:name=".MyService"/>

//第四步,启动或者停止服务
//启动服务
Intent intent = new Intent(Main3Activity.this,MyService.class);
startService(intent);
//停止服务
Intent intent = new Intent(Main3Activity.this,MyService.class);
stopService(intent);
4.2 bindService方式(也是service与activity通信的方式)

整体思路:
首先,自定义类继承Binder,按照自己的业务逻辑写相应的方法供activity调用。
然后,自定义类继承Service,在onBind方法中返回自定义的Binder。
接着,实例化ServiceConnection对象,重写onServiceConnected和onServiceDisconnected方法。
最后,bindService绑定服务即可。

public class MyService extends Service {

    private static final String TAG = "MyService";
    private DownloadBinder mBinder = new DownloadBinder();

    //自定义类继承Binder
    class DownloadBinder extends Binder {

        public void startDownload(){
            Log.i(TAG, "startDownload: ");
        }

        public int getProgress(){
            Log.i(TAG, "getProgress: ");
            return 0;
        }
    }

    @Override
    public IBinder onBind(Intent intent) {
        Log.i(TAG, "onBind: ");
        return mBinder; //返回自定义的Binder对象
    }
    
    @Override
    public void onCreate() {
        Log.i(TAG, "onCreate: ");
        super.onCreate();
    }

    @Override
    public boolean onUnbind(Intent intent) {
        Log.i(TAG, "onUnbind: ");
        return super.onUnbind(intent);
    }

    @Override
    public void onDestroy() {
        Log.i(TAG, "onDestroy: ");
        super.onDestroy();
    }
}
public class Main3Activity extends AppCompatActivity {

    private static final String TAG = "Main3Activity";
    private Button btn_send,btn_quit;
    private MyService.DownloadBinder downloadBinder;
    private ServiceConnection connection = new ServiceConnection() {
        
        //service与activity绑定成功时调用
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            downloadBinder = (MyService.DownloadBinder) service;
            downloadBinder.startDownload();
            downloadBinder.getProgress();
        }

        //service与activity绑定断开时调用
        @Override
        public void onServiceDisconnected(ComponentName name) {

        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main3);


        btn_send = findViewById(R.id.btn_send);
        btn_send.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Intent intent = new Intent(Main3Activity.this,MyService.class);
                //第三个参数表示在活动和服务进行绑定后自动创建服务
                bindService(intent,connection,BIND_AUTO_CREATE);
            }
        });


        btn_quit = findViewById(R.id.btn_quit);
        btn_quit.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                //解除绑定
                unbindService(connection);
            }
        });
    }
}

五、前台服务

前言

服务几乎都是在后台运行的,系统优先级还是比较低的,当系统出现内存不足时就有可能会回收掉正在后台运行的服务。此时为了防止服务被回收掉,就可以使用前台服务。前台服务和普通服务最大的区别就在于,它会一直有一个正在运行的图标在系统状态栏显示,下拉状态栏可以看到更加详细的信息,非常类似于通知的效果。

使用方法
public class MyService extends Service {

    ...

    @Override
    public void onCreate() {
        super.onCreate();
        Intent intent = new Intent(this,MainActivity.class);
        PendingIntent pi = PendingIntent.getActivity(this,0,intent,0);
        Notification notification = new Notification.Builder(this)
                .setContentTitle("This is content title")
                .setContentText("This is content text")
                .setWhen(System.currentTimeMillis())
                .setSmallIcon(R.mipmap.ic_launcher)
                .setLargeIcon(BitmapFactory.decodeResource(getResources(),R.mipmap.ic_launcher))
                .setContentIntent(pi)
                .build();
        startForeground(1,notification);
    }
注意

现在Android对于通知栏已经做了修改,我们还需要去做版本适配,在这里我就不展开来讲了,贴一个郭神的文章吧Android通知栏微技巧,8.0系统中通知栏的适配

六、保证service不被杀死的方法

  • 在Service的onStartCommand方法中设置flages值为START_STICKY,使得Service被杀死后尝试再次启动Service。
  • 提升Service优先级,比如设置为一个前台服务。
  • 在Activity的onDestroy通过发送广播,并在广播接收器的onReceive中启动service。
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 200,612评论 5 471
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,345评论 2 377
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 147,625评论 0 332
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,022评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,974评论 5 360
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,227评论 1 277
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,688评论 3 392
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,358评论 0 255
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,490评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,402评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,446评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,126评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,721评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,802评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,013评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,504评论 2 346
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,080评论 2 341

推荐阅读更多精彩内容