知识点目录

  • 10.1 服务是什么
  • 10.2 Android多线程编程
    * 10.2.1 线程的基本用法
    * 10.2.2 在子线程中更新UI
    * 10.2.3 解析异步消息处理机制
    * 10.2.4 使用AsyncTask
  • 10.3 服务的基本用法
    * 10.3.1 定义一个服务
    * 10.3.2 启动和停止服务
    * 10.3.3 活动和服务进行通信
  • 10.4 服务的生命周期
  • 10.5 服务的更多技巧
    * 10.5.1 使用前台服务
    * 10.5.2 使用IntentService
  • 10.6 服务的最佳实践——完整版的下载示例
  • 10.7 小结与点评

知识点回顾

10.1 服务是什么

服务(Service)是Android中实现程序后台运行的解决方案。它非常适合去执行一些不需要和用户交互而且还要求长期运行的任务。

  • 服务并不是运行在独立的进程中,而是依赖创建服务时所在的应用程序进程

  • 服务并不会自动开启线程,所有的代码都是默认运行在主线程中。

正常情况下,我我们都需要手动创建子线程,并在子线程中执行具体的任务,否则就有可能出现主线程被阻塞住的情况。

10.2 Android多线程编程

当我们需要执行一些耗时操作(例如:网络请求)时,都会将这些操作放在子线程中去运行,否则容易被阻塞住,从而影响用户对软件的正常使用。

10.2.1 线程的基本用法

创建线程的方式一般有如下两种:

1. 继承Thread

新建一个类继承Thread,然后重写父类的run()方法,在run()方法里面写耗时逻辑。

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

然后new出一个MyThread实例,调用它的start()方法,这样run()方法中的代码就会在子线程中运行。

new MyThread().start();

2. 实现Runnable接口

继承的耦合性太高,我们可以通过实现Runnable接口的方式来定义一个线程。

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

启动线程的方式如下:

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

3. 匿名类

如果不想去专门再定义一个类去实现Runnable接口,那么也可以使用匿名类的形式,这种方式更加常见。

new Thread(new Runnable() {@Overridepublic void run() {// 处理具体的逻辑}
}).start();
10.2.2 在子线程中更新UI

下面我们写一个Demo在子线程中更新UI看看效果。

1.布局文件

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"android:layout_width="match_parent"android:layout_height="match_parent"><Buttonandroid:id="@+id/change_text"android:layout_width="match_parent"android:layout_height="wrap_content"android:textAllCaps="false"android:text="Change text"/><TextViewandroid:id="@+id/text"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_centerInParent="true"android:textAllCaps="false"android:text="Hello world"android:textSize="20sp"/></RelativeLayout>

2.在子线程中更新UI

    protected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);mText = (TextView) findViewById(R.id.text);Button changeText = (Button) findViewById(R.id.change_text);changeText.setOnClickListener(this);}@Overridepublic void onClick(View v) {switch (v.getId()) {case R.id.change_text://开一个子线程更新UInew Thread(new Runnable() {@Overridepublic void run() {mText.setText("Nice to meet you");}}).start();break;}}
}

运行程序,并点击"Change text"按钮。程序会直接崩溃:

观察logcat中的错误日志如下:

为了解决这种情况,Android提供了一套异步消息处理机制,很好地解决了子线程中进行UI操作的问题。

异步消息机制的基本用法:

public class MainActivity extends AppCompatActivity implements View.OnClickListener {public static final int UPDATE_TEXT = 1; // 定义个整型常量,用于表示Handler中某个动作private TextView mText;private Handler mHandler = new Handler() {@Overridepublic void handleMessage(Message msg) {switch (msg.what) {case UPDATE_TEXT://在这里进行UI操作mText.setText("Nice to meet you");break;}}};@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);mText = (TextView) findViewById(R.id.text);Button changeText = (Button) findViewById(R.id.change_text);changeText.setOnClickListener(this);}@Overridepublic void onClick(View v) {switch (v.getId()) {case R.id.change_text:new Thread(new Runnable() {@Overridepublic void run() {Message message = new Message();message.what = UPDATE_TEXT;mHandler.sendMessage(message); //将Message对象发出去}}).start();break;default:break;}}
}
10.2.3 解析异步消息处理机制

在了解了Android异步消息处理的基本用法后,我们来深入学习下异步消息机制的原理。

Android异步消息处理主要由4个部分组成:Message、Handler、MessageQueue和Looper。

Message

在线程之间传递消息,可以在内部携带少量的信息,主要用于在不同线程之间交换数据。

Handler

主要用于发送和处理消息。sendMessage()方法用于发送消息,handleMessage()方法用于处理消息。

MessageQueue

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

Looper

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

综上所述:异步消息处理的整个流程如下:

  • 主线程中创建一个Handler对象,并重写handleMessage()方法

  • 子线程中需要进行UI操作时,创建一个Message对象,并通过Handler的sendMessage()方法将这条消息发送出去

  • 发送出去的消息被添加到MessageQueue中等待被处理

  • Looper会一直尝试从MessageQueue中取出待处理的消息

  • 分发到Handler的handleMessage()方法中进行处理

整个异步消息处理机制的流程示意图如下所示:

前面使用的runOnUiThread()方法就是一个异步消息处理机制的接口封装。

10.2.4 使用AsyncTask

AsyncTask是Android帮我们封装好的对异步消息处理的工具,背后实现的原理也是异步消息处理机制。

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

  • 第一个参数Params。可用于在后台任务中使用

  • 第二个参数Progress。后台任务执行时,如果需要在界面上显示当前进度,则使用这里指定的泛型作为进度单位

  • 第三个参数Result。当任务执行完毕,如果需要对结果进行返回,则使用这里指定的泛型作为返回值类型

基本使用方法:

class DownloadTask extends AsyncTask <Void,Integer,Boolean>{/*** 在后台任务开始之前调用* 用于一些界面的初始化操作,例如显示一个进度条对话框* 主线程中运行*/@Overrideprotected void onPreExecute() {super.onPreExecute();//显示进度对话框progressDialog.show();}/***在这里处理所有的耗时操作* 如果需要更新UI元素,例如反馈当前任务的执行进度,可以调用publishProgress(Progress...)* 如果任务完成可以通过return语句来将任务执行的结果返回,返回类型是AsyncTask中的第三参数* 如果AsyncTask的第三个参数是Void,那么就可以不返回任务执行的结果* 子线程中运行* @param voids* @return*/@Overrideprotected Boolean doInBackground(Void... voids) {try {while (true) {int downloadPercent = doDownload(); //这是一个虚构的方法publishProgress(downloadPercent);if (downloadPercent >= 100) {break;}}} catch (Exception e) {return false;}return true;}/*** 当doInBackground()中调用了publishProgress(Progress...),则此方法会很快被调用* 该方法中携带的参数就是在后台任务中传递多来的* 可以对UI进行操作,利用参数中的值可以对界面上的元素进行更新* 主线程中运行* @param values*/@Overrideprotected void onProgressUpdate(Integer... values) {//在这里更新下载进度progressDialog.setMessage();}/*** 当doInBackground()中调用了return语句时,这个方法会很快被调用* 返回的数据作为参数传递到此方法中* 可以利用返回的数据来进行一些UI操作,比如提醒任务执行的结果或关闭掉进度条对话框等* 主线程中运行* @param aBoolean*/@Overrideprotected void onPostExecute(Boolean aBoolean) {//关闭对话框progressDialog.dismiss();if (aBoolean) {Toast.makeText(MainActivity.this, "Download succeeded", Toast.LENGTH_SHORT).show();} else {Toast.makeText(MainActivity.this, "Download failed", Toast.LENGTH_SHORT).show();}}
}

AsyncTask的用法就是:

  • 在onPreExecute()中做一些初始化工作

  • 在doInBackground()中执行具体的耗时操作

  • 在onProgressUpdate()中进行UI操作

  • 在onPostExecute()中执行一些任务的收尾工作

10.3 服务的基本用法

10.3.1 定义一个服务

可以通过Android Studio的快捷键来创建:

通过这样创建的Service,会自动在AndroidManifest.xml中注册。

public class MyService extends Service {public MyService() {}/*** 在服务创建的时候调用*/@Overridepublic void onCreate() {super.onCreate();}/*** 在每次服务启动的时候调用* @param intent* @param flags* @param startId* @return*/@Overridepublic int onStartCommand(Intent intent, int flags, int startId) {return super.onStartCommand(intent, flags, startId);}@Overridepublic IBinder onBind(Intent intent) {// TODO: Return the communication channel to the service.throw new UnsupportedOperationException("Not yet implemented");}/*** 在服务销毁的时候调用,一般用于回收那些不再使用的资源*/@Overridepublic void onDestroy() {super.onDestroy();}
}
10.3.2 启动和停止服务

启动和停止Service的方法都是借助Intent来实现的。

@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;}
}

服务启动后,可以在Settings—>Developer options—>Running servcies中找到它,如下图所示:

onCreate()与onStartCommand()的区别:

  • onCreate()只在服务第一次创建的时候使用

  • onStartCommand()是在服务每次启动的时候都会调用

10.3.3 活动和服务进行通信

如果在活动中想决定何时开始下载和随时查看下载进度,我们可以创建一个专门的Binder对象来对下载功能进行管理。同时借助onBind()方法。

public class MyService extends Service {private static final String TAG = "MyService";public MyService() {}private DownloadBinder mDownloadBinder = new DownloadBinder();class DownloadBinder extends Binder {public void startDownload() {Log.d(TAG, "startDownload executed");}public int getProgress() {Log.d(TAG, "getProgress executed");return 0;}}/*** 在服务创建的时候调用*/@Overridepublic void onCreate() {super.onCreate();Log.d(TAG, "onCreate executed ");}@Overridepublic IBinder onBind(Intent intent) {return mDownloadBinder;}/*** 在服务销毁的时候调用,一般用于回收那些不再使用的资源*/@Overridepublic void onDestroy() {super.onDestroy();Log.d(TAG, "onDestroy executed");}
}

在活动中绑定和解绑服务:

public class MainActivity extends AppCompatActivity implements View.OnClickListener {private MyService.DownloadBinder mDownloadBinder;private ServiceConnection mConnection = new ServiceConnection() {@Overridepublic void onServiceConnected(ComponentName name, IBinder service) {mDownloadBinder = (MyService.DownloadBinder) service;mDownloadBinder.startDownload();mDownloadBinder.getProgress();}@Overridepublic void onServiceDisconnected(ComponentName name) {}};@Overrideprotected 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);}@Overridepublic void onClick(View v) {switch (v.getId()) {case R.id.bind_service:Intent bindIntent = new Intent(this, MyService.class);//BIND_AUTO_CREATE 表示在活动和服务进行绑定后自动创建服务bindService(bindIntent, mConnection, BIND_AUTO_CREATE); // 绑定服务break;case R.id.unbind_service:Intent unbindIntent = new Intent(this, MyService.class);unbindService(mConnection); // 解绑服务break;default:break;}}
}

备注:服务在整个应用程序范围内都是通用的,即可以和不同的活动进行绑定,而且在绑定完成后它们都可以获取到相同的DownloadBinder实例。

10.4 服务的生命周期

服务的生命周期主要有以下两种路径:

  • 启动服务

    该服务在其他组件调用startService()时创建,并回调onStartCommand()方法。如果这个服务之前没有创建过,onCreate()方法会先于onStartCommand()方法执行。直到调用stopService()或stopSelf()方法服务才会停止。注意:虽然每调用一次startService()方法onStartCommand()都会执行一次,但实际上每个服务都只会存在一个实例。

  • 绑定服务

    该服务在其他组件调用bindService()时创建,并回调服务中的onBind()方法。如果服务之前没有创建过,onCreate()方法会优于onBind()方法执行。客户端获取到onBind()方法里面返回的IBinder对象的实例,就能与服务端进行通信了。

当我们对服务同时进行了startService()和bindService()时,这时候我们需要同时调用stopService()和unbindService()方法,服务才会被销毁,onDestory()方法才会执行。

Service两种情况下生命周期图如下:

10.5 服务的更多技巧

10.5.1 使用前台服务

服务一直都在后台运行,优先级比较低,当系统出现内存不足的情况时,有可能被系统回收掉。如果想服务一直保持运行,可以考虑使用前台服务。

前台服务与后台服务的区别:

  • 不会被系统回收掉

  • 状态栏会有一个正在运行的图标,下拉状态栏后可以看到更加详细的信息。

让服务变成前台服务的方法如下:

public class MyService extends Service {....../*** 在服务创建的时候调用*/@Overridepublic void onCreate() {super.onCreate();Log.d(TAG, "onCreate executed ");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);}......}

具体做法就是创建一个Notification,然后调用startForeground()方法即可。

startForeground()有两个参数:

参数一:通知的id

参数二:构建的Notification对象。

点击StartService或BindService按钮后,MyService就会以前台服务的模式启动,并且在系统状态栏会显示一个通知图标,下拉状态栏后可以看到该通知的详细内容,如下图所示:

10.5.2 使用IntentService

IntentService是一种异步的,会自动停止的服务。

基本用法如下:

public class MyIntentService extends IntentService {private static final String TAG = "MyIntentService";public MyIntentService() {super("MyIntentService"); //调用父类的有参构造函数}/*** 处理一些耗时操作* 运行在子线程中,不会出现ANR问题* @param intent*/@Overrideprotected void onHandleIntent(Intent intent) {Log.d(TAG, "Thread id is " + Thread.currentThread().getId());}/*** 服务运行结束后,会自动停止*/@Overridepublic void onDestroy() {super.onDestroy();Log.d(TAG, "onDestroy executed");}
}

点击Start IntentServcie按钮后,打印log如下:

可以看出IntentServcie集开启线程和自动停止于一身。

10.6 服务的最佳实践——完整版的下载示例

完整版的功能需求:结合AsyncTask和Service实现开始下载、暂停下载和取消下载的功能。

代码已上传到我的github上,需要的朋友可以点击如下链接查看:

服务的最佳实践——完整版的下载示例

10.7 小结与点评

本章主要学习了Android多线程编程、服务的基本用法、服务的生命周期、前台服务和IntentService等。最后通过完整版下载示例对前面学习的知识进行了综合运用。

非常感谢您的耐心阅读,希望我的文章对您有帮助。欢迎点评、转发或分享给您的朋友或技术群。

第一行代码学习笔记第十章——探究服务相关推荐

  1. 第一行代码学习笔记第二章——探究活动

    知识点目录 2.1 活动是什么 2.2 活动的基本用法 2.2.1 手动创建活动 2.2.2 创建和加载布局 2.2.3 在AndroidManifest文件中注册 2.2.4 在活动中使用Toast ...

  2. 第一行代码学习笔记第六章——详解持久化技术

    知识点目录 6.1 持久化技术简介 6.2 文件存储 * 6.2.1 将数据存储到文件中 * 6.2.2 从文件中读取数据 6.3 SharedPreferences存储 * 6.3.1 将数据存储到 ...

  3. 第一行代码读书笔记(Chapter2 探究新语言,快速入门Kotlin编程)

    准确来说,Java是解释性语言,Kotlin能被编译为class文件,再在虚拟机中运行 Kotlin几乎杜绝了空指针异常 运行Kotlin代码:IDEA创建Kotlin项目:在线运行kotlin代码: ...

  4. 第一行代码学习笔记第七章——探究内容提供器

    知识点目录 7.1 内容提供器简介 7.2 运行权限 * 7.2.1 Android权限机制详解 * 7.2.2 在程序运行时申请权限 7.3 访问其他程序中的数据 * 7.3.1 ContentRe ...

  5. 第一行代码学习笔记第四章——探究碎片

    知识点目录 4.1 碎片是什么 4.2 碎片的使用方式 * 4.2.1 碎片的简单用法 * 4.2.2 动态添加碎片 * 4.2.3 在碎片中模拟返回栈 * 4.2.4 碎片和活动之间进行通信 4.3 ...

  6. 第一行代码学习笔记第八章——运用手机多媒体

    知识点目录 8.1 将程序运行到手机上 8.2 使用通知 * 8.2.1 通知的基本使用 * 8.2.2 通知的进阶技巧 * 8.2.3 通知的高级功能 8.3 调用摄像头和相册 * 8.3.1 调用 ...

  7. 第一行代码学习笔记第三章——UI开发的点点滴滴

    知识点目录 3.1 如何编写程序界面 3.2 常用控件的使用方法 * 3.2.1 TextView * 3.2.2 Button * 3.2.3 EditText * 3.2.4 ImageView ...

  8. 第一行代码学习笔记第九章——使用网络技术

    知识点目录 9.1 WebView的用法 9.2 使用HTTP协议访问网络 * 9.2.1 使用HttpURLConnection * 9.2.2 使用OkHttp 9.3 解析XML格式数据 * 9 ...

  9. 第一行代码学习笔记第五章——详解广播机制

    知识点目录 5.1 广播机制 5.2 接收系统广播 * 5.2.1 动态注册监听网络变化 * 5.2.2 静态注册实现开机广播 5.3 发送自定义广播 * 5.3.1 发送标准广播 * 5.3.2 发 ...

最新文章

  1. php开发问题及解决方案,PHP开源开发框架ZendFramework使用中常见问题说明及解决方案...
  2. 晓曼机器人能恢复出厂设置吗_手机出毛病了就恢复出厂设置,会损伤手机吗?影响有多大呢...
  3. REVERSE-PRACTICE-BUUCTF-1
  4. 作者:高翔(1984-),男,国防大学信息作战与指挥训练教研部博士后,主要研究方向为体系分析与超网建模。...
  5. win 7 或 mac 远程桌面到 ubuntu (ssh)
  6. 第二章课后习题2-5
  7. 软件工程导论 01章软件工程学概述
  8. 安装SQL Server 2012遇到“需要更新的以前的Visual Studio 2010实例.”
  9. Cent OS 下 VI 使用方法
  10. java编写个倒计时_怎么编写一个倒计时java程序?求具体步骤!
  11. 软考数据库工程师2020下午题@故障恢复解析
  12. js实现微信浏览器关闭
  13. Mac版3D动画建模渲染工具C4D R26
  14. circular包绘图笔记
  15. 深度学习基础知识每日更 upupup
  16. 多智能体系统的概念与结构
  17. 2017年5月历史文章汇总
  18. 日常随笔——如何判断字符是汉字、字母、还是拼音? 在C++中又该如何判断?
  19. [shell]两种方法写出99乘法表
  20. PPT调色技巧,调色小白们快来瞧瞧

热门文章

  1. NLP中对困惑度感到困惑?
  2. 上届作品回顾丨如何在 Innovation 2021 开发者大赛中脱颖而出?
  3. 技术实践 | 网易云信视频转码提速之分片转码
  4. KEILC51警告:WARNING L15: MULTIPLE CALL TO SEGMENT
  5. 基于环信的仿QQ即时通讯的简单实现
  6. 95后实习生的远程办公体验(asp.net mvc\C#技术栈)
  7. (转)那些年我们一起清除过的浮动
  8. GDataXML解析XML文档
  9. Android布局中涉及的一些属性
  10. 宽带拨号时出现错误列表