Android学习笔记——Service

2017-08-12

参考书籍:Android第一行代码(第二版).郭霖著

服务(Service)是Android中实现程序后台运行的解决方案,适合执行不需要和用户交互且需长期运行的任务,不依赖于任何用户界面。

服务并不是运行在一个独立进程中,依赖于创建服务时所在的应用程序进程(应用程序被杀掉,服务也会停止运行),默认在主线程中运行(一般在内部手动创建子线程)。

1、Android多线程

(1)线程基本用法

与Java基本使用相同语法。

一般有两种:
a、新建继承自Thread的类定义一个线程,重写父类的run()方法,通过new出此类的实例,调用其start()方法来启动:

class MyThread extends Thread{
    @Override
    public void run(){
    //处理具体逻辑
    }
}
new MyThread().start();

b、使用实现Runnable接口的方式定义一个线程(使用集成的方式耦合性有点高,更多采用此方法),相应启动线程的方式也不同:

class MyThread implements Runnable{
    @Override
    public void run(){
    //处理具体逻辑
    }
}
MyThread myThread = new MyThread();
new Thread(myThread).start;//Thread的构造函数接收一个Runnable参数

还可使用匿名类方式启动(更常见):

new Thread(new Runnable(){
    @Override
    public void run(){
    //处理具体逻辑
    }
}).start();

(2)在子线程中更新UI

Android多线程编程与Java多线程编程不同之处。

Android的UI是线程不安全的,必须在主线程中更新UI元素,否则会出现异常。
但有时必须在子线程中执行一些耗时任务,根据结果更新相应UI元素——异步消息处理机制。

例:新建AndroidThreadTest项目,布局文件中定义一个按钮和一个TextView,希望点击按钮来改变文本内容,修改MainActivity:

public class MainActivity extends AppCompatActivity implements View.OnClickListener{
    public static final int UPDATE_TEXT = 1;

    private TextView text;
    private Handler handler = new Handler(){
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what){
                case UPDATE_TEXT:
                    //在这里进行UI操作
                    text.setText("How are you?");
                    break;
                default:
                    break;
            }
        }
    };
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        text = (TextView) findViewById(R.id.text);
        Button chaneText = (Button) findViewById(R.id.change_text);
        chaneText.setOnClickListener(this);
    }

    @Override
    public void onClick(View v) {
        switch (v.getId()){
            case R.id.change_text:
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        Message message = new Message();
                        message.what = UPDATE_TEXT;
                        handler.sendMessage(message);//将Message对象发送出去
                    }
                }).start();
                break;
            default:
                break;
        }

    }
}

运行程序,点击按钮:
这里写图片描述

(3)异步消息处理机制

Android中的异步消息处理主要由4部分组成:
a、Message
在线程之间传递的消息(可在内部携带少量信息)。有what字段、arg1/arg2字段(携带整型数据)、obj字段(携带Object对象)。

b、Handler
处理者,用于发送(一般使用Handler的sendMessage()方法,发出的消息经系列辗转处理后传递到Handler的handleMessage()方法中)和处理消息。

c、MessageQueue
消息队列,用于存放所有通过Handler发送的消息(一直存在于消息队列中等待被处理)。每个线程只有一个此对象。

d、Looper
每个线程中MessageQueue的管家,调用Looper的loop()方法后会进入到无限循环中,每当发现MessageQueue中存在一条消息就取出,并传递到Handler的handleMessage()方法中。每个线程只有一个此对象。

异步消息处理流程:在主线程中创建一个Handler对象并重写handleMassage()方法;当子线程中需进行UI操作时,创建一个Message对象并通过Handler发送这条消息;此消息会被添加到MessageQueue队列中等待被处理,Looper一直尝试从MessageQueue中取出待处理消息,后分发回Handler的handleMessage()中。由于Handler在主线程中创建,所以相应的handleMessage()方法中的代码也在主线程中运行,可在此进行UI操作(消息从子线程进入主线程)。
这里写图片描述

runOnUiThread()方法就是一个异步处理机制的接口封装。

(4)AsyncTask

Android提供了一些好用的工具更方便在子线程中操作UI。AsyncTask的实现原理基于异步消息处理机制,做了很好的封装。

基本用法:
a、AsyncTask是一个抽象类,需创建子类继承。继承时可指定三个泛型参数:Params(在执行AsyncTask时需传入的参数,可用于在后台任务中使用)、Progress(后台任务执行时,如需在界面上显示当前进度,则使用此泛型单位)、Result(任务执行完毕后,如需返回结果,则使用此泛型作为返回值类型):

class DownloadTask extends AsyncTask<Void, Integer, Boolean>{
    ...
}

b、重写AsyncTask中的方法,常用的有四个:

onPreExecute(),在后台任务开始之前调用,用于进行界面上的初始化操作(如显示进度条对话框);

doInBackground(Params…),方法中代码在子线程中运行,处理所有耗时任务,完成通过return返回结果(如Rusult指定为Void则不返回结果),如需更新UI,可调用publishProgress(Progress…)完成;

onProgressUpdate(Progress…),当后台调用publishProgress后,会被很快调用,参数是后台传递过来的,可利用参数操作UI;

onPostExecute(Result),后台任务完成并通过return返回时很快被调用,可利用返回的数据进行UI操作(如提醒执行结果、关闭进度条等)

使用AsyncTask的方法简单来说就是,在doInBackground()中执行具体的耗时任务,在onProgressUpdate()中进行UI操作,在onPostExecute()中执行任务的收尾工作。

启动此任务:

new DownloadTask().execute();//DownloadTask为自定义的AsyncTask

2、服务的基本用法

(1)定义

新建ServiceTest项目,右键包名New一个Service,Exported和Enableed都勾选:

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

    @Override
    public IBinder onBind(Intent intent) {
        // TODO: Return the communication channel to the service.
        throw new UnsupportedOperationException("Not yet implemented");//唯一一个抽象方法
    }
}

此时,服务已自动被注册了。要让服务处理事情,还需重写一些方法:

public class MyService extends Service {
   ...

    //服务创建时调用
    @Override
    public void onCreate() {
        super.onCreate();
    }

    //每次服务启动时调用
    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        return super.onStartCommand(intent, flags, startId);
    }
    //服务销毁时调用
    @Override
    public void onDestroy() {
        super.onDestroy();
    }
}

(2)启动/停止服务

修改布局文件,加入两个按钮分别启动和停止服务,修改MainActivity:

public class MainActivity extends AppCompatActivity implements View.OnClickListener{

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Button startService = (Button) findViewById(R.id.start_service);
        Button stopService = (Button) findViewById(R.id.stop_service);
        startService.setOnClickListener(this);
        stopService.setOnClickListener(this);
    }

    @Override
    public void onClick(View v) {
        switch (v.getId()){
            case R.id.start_service:
                Intent startIntent = new Intent(this, MyService.class);
                startService(startIntent);//启动服务,startService定义在Context类中
                break;
            case R.id.stop_service:
                Intent stopIntent = new Intent(this, MyService.class);
                stopService(stopIntent);//停止服务,stopService定义在Context类中
                break;
            default:
                break;
        }
    }
}

让服务自己停下来,只需在MyService的任何位置调用stopSelf()即可。要观察服务是否真的启动和停止,只需在重写的三个方法中加入打印信息即可。

运行程序:
这里写图片描述

(3)与活动通信

借助onBind()方法。如希望在MyService里提供一个下载功能,在活动中可以决定何时开始下载及随时查看下载进度。思路:创建一个专门的Binder对象管理下载功能,修改MyService:

public class MyService extends Service {
    private DownloadBinder mBinder = new DownloadBinder();

    class DownloadBinder extends Binder{
        public void startDownload(){
            Log.d("MyService", "startDownload executed");
        }
        public int getProgress(){
            Log.d("MyService", "getProgress executed");
            return 0;
        }
    }
    public MyService() {
    }

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

在布局文件中加入两个按钮分别用于绑定和取消绑定服务。当活动和服务绑定后,就可调用服务里Binder提供的方法了。修改MainActivity:

public class MainActivity extends AppCompatActivity implements View.OnClickListener{
    private MyService.DownloadBinder downloadBinder;
    private ServiceConnection connection = new ServiceConnection() {
        //活动与服务成功绑定时调用
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            downloadBinder = (MyService.DownloadBinder) service;//向下转型得到DownloadBinder实例
            downloadBinder.startDownload();//调用DownloadBinder任何public方法
            downloadBinder.getProgress();
        }
        //活动与服务解除绑定时调用
        @Override
        public void onServiceDisconnected(ComponentName name) {

        }
    };//创建ServiceConnection匿名类

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        ...
        Button bindService = (Button) findViewById(R.id.bind_service);
        Button unbindService = (Button) findViewById(R.id.unbind_service);
        bindService.setOnClickListener(this);
        unbindService.setOnClickListener(this);

    }

    @Override
    public void onClick(View v) {
        switch (v.getId()){
                ...
            case R.id.bind_service:
                Intent bindIntent = new Intent(this, MyService.class);
                bindService(bindIntent, connection, BIND_AUTO_CREATE);//绑定服务,第三个参数为标志位,
                // BIND_AUTO_CREATE表示在活动与服务绑定后自动创建服务:onCreate()方法执行而onStartCommand()方法不执行
                break;
            case R.id.unbind_service:
                unbindService(connection);//解绑服务
                break;
            default:
                break;
        }
    }
}

运行程序,点击绑定按钮:

这里写图片描述

已在活动中调用了服务里提供的方法。任何一个服务在整个应用程序范围内是通用的,还可与其他活动绑定,且绑定后都可活得相同的DownloadBinder实例。

3、服务的生命周期

一旦调用了Context的startService(),服务启动并回调onStartCommand()方法(没没创建则先执行onCreate()方法);服务启动后一直保持运行状态,直到stopService()/stopSelf()被调用。每个服务只存在一个实例,只需调用一次stopService()/stopSelf()就会停止。
另,还可调用Context的bindService()获取服务的持久连接,同时会回调服务中的onBind()方法(类似,如服务之前没创建则先调用onCreate()方法)。之后,调用方可获取onBind()返回的IBinder对象实例,与服务进行自由通信。只要连接没断开,服务会一直保持运行。

当调用了startService()/bindService()后,又调用stopService()/unbindService()(一一对应),onDestroy()会被执行。

Android机制是,服务只要被启动或被绑定,就会一直处于运行状态,必须同时不满足以上两种条件才能被销毁。所以,如果对服务既调用了startService()又调用了bindService(),要同时调用stopService()和unbindService()方法,onDestroy()才会被执行。

4、使用技巧

(1)前台服务

服务的系统优先级较低,系统内存不足时可能被回收。如想让服务一直保持运行状态不会被回收(如天气预报应用),可使用前台服务。

与普通服务的区别:一直有一个正在运行的图标在系统状态栏显示(下拉后可看到更详细信息,与通知类似)。

方法:修改MyService代码,

public class MyService extends Service {
    ...

    @Override
    public void onCreate() {
        Log.d("MyService", "onCreate executed");
        super.onCreate();
        Intent intent = new Intent(this, MainActivity.class);
        PendingIntent pi = PendingIntent.getActivity(this, 0, intent, 0);
        Notification notification = new NotificationCompat.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);//第一个参数为通知的id,startForeground让MyService成为一个前台服务,并在系统状态栏显示
    }

运行程序,点击开始/绑定服务按钮:
这里写图片描述

(2)使用IntentService

服务中代码默认在主线程中运行,所以应该在服务每个具体方法里开启一个子线程去处理耗时操作,要让服务自动停止还需调用stopSelf()方法。为了简单创建一个异步自动停止的服务,Android专门提供了一个IntentService类。

新建一个MyIntentService类继承自IntentService(手动创建记得添加声明):

public class MyIntentService extends IntentService {

    public MyIntentService() {//提供无参构造函数,内部调用父类有参构造函数
        super("MyIntentService");//调用父类有参构造函数
    }

    @Override
    protected void onHandleIntent(Intent intent) {//继承父类中的抽象方法,
        // 处理具体的逻辑,在子线程中运行(不用担心ANR(应用无响应)问题)

        //打印当前线程的id
        Log.d("MyIntentService", "Thread id is " + Thread.currentThread().getId());
    }

    //IntentService服务运行结束后会自动停止
    @Override
    public void onDestroy() {
        super.onDestroy();
        Log.d("MyIntentService", "onDestroy executed");
    }
}

修改布局文件,添加启动此服务的按钮,修改MainActivity:

public class MainActivity extends AppCompatActivity implements View.OnClickListener{
   ...
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        ...
        Button startIntentService = (Button) findViewById(R.id.start_intent_service); 
        startIntentService.setOnClickListener(this);

    }

    @Override
    public void onClick(View v) {
        switch (v.getId()){
            ...
            case R.id.start_intent_service:
                //打印主线程id
                Log.d("MainActivity", "Thread id is " + Thread.currentThread().getId());
                Intent intentService = new Intent(this, MyIntentService.class);
                startService(intentService);
                break;
            default:
                break;
        }
    }
}

运行程序,点击开始按钮,查看日志:

这里写图片描述

可以看到线程id不一样,且onDestroy()方法得到执行(说明服务运行完自动停止)。



注明:本文章属于转载,仅供行业人员学习交流使用,文章版权属于原创作者,在此向原创者致敬,感谢原创作者为大家学习交流提供精品内容。

站方声明:IThao123是为广大互联网从业者免费提供学习交流的平台,如果侵犯了原创著作权,请联系站方删除,给你带来不便,深表歉意。

顶部