服务作为Android四大组件之一,是一种可在后盾执行长时间运行操作而不提供界面的利用组件。服务可由其余利用组件启动,而且即便用户切换到其余利用,服务仍将在后盾持续运行。须要留神的是服务并不会主动开启线程,所有的代码都是默认运行在主线程当中的,所以须要在服务的外部手动创立子线程,并在这里执行具体的工作,否则就有可能呈现主线程被阻塞住的状况。

<!-- more -->

Android多线程编程

异步音讯机制

对于多线程编程其实和Java统一,无论是继承Thread还是实现Runnable接口都能够实现。在Android中须要把握的就是在子线程中更新UI,UI是由主线程来管制的,所以主线程又称为UI线程。

Only the original thread that created a view hierarchy can touch its views.

尽管不容许在子线程中更新UI,然而Android提供了一套异步音讯解决机制,完满解决了在子线程中操作UI的问题,那就是应用Handler。先来回顾一下应用Handler更新UI的用法:

public class MainActivity extends AppCompatActivity {    private static final int UPDATE_UI = 1001;    private TextView textView;    private Handler handler = new Handler(Looper.getMainLooper()){        @Override        public void handleMessage(@NonNull Message msg) {            if(msg.what == UPDATE_UI) textView.setText("Hello Thread!");        }    };    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);        textView = findViewById(R.id.tv_main);    }    public void updateUI(View view) {        // new Thread(()-> textView.setText("Hello Thread!")).start(); Error!        new Thread(()->{            Message message = new Message();            message.what = UPDATE_UI;            handler.sendMessage(message);        }).start();    }}

应用这种机制就能够杰出地解决掉在子线程中更新UI的问题,上面就来剖析一下Android异步音讯解决机制到底的工作原理:Android中的异步音讯解决次要由4个局部组成:Message,Handler,MessageQueue和Looper。
1、Message:线程之间传递的音讯,它能够在外部携带大量的信息,用于在不同线程之间替换数据。
2、Handler:解决者,它次要是用于发送和解决音讯的。发送音讯个别是应用Handler的sendMessage()办法,而收回的音讯通过一系列地辗转解决后,最终会传递到Handler的handleMessage()办法中。
3、MessageQueue:音讯队列,它次要用于寄存所有通过Handler发送的音讯。这部分音讯会始终存在于音讯队列中,期待被解决。每个线程中只会有一个MessageQueue对象。

4、Looper是每个线程中的MessageQueue的管家,调用Looper的loop()办法后,就会进入到一个有限循环当中,而后每当发现 MessageQueue 中存在一条音讯,就会将它取出,并传递到Handler的handleMessage()办法中。每个线程中也只会有一个Looper对象。

异步音讯解决整个流程:首先须要在主线程当中创立一个Handler 对象,并重写handleMessage()办法。而后当子线程中须要进行UI操作时,就创立一个Message对象,并通过Handler将这条音讯发送进来。之后这条音讯会被增加到MessageQueue的队列中期待被解决,而Looper则会始终尝试从MessageQueue 中取出待处理音讯,最初散发回 Handler 的handleMessage()办法中。因为Handler是在主线程中创立的,所以此时handleMessage()办法中的代码也会在主线程中运行,于是咱们在这里就能够安心地进行UI操作了。整个异步音讯解决机制的流程如下图所示:

AsyncTask

不过为了更加不便咱们在子线程中对UI进行操作,Android还提供了另外一些好用的工具,比方AsyncTask。AsyncTask背地的实现原理也是基于异步音讯解决机制,只是Android帮咱们做了很好的封装而已。首先来看一下AsyncTask的根本用法,因为AsyncTask是一个抽象类,所以如果咱们想应用它,就必须要创立一个子类去继承它。在继承时咱们能够为AsyncTask类指定3个泛型参数,这3个参数的用处如下:

Params:在执行AsyncTask时须要传入的参数,可用于在后台任务中应用。
Progress:后台任务执行时,如果须要在界面上显示以后的进度,则应用这里指定的泛型作为进度单位。
Result:当工作执行结束后,如果须要对后果进行返回,则应用这里指定的泛型作为返回值类型。

public class MainActivity extends AppCompatActivity {    private static final String TAG = "MainActivity";    private final int REQUEST_EXTERNAL_STORAGE = 1;        @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);    }    public void startDownload(View view) {        verifyStoragePermissions(this);        ProgressBar progressBar = findViewById(R.id.download_pb);        TextView textView = findViewById(R.id.download_tv);        new MyDownloadAsyncTask(progressBar, textView).execute("http://xxx.zip");    }    class MyDownloadAsyncTask extends AsyncTask<String, Integer, Boolean> {        private ProgressBar progressBar;        private TextView textView;        public MyDownloadAsyncTask(ProgressBar progressBar, TextView textView) {            this.progressBar = progressBar;            this.textView = textView;        }        @Override        protected Boolean doInBackground(String... strings) {            String urlStr = strings[0];            try {                URL url = new URL(urlStr);                HttpURLConnection conn = (HttpURLConnection) url.openConnection();                InputStream inputStream = conn.getInputStream();                // 获取文件总长度                int length = conn.getContentLength();                File downloadsDir = new File("...");                File descFile = new File(downloadsDir, "xxx.zip");                int downloadSize = 0;                int offset;                byte[] buffer = new byte[1024];                FileOutputStream fileOutputStream = new FileOutputStream(descFile);                while ((offset = inputStream.read(buffer)) != -1){                    downloadSize += offset;                    fileOutputStream.write(buffer, 0, offset);                                        // 抛出工作执行的进度                    publishProgress((downloadSize * 100 / length));                }                fileOutputStream.close();                inputStream.close();                Log.i(TAG, "download: descFile = " + descFile.getAbsolutePath());            } catch (IOException e) {                e.printStackTrace();                return false;            }            return true;        }        // 在主线程中执行后果解决        @Override        protected void onPostExecute(Boolean aBoolean) {            super.onPostExecute(aBoolean);            if(aBoolean){                textView.setText("下载实现,文件位于..xx.zip");            }else{                textView.setText("下载失败");            }        }        // 工作进度更新        @Override        protected void onProgressUpdate(Integer... values) {            super.onProgressUpdate(values);            // 收到新进度,执行解决            textView.setText("已下载" + values[0] + "%");            progressBar.setProgress(values[0]);        }        @Override        protected void onPreExecute() {            super.onPreExecute();            textView.setText("未点击下载");        }    }}

1、onPreExecute():办法会在后台任务开始执行之前调用,用于进行一些界面上的初始化操作,比方显示一个进度条对话框等。

2、doInBackground():办法中的所有代码都会在子线程中运行,咱们应该在这里去解决所有的耗时工作。工作一旦实现就能够通过return语句来将工作的执行后果返回,如果 AsyncTask的第三个泛型参数指定的是Void,就能够不返回工作执行后果。留神,在这个办法中是不能够进行UI操作的,如果须要更新UI元素,比如说反馈当前任务的执行进度,能够调用publishProgress()办法来实现。

3、onProgressUpdate():当在后台任务中调用了publishProgress()办法后,onProgressUpdate()办法就会很快被调用,该办法中携带的参数就是在后台任务中传递过去的。在这个办法中能够对UI进行操作,利用参数中的数值就能够对界面元素进行相应的更新。

4、onPostExecute():当后台任务执行结束并通过return语句进行返回时,这个办法就很快会被调用。返回的数据会作为参数传递到此办法中,能够利用返回的数据来进行一些UI操作,比如说揭示工作执行的后果,以及敞开掉进度条对话框等。

服务的根本用法

服务首先作为Android之一,天然也要在Manifest文件中申明,这是Android四大组件共有的特点。新建一个MyService类继承自Service,而后再清单文件中申明即可。

服务的创立与启动

MyService.java:

public class MyService extends Service {    private static final String TAG = "MyService";    public MyService() {            }        @Override    public IBinder onBind(Intent intent) {        Log.i(TAG, "onBind: ");        // TODO: Return the communication channel to the service.        throw new UnsupportedOperationException("Not yet implemented");    }}

AndroidManifest.xml:

<?xml version="1.0" encoding="utf-8"?><manifest xmlns:android="http://schemas.android.com/apk/res/android"    package="cn.tim.basic_service">    <application        android:allowBackup="true"        android:icon="@mipmap/ic_launcher"        android:label="@string/app_name"        android:roundIcon="@mipmap/ic_launcher_round"        android:supportsRtl="true"        android:theme="@style/AppTheme">                <service            android:name=".MyService"            android:enabled="true"            android:exported="true" />        <activity android:name=".MainActivity">            <intent-filter>                <action android:name="android.intent.action.MAIN" />                <category android:name="android.intent.category.LAUNCHER" />            </intent-filter>        </activity>    </application></manifest>

能够看到,MyService的服务标签中有两个属性,exported属性示意是否容许除了以后程序之外的其余程序拜访这个服务,enabled属性示意是否启用这个服务。而后在MainActivity.java中启动这个服务:

// 启动服务startService(new Intent(this, MyService.class));

服务的进行(销毁)

如何进行服务呢?在MainActivity.java中进行这个服务:

Intent intent = new Intent(this, MyService.class);// 启动服务startService(intent);// 进行服务stopService(intent);

其实Service还能够重写其余办法:

public class MyService extends Service {    private static final String TAG = "MyService";    public MyService() {    }    // 创立    @Override    public void onCreate() {        super.onCreate();        Log.i(TAG, "onCreate: ");    }    // 启动    @Override    public int onStartCommand(Intent intent, int flags, int startId) {        Log.i(TAG, "onStartCommand: ");        return super.onStartCommand(intent, flags, startId);    }    // 绑定    @Override    public IBinder onBind(Intent intent) {        Log.i(TAG, "onBind: ");        // TODO: Return the communication channel to the service.        throw new UnsupportedOperationException("Not yet implemented");    }    // 解绑    @Override    public void unbindService(ServiceConnection conn) {        super.unbindService(conn);        Log.i(TAG, "unbindService: ");    }    // 销毁    @Override    public void onDestroy() {        super.onDestroy();        Log.i(TAG, "onDestroy: ");    }}

其实onCreate()办法是在服务第一次创立的时候调用的,而 onStartCommand()办法则在每次启动服务的时候都会调用,因为方才咱们是第一次点击Start Service按钮,服务此时还未创立过,所以两个办法都会执行,之后如果再间断多点击几次 Start Service按钮,就只有onStartCommand()办法能够失去执行了:

服务绑定与解绑

在下面的例子中,尽管服务是在流动里启动的,但在启动了服务之后,流动与服务根本就没有什么关系了。这就相似于流动告诉了服务一下:你能够启动了!而后服务就去忙本人的事件了,但流动并不知道服务到底去做了什么事件,以及实现得如何。所以这就要借助服务绑定了。

比方在MyService里提供一个下载性能,而后在流动中能够决定何时开始下载,以及随时查看下载进度。实现这个性能的思路是创立一个专门的Binder对象来对下载性能进行治理,批改MyService.java:

public class MyService extends Service {    private static final String TAG = "MyService";    private DownloadBinder mBinder = new DownloadBinder();        static class DownloadBinder extends Binder {        public void startDownload() {            // 模仿开始下载            Log.i(TAG, "startDownload executed");        }        public int getProgress() {            // 模仿返回下载进度            Log.i(TAG, "getProgress executed");            return 0;        }    }    public MyService() {}    // 创立    @Override    public void onCreate() {        super.onCreate();        Log.i(TAG, "onCreate: ");    }    // 启动    @Override    public int onStartCommand(Intent intent, int flags, int startId) {        Log.i(TAG, "onStartCommand: ");        return super.onStartCommand(intent, flags, startId);    }    // 绑定    @Override    public IBinder onBind(Intent intent) {        Log.i(TAG, "onBind: ");        return mBinder;    }    // 解绑    @Override    public void unbindService(ServiceConnection conn) {        super.unbindService(conn);        Log.i(TAG, "unbindService: ");    }    // 销毁    @Override    public void onDestroy() {        super.onDestroy();        Log.i(TAG, "onDestroy: ");    }}

MainActivity.java如下:

public class MainActivity extends AppCompatActivity {    private MyService.DownloadBinder downloadBinder;    ServiceConnection connection = new ServiceConnection() {        @Override        public void onServiceConnected(ComponentName name, IBinder service) {            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);    }    public void aboutService(View view) {        int id = view.getId();        Intent intent = new Intent(this, MyService.class);        switch (id){            case R.id.start_btn:                startService(intent);                break;            case R.id.stop_btn:                stopService(intent);                break;            case R.id.bind_btn:                // 这里传入BIND_AUTO_CREATE示意在流动和服务进行绑定后主动创立服务                bindService(intent, connection, BIND_AUTO_CREATE);                break;            case R.id.unbind_btn:                unbindService(connection);                break;        }    }}

这个ServiceConnection的匿名类外面重写了onServiceConnected()办法和 onServiceDisconnected()办法,这两个办法别离会在流动与服务胜利绑定以及解除绑定的时候调用。在 onServiceConnected()办法中,通过向下转型失去DownloadBinder的实例,有了这个实例,流动和服务之间的关系就变得十分严密了。当初咱们能够在流动中依据具体的场景来调用DownloadBinder中的任何public()办法,即实现了指挥服务干什么服务就去干什么的性能(尽管实现startDownload与getProgress实现很简略)。

须要留神的是,任何一个服务在整个应用程序范畴内都是通用的,即 MyService不仅能够和MainActivity绑定,还能够和任何一个其余的流动进行绑定,而且在绑定实现后它们都能够获取到雷同的DownloadBinder实例。

服务的生命周期

一旦调用了startServices()办法,对应的服务就会被启动且回调onStartCommand(),如果服务未被创立,则会调用onCreate()创立Service对象。服务被启动后会始终放弃运行状态,直到stopService()或者stopSelf()办法被调用。不论startService()被调用了多少次,然而只有Service对象存在,onCreate()办法就不会被执行,所以只须要调用一次stopService()或者stopSelf()办法就会进行对应的服务。

在通过bindService()来获取一个服务的长久连贯的时候,这时就会回调服务中的 onBind()办法。相似地,如果这个服务之前还没有创立过,oncreate()办法会先于onBind()办法执行。之后,调用方能够获取到onBind()办法里返回的IBinder对象的实例,这样就能自在地和服务进行通信了。只有调用方和服务之间的连贯没有断开,服务就会始终放弃运行状态。

那么即调用了startService()又调用了bindService()办法的,这种状况下该如何能力让服务销毁掉呢?依据Android零碎的机制,一个服务只有被启动或者被绑定了之后,就会始终处于运行状态,必须要让以上两种条件同时不满足,服务能力被销毁。所以,这种状况下要同时调用stopService()和 unbindService()办法,onDestroy()办法才会执行。

服务的更多技巧

下面讲述了服务最根本的用法,上面来看看对于服务的更高级的技巧。

应用前台服务

服务简直都是在后盾运行的,服务的零碎优先级还是比拟低的,当零碎呈现内存不足的状况时,就有可能会回收掉正在后盾运行的服务。如果你心愿服务能够始终放弃运行状态,而不会因为零碎内存不足的起因导致被回收,就能够应用前台服务。比方QQ电话的悬浮窗口,或者是某些天气利用须要在状态栏显示天气。

public class FrontService extends Service {    String mChannelId = "1001";    public FrontService() {    }    @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();        Intent intent = new Intent(this, MainActivity.class);        PendingIntent pi = PendingIntent.getActivity(this, 0, intent, 0);        Notification notification = new NotificationCompat.Builder(this, mChannelId)                .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);    }}

应用IntentService

服务中的代码都是默认运行在主线程当中的,如果间接在服务里去解决一些耗时的逻辑,就很容易呈现ANR的状况。所以须要用到多线程编程,遇到耗时操作能够在服务的每个具体的办法里开启一个子线程,而后在这里去解决那些耗时的逻辑。就能够写成如下模式:

public class OtherService extends Service {    public OtherService() {}    @Override    public int onStartCommand(Intent intent, int flags, int startId) {        new Thread(()->{            // TODO 执行耗时操作        }).start();        return super.onStartCommand(intent, flags, startId);    }    ...}

然而,这种服务一旦启动之后,就会始终处于运行状态,必须调用stopService()或者stopSelf()办法能力让服务停止下来。所以,如果想要实现让一个服务在执行结束后主动进行的性能,就能够这样写:

public class OtherService extends Service {    public OtherService() {}    @Override    public int onStartCommand(Intent intent, int flags, int startId) {        new Thread(()->{            // TODO 执行耗时操作            stopSelf();        }).start();        return super.onStartCommand(intent, flags, startId);    }    ...}

尽管这种写法并不简单,然而总会有一些程序员遗记开启线程,或者遗记调用stopSelf()办法。为了能够简略地创立一个异步的、会主动进行的服务,Android 专门提供了一个IntentService类,这个类就很好地解决了后面所提到的两种难堪,上面咱们就来看一下它的用法:

MyIntentService.java

public class MyIntentService extends IntentService {    private static final String TAG = "MyIntentService";    private int count = 0;    public MyIntentService() {        super("MyIntentService");    }    @Override    protected void onHandleIntent(Intent intent) {        count++;        Log.i(TAG, "onHandleIntent: count = " + count);    }}

MainActivity.java:

for (int i = 0; i < 10; i++) {    Intent intent = new Intent(MainActivity.this, MyIntentService.class);    startService(intent);}

参考资料:《第一行代码》

原文地址:《后盾默默的劳动者,探索服务》