「第一行代码」十、探究服务

服务是什么

服务(Service)是Android系统中实现程序后台运行的解决方案,它非常适合去执行那些不需要和用户交互而且还要求长期运行的任务。服务的运行不依赖于任何用户界面,即使程序被切换到后台,或者用户打开另一个应用程序,服务仍然能够保持正常运行。

服务并不是运行在一个独立的进程当中,而是依赖于创建服务时所在的应用程序进程。当某个应用程序进程被杀掉时,所有依赖于该进程的服务也会停止运行。

实际上,服务并不会自动开启线程,所有的代码都是默认运行在主线程当中的。我们需要在服务的内部手动创建子线程,并在这里执行具体的任务,否则就可能出现主线程被阻塞住的情况。

 

Android多线程编程

线程的基本用法

1. 新建一个类继承自Thread

重写run()方法,并在里面编写逻辑。

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

启动线程

new出MyThread的实例,然后调用它的start()方法。

new MyThread().start();

2. 新建一个类实现Runnable接口

重写run()方法,并在里面编写逻辑。

class MyThread implements Runnable {
    @Override
    public void run() {
        //处理具体的逻辑
    }
}

启动线程

Thread的构造函数接受一个Runnable参数,而new出的MyThread正是一个实现了Runnable接口的对象,将它传入到Thread的构造函数里,接着调用Thread的start()方法。

MyThread myThread = new MyThread();
new Thread(myThread).start();

3. 匿名类

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

 

解析异步消息处理机制

Android不允许在子线程进行UI操作

Android提供了一套异步消息处理机制,解决了在子线程中进行UI操作的问题。

Android中异步信息处理机制主要有4个部分组成:MessageHandlerMessageQueueLooper

1. Message

线程之间传递的消息,可以在内部携带少量的信息,用于在不同的线程之间交换数据。Message有what字段、arg1和arg2字段、obj字段。

2. Handler

处理者,主要用于发送和处理消息的。发送消息一般是用Handler的sendMessage()方法,发出信息最终会传递到Handler的handleMessage()方法中。

3. MessageQueue

消息队列,主要用于存放所有通过Handler发送的消息。这部分消息会一直存在于消息队列中,等待被处理。每个线程中只会有一个MessageQueue对象。

4. Looper

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

异步消息处理机制流程:

1. 首先在主线程当中创建一个Handler对象,并重写handleMessage()方法。2. 然后当子线程需要进行UI操作时,创建一个Message对象,并通过Handler将这条消息发送出去。3. 之后这条消息会被添加到MessageQueue的队列中等待被处理,而Looper则会一直尝试从MessageQueue中取出待处理消息,最后分发回Handler的handleMessage()方法中。4. 由于Handler是在主线程中创建的,所以此时handleMessage()方法中的代码会在主线程中执行。

public class MainActivity extends AppCompatActivity {

    Context context = this;

    public static final int UPDATE_TEXT = 1;

    private TextView text;

    private Handler handler = new Handler() {
        @Override
        public void handleMessage(@NonNull Message msg) {
            switch (msg.what) {
                case UPDATE_TEXT:
                    //在这里进行UI操作
                    text.setText("Nice to have you");
                    break;
                default:
                    break;
            }
        }
    };

    private View.OnClickListener listener = new View.OnClickListener() {
        @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;
                            //将Message对象发送出去
                            handler.sendMessage(message);
                        }
                    }).start();
                    break;
                default:
                    break;
            }
        }
    };
}

这里定义了一个整型常量UPDATE_TEXT,用于表示更新TextView这个动作,然后新增一个Handler对象,并重写父类的handleMessage()方法,在这里队具体的Message进行处理。如果发现Message的what字段的值等于UPDATE_TEXT,就将TextView显示的内容改成Nice to have you。

修改ChangeText按钮的点击事件的代码。这里并没有在子线程直接进行UI操作,而是创建了一个Message(android.os.Message)对象,并将它的what字段的值指定为UPDATE_TEXT,然后调用Handler的sendMessage()方法将这条Message发送出去。Handler收到这条Message,并在handleMessage()方法中对它进行处理。注意,此时handleMessage()方法中的代码运行在主线程中,所以可以进行UI操作。接下来对Message携带的what字段的值进行判断,如果等于UPDATE_TEXT,就将TextView显示的内容改成Nice to have you。

 

使用AsyncTask

为了更加方便的在子线程中对UI进行操作,Android提供了AsyncTask。其背后的实现原理也是基于异步消息处理机制的。

AsyncTask是一个抽象类,需要创建一个子类去继承它。在继承时可以为其指定3个泛型参数:

  • Params。执行AsyncTask时需要传入的参数,用于在后台任务中使用。
  • Progress。后台任务执行时,如果需要在界面上显示当前的进度,则使用这里指定的泛型作为进度单位。
  • Result。当任务执行完毕后,如果需要对结果进行返回,则使用这里指定的泛型作为返回值类型。
class DownloadTask extends AsyncTask<Void, Integer, Boolean> {

}

这里第一个泛型参数指定为Void,表示在执行AsyncTask的时候不需要传入参数给后台任务。第二个泛型参数指定为Integer,表示使用整形数据作为进度显示单位。第三个泛型参数指定为Boolean,表示使用布尔型数据来反馈执行结果。

重写AsyncTask中的几个方法才能完成对任务的定制:

1. onPreExcute()

这个方法在后台任务开始执行之前调用,用于进行一些界面上的初始化操作,比如显示一个进度条对话框等。

2. doInBakcground(Params...)

这个方法的所有代码都会在子线程中运行,应该在这里处理所有的耗时的任务。任务一旦完成就可以通过return语句来将任务的执行结果返回,如果AsyncTask的第三个泛型参数指定的是Void,就可以不返回任务执行结果。注意,这个方法中是不可以进行UI操作的,如果需要更新UI元素,可以调用publishProgress(Progress...)方法来完成。

3. onProgressUpdate(Progress...)

当在后台任务中调用了publisProgress(Progress...)方法后,onProgressUpdate(Progress...)方法就会很快被调用,该方法中携带的参数就是在后台任务中传递过来的。在这个方法中可以对UI进行操作,利用参数中的数值就可以对界面元素进行相应的更新。

4. onPostExecute(Result)

当后台任务执行完毕并通过return语句进行返回时,这个方法很快就会被调用。返回的数据会作为参数传递到此方法中卡,可以利用返回的数据进行一些UI操作,比如说提醒任务执行的结果,以及关闭掉进度条对话框等。

class DownloadTask extends AsyncTask<Void, Integer, Boolean> {
    
    ProgressDialog progressDialog;
    
    @Override
    protected void onPreExecute() {
        progressDialog.show();
    }

    @Override
    protected Boolean doInBackground(Void... voids) {
        try {
            while (true) {
                int downloadPercent = doDownload();
                publishProgress(downloadPercent);
                if (downloadPercent >= 100) {
                    break;
                }
            }
        } catch (Exception e) {
            return false;
        }
        return true;
    }

    @Override
    protected void onProgressUpdate(Integer... values) {
        progressDialog.setMessage("Downloaded " + values[0] + "%");
    }

    @Override
    protected void onPostExecute(Boolean result) {
        progressDialog.dismiss();
        if (result) {
            Toast.makeText(context, "Download succeeded", Toast.LENGTH_SHORT).show();
        } else {
            Toast.makeText(context, "Download failed", Toast.LENGTH_SHORT).show();
        }
    }
}

这段代码在doInBackground()方法里去执行具体的下载任务。方法里的代码都是在子线程运行的,因而不会影响到主线程的运行。由于doInBackground()方法是在子线程中运行,不能进行UI操作,所以调用publishProgress()方法并将当前下载进度传进来,这样onProgressUpdate()方法就会被调用,在这里就可以进行UI操作了。当下载完成后,doInBackground()方法会返回一个布尔型变量,这样onPostExecute()方法就会被调用,这个方法是在主线程中运行的。

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

如果想要启动这个任务:

new DownloadTask().execute();

 

服务的基本用法

定义一个服务

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

    @Override
    public void onCreate() {
        super.onCreate();
        Log.d("MyService", "onCreate executed");
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        Log.d("MyService", "onStartCommand executed");
        return super.onStartCommand(intent, flags, startId);
    }

    @Override
    public void onDestroy() {
        Log.d("MyService", "onDestroy executed");
        super.onDestroy();
    }
}

onBind()方法是Service类中唯一的抽象方法,所以必须在子类里实现。

处理事情的逻辑,需要重写onCreate()、onStartCommand()、onDestroy()。

onCreate()方法会在服务创建的时候调用;onStartCommand()方法会在每次服务启动的时候调用;onDestroy()方法会在服务销毁的时候调用。

如果希望服务一旦启动就立刻去执行某个动作,可以把该逻辑写在onStartCommand()方法里。而当服务销毁时,onDestroy()方法中去回收那些不再使用的资源。

注意,每个服务都需要在AndroidManifest文件中进行注册才能生效。实际上,这是Android四大组件共有的特点

        <service
            android:name=".MyService"
            android:enabled="true"
            android:exported="true"></service>

 

启动和停止服务

启动和停止服务的方法,主要借助Intent实现

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);
                break;
            case R.id.stop_service:
                Intent stopIntent = new Intent(this, MyService.class);
                //停止服务
                stopService(stopIntent);
                break;
            default:
                break;
        }
    }
    
}

在onCreate()方法中分别获取了StartService按钮和StopService按钮的实例,并注册了点击事件。在StartService按钮的点击事件里,创建出一个Intent对象,并调用startService()方法来启动MyService这个服务。在StopService按钮的点击事件里,创建出一个Intent对象,并调用stopService()方法来停止MyService这个服务。starService()和stopService()方法都是定义在Context类中,所以可以直接调用。

在MyService的任何一个位置调用stopSelf()方法就能让服务自己停止。

onCreate()方法是在服务第一次创建的时候调用的,onStartCommand()方法则在每次启动服务的时候都会调用。

 

活动和服务进行通信

借助onBind()方法实现活动和服务的通信

public class MyService extends Service {

    private DownloadBinder mBinder = new DownloadBinder();

    class DownloadBinder extends Binder {

        public void startDownload() {
            Log.d("MyService", "startDownload");
        }

        public int getProgress() {
            Log.d("MyService", "getProgress");
            return 0;
        }
    }

    @Override
    public IBinder onBind(Intent intent) {
        // TODO: Return the communication channel to the service.
        return mBinder;
    }
    ...
}

新建了一个DownloadBinder类,并让其继承自Binder,然后在其内部提供了开始下载以及查看下载进度的方法。

接着,在Myservice中创建DownloadBinder的实例,然后在onBind()方法里返回这个实例

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的实例
            downloadBinder = (MyService.DownloadBinder) service;
            downloadBinder.startDownload();
            downloadBinder.getProgress();
        }
        
        //在活动与服务断开时调用
        @Override
        public void onServiceDisconnected(ComponentName name) {
        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        ...
        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);
                break;
            case R.id.unbind_service:
                //解绑服务
                try {
                    if (connection != null) {
                        unbindService(connection);
                        break;
                    } else {
                        break;
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                }
            default:
                break;
        }
    }

}

首先创建一个ServiceConnection的匿名类,在里面重写了onServiceConnected()方法onServiceDisconnected()方法,这两个方法分别会在活动与服务成功绑定的时候以及活动与服务断开的时候调用。在onServiceConnected()方法中,通过向下转型获得DownloadBinder的实例。有了这个实例,活动与服务之间的关系就变得紧密起来,可以在活动里根据具体的场景来调用DownloadBinder中的任何public方法,即实现了指挥服务干什么服务就去干什么的功能。

为了实现活动与服务的绑定,仍然是创建一个Intent对象,然后调用bindService()方法将MainActivity和MyService进行绑定

bindService()

作用:

将活动与服务进行绑定

参数:

1. Intent对象

2. 前面创建的ServiceConnection实例

3. 标志位,这里传入BIND_AUTO_CREATE表示活动和服务进行绑定后自动创建服务,这会使得MyService中的onCreate()方法得到执行,但onStartCommand()方法不会执行。

unbindService()方法可以解除活动与服务之间的绑定

 

服务的生命周期

在项目的任何位置调用Context的startService()方法, 相应的服务就会启动起来,并回调onStartCommand()方法。如果这个服务之前没有被创建过,onCreate()方法就会先于onStartCommand()方法执行。

服务启动了之后就会一直保持运行状态,直到stopService()或者stopSelf()方法被调用。由于每个服务只会存在一个实例,所以不论调用多少次startService()方法,只需要调用一次stopService()或者stopSelf()方法,服务就会停止。

调用Context的bindService()来获取一个服务的持久连接,这时就会回调服务中的onBind()方法。如果这个服务还未被创建过,onCreate()方法会先于onBind()方法执行。之后,调用方可以获取到onBind()方法里返回的IBinder对象的实例,这样就能自由的和服务进行通信了。只要调用方和服务之间的连接没有断开,服务就会一直保持运行状态。

调用了startService()方法后,又去调用stopService()方法,此时服务中的onDestroy()方法就会执行,表示服务已经销毁。当调用了bindService()方法后,又去调用unbindService()方法,onDestroy()方法也会执行。

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

 

服务的更多技巧

使用IntentService

服务中的代码都是默认运行在主线程当中的,如果直接在服务里去处理一些耗时的逻辑,容易出现ANR(Application Not Responding)的情况。

所以需要用到Android多线程编程技术。应该在服务的每个具体的方法里开启一个线程,然后在这里去处理一些耗时的逻辑

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        Log.d("MyService", "onStartCommand executed");
        new Thread(new Runnable() {
            @Override
            public void run() {
                
                //处理具体的逻辑
                //...
                
                //执行完毕后自动停止
                stopSelf();
            }
        }).start();
        return super.onStartCommand(intent, flags, startId);
    }

 

为了简单的创建一个异步的会自动停止的服务,Android提供了一个IntentService类

public class MyIntentService extends IntentService {

    public MyIntentService() {
        //调用父类的有参构造方法
        super("MyIntentService");
    }

    @Override
    protected void onHandleIntent(Intent intent) {
        //这个方法中可以处理一些具体的逻辑
        //打印当前线程的id
        Log.d("MyIntentService", "Thread id is " + Thread.currentThread().getId());
    }

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

首先要提供一个无参的构造函数,并且必须在其内部调用父类的有参构造函数。然后要去子类中实现onHandleIntent()这个抽象方法,在这个方法中可以去处理一些具体的逻辑,而且不用担心ANR问题,因为这个方法已经是在子线程中运行的了。根据IntentService的特性,这个服务在运行结束后是会自动停止的,所以重写了onDestroy()方法,以证实服务是不是停止掉了。

            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;

IntentService的用法和普通的服务没什么区别。

注意,服务都是需要在androidManifest里注册的

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值