服务做为Android四大组件之一,是一种可在后台执行长时间运行操做而不提供界面的应用组件。服务可由其余应用组件启动,并且即便用户切换到其余应用,服务仍将在后台继续运行。须要注意的是服务并不会自动开启线程,全部的代码都是默认运行在主线程当中的,因此须要在服务的内部手动建立子线程,并在这里执行具体的任务,不然就有可能出现主线程被阻塞住的状况。html
<!-- more -->java
关于多线程编程其实和Java一致,不管是继承Thread仍是实现Runnable接口均可以实现。在Android中须要掌握的就是在子线程中更新UI,UI是由主线程来控制的,因此主线程又称为UI线程。android
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。
一、Message:线程之间传递的消息,它能够在内部携带少许的信息,用于在不一样线程之间交换数据。
二、Handler:处理者,它主要是用于发送和处理消息的。发送消息通常是使用Handler的sendMessage()方法,而发出的消息通过一系列地展转处理后,最终会传递到Handler的handleMessage()方法中。
三、MessageQueue:消息队列,它主要用于存放全部经过Handler发送的消息。这部分消息会一直存在于消息队列中,等待被处理。每一个线程中只会有一个MessageQueue对象。编程
四、Looper是每一个线程中的MessageQueue的管家,调用Looper的loop()方法后,就会进入到一个无限循环当中,而后每当发现 MessageQueue 中存在一条消息,就会将它取出,并传递到Handler的handleMessage()方法中。每一个线程中也只会有一个Looper对象。多线程
异步消息处理整个流程:首先须要在主线程当中建立一个Handler 对象,并重写handleMessage()方法。而后当子线程中须要进行UI操做时,就建立一个Message对象,并经过Handler将这条消息发送出去。以后这条消息会被添加到MessageQueue的队列中等待被处理,而Looper则会一直尝试从MessageQueue 中取出待处理消息,最后分发回 Handler 的handleMessage()方法中。因为Handler是在主线程中建立的,因此此时handleMessage()方法中的代码也会在主线程中运行,因而咱们在这里就能够安心地进行UI操做了。整个异步消息处理机制的流程以下图所示:app
不过为了更加方便咱们在子线程中对UI进行操做,Android还提供了另一些好用的工具,好比AsyncTask。AsyncTask背后的实现原理也是基于异步消息处理机制,只是Android帮咱们作了很好的封装而已。首先来看一下AsyncTask的基本用法,因为AsyncTask是一个抽象类,因此若是咱们想使用它,就必需要建立一个子类去继承它。在继承时咱们能够为AsyncTask类指定3个泛型参数,这3个参数的用途以下:异步
Params:在执行AsyncTask时须要传入的参数,可用于在后台任务中使用。
Progress:后台任务执行时,若是须要在界面上显示当前的进度,则使用这里指定的泛型做为进度单位。
Result:当任务执行完毕后,若是须要对结果进行返回,则使用这里指定的泛型做为返回值类型。ide
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("未点击下载"); } } }
一、onPreExecute():方法会在后台任务开始执行以前调用,用于进行一些界面上的初始化操做,好比显示一个进度条对话框等。工具
二、doInBackground():方法中的全部代码都会在子线程中运行,咱们应该在这里去处理全部的耗时任务。任务一旦完成就能够经过return语句来将任务的执行结果返回,若是 AsyncTask的第三个泛型参数指定的是Void,就能够不返回任务执行结果。注意,在这个方法中是不能够进行UI操做的,若是须要更新UI元素,好比说反馈当前任务的执行进度,能够调用publishProgress()方法来完成。
三、onProgressUpdate():当在后台任务中调用了publishProgress()方法后,onProgressUpdate()方法就会很快被调用,该方法中携带的参数就是在后台任务中传递过来的。在这个方法中能够对UI进行操做,利用参数中的数值就能够对界面元素进行相应的更新。
四、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); } }
服务中的代码都是默认运行在主线程当中的,若是直接在服务里去处理一些耗时的逻辑,就很容易出现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); }
参考资料:《第一行代码》
原文地址: 《后台默默的劳动者,探究服务》