• 第一行代码Android 第十章 10.1-10.2(服务,线程,子线程中更新UI,异步消息处理机制,AsyncTask异步消息处理工具)


    第十章:后台默默地劳动者——探究服务
           后台功能属于四大组件之一,其重要程度不言而喻,那么我们自然要好好学习一下它的用法了

    10.1 服务是什么
            服务是Android中实现程序后台运行的解决方案,它非常适合去执行那些不需要和用户交互而且还要求长期运行的任务。服务的运行不依赖于任何用户界面,即使程序被切换到后台,或者用户打开了另一个应用程序,服务仍然能够保持正确运行。
            需要注意的是,服务并不是运行在一个独立的进程当中的,而是依赖于创建服务时所在的应用程序进程,当某个应用程序进程被杀掉时,所有依赖于该进程的服务也会停止运行。
            服务实际上并不会自动开启线程,所有的代码都是默认运行在主线程当中的。也就是说,我们需要在服务的内部手动创建子线程,并在这里执行具体的任务,否则就有可能出现主线程被阻塞住的情况。

    10.2 Android多线程编程
    10.2.1 线程的基本用法
            Android多线程编程其实并不比Java多线程编程特殊,基本都是使用相同的语法。
            1)新建一个类继承自Thread,然后重写run()方法

    1. class MyThread extends Thread {
    2. @Override
    3. public void run() {
    4. //处理具体的逻辑
    5. }
    6. }

            启动线程: new MyThread().start();
            2)实现Runnable接口的方式来定义一个线程

    1. class MyThread implements Runnable {
    2. @Override
    3. public void run() {
    4. //处理具体的逻辑
    5. }
    6. }

            启动线程:MyThread myThread = new Thread();
                              new Thread(myThread).start();
            3)匿名类的方式去实现Runnable接口

    1. new Thread(new Runnable() {
    2. @Override
    3. public void run() {
    4. //处理具体的逻辑
    5. }
    6. }).start();

    10.2.2 在子线程中更新UI
            和许多其他的GUI库一样,Android的UI也是线程不安全的。因此如果想要更新应用程序里的UI元素,则必须在主线程中进行,否则就会出现异常。
            Android提供了一套异步消息处理机制,用来解决在子线程中进行UI操作的问题。

    1. //修改MainActivity中的代码
    2. public class MainActivity extends AppCompatActivity implements View.OnClickListaner {
    3. public static final int UPDATE_TEXT = 1;
    4. private TextView text;
    5. private Handler handler = new Handler() {
    6. public void handleMessage(Message msg) {
    7. //对具体的Message信息进行处理
    8. switch (msg.what) {
    9. case UPDATE_TEXT:
    10. //在这里可以进行UI操作
    11. text.setText("Nice to meet you");
    12. break;
    13. default:
    14. break;
    15. }
    16. }
    17. };
    18. @Override
    19. protected void onCreate(Bundle savedInstanceState) {
    20. super.onCreate(savedInstanceState);
    21. setContentView(R.layout.activity_main);
    22. text = (TextView) findViewById(R.id.text); //显示文字
    23. Button changeText = (Button) findViewById(R.id.change_text); //点击按钮实现换文字
    24. changeText.setOnClickListener(this);
    25. }
    26. /*
    27. 这里创建了一个Message(android.os.Message)对象,并将它的what字段的值指定为UPDATE_TEXT
    28. 然后调用Handler的sendMessage()方法将这条Message发送出去
    29. 很快,Handler就会收到这条Message,并在HandlerMessage()方法中对它进行处理(注意这里handlerMessage()方法中的代码就是在主线程中运行的)
    30. */
    31. @Override
    32. public void onClick(View v) {
    33. switch (v.getId()) {
    34. case R.id.change_text:
    35. new Thread(new Runnable () {
    36. @Override
    37. public void run() {
    38. Message message = new Message();
    39. message.what = UPDATE_TEXT;
    40. handler.sendMessage(message); //将Message对象发送出去
    41. }
    42. }).start();
    43. default:
    44. break;
    45. }
    46. }
    47. }

    10.2.3 解析异步消息处理机制
            Android中的异步消息处理主要由4个部分组成:Message、Handler、MessageQueue和Looper。
            1)Message:Message是在线程之间传递的消息,它可以在内部携带少量的信息,用户在不同线程之间进行交换数据。(eg:Message.what携带的是what字段,还可以携带一些整型数据)
            2)Handler:处理者,主要用于发送和处理消息的,发送消息一般是使用Handler的sendMessage方法,经过一些列的辗转后最终传递到Handler的handleMessage方法中。
            3)MessageQueue:MessageQueue是消息队列的意思,它主要用于存放所有通过Handler发送的消息,这部分消息会一直存在于消息队列中,等待被处理。每个线程中只会有一个MessageQueue对象。
            4)Looper:每个线程中的MessageQueue的管家,调用Loop()方法后,就会进入到一个无限循环当中,然后每当发现MessageQueue中存在一条消息,就会将它取出,并传递到Handler的handleMessage()方法当中。每个线程中也只会有一个Looper对象。

    异步消息处理的整个流程:

            首先需要在主线程当中创建一个Handler对象,并重写handleMessage()方法。然后当子线程中需要进行UI操作时,就创建一个Message对象,并通过Handler将这条消息发送出去。之后这条消息会被添加到MessageQueue的队列中等待被处理,而Looper则会一直尝试从MessageQueue中取出待处理消息,最后分发会Handler的handleMessage()方法中。由于Handler是在主线程中创建的,所以此时handleMessage()方法中的代码也会在主线程中运行,于是我们就可以安心地进行UI操作了。这就是整个异步消息处理机制。

    10.2.4 使用AsyncTask
            AsyncTask工具更方便在子线程中进行UI操作,它的实现原理也是基于异步消息处理机制的,只是Android帮我们做了很好的封装而已。

    AsyncTask的基本用法:
    AsyncTask是个抽象类,要使用它的话需要创建一个子类去继承,在继承时可以为AsyncTask指定3个泛型参数,这3个参数的用途为:
    1)Params:执行AsyncTask时需要传入的参数,可用于在后台任务中使用。

    2)Progress:后台执行任务时需要在界面上显示当前的进度,则使用这里指定的泛型作为进度单位。
    3)Result:当任务执行完毕后,如果需要对结果进行返回,则使用这里指定的泛型作为返回值的类型

    1. //一个比较完整的AsyncTask如下
    2. class DownloadTask extends AsyncTask {
    3. @Override
    4. //这个方法在后台任务开始执行之前调用,进行一些界面上的初始化操作
    5. protected void onPreExcute() {
    6. progressDialog.show(); //显示进度对话框
    7. }
    8. @Override
    9. //这个方法在子线程中运行,在这里去处理所有的耗时任务,处理完成return返回
    10. //这里要处理UI的话,可以用过调用publishProgress方法来完成
    11. protected Boolean doInBackground(Void... params) {
    12. try {
    13. while(true) {
    14. int downloadPercent = doDownload(); //doDownload是一个假方法,用于计算当前的下载速度并返回(虚构的方法)
    15. //publishProgress方法通过handler发送信息,在主线程中接收消息的地方会执行到onProgressUpdate()方法去更新UI
    16. publishProgress(downloadPercent);
    17. if (downloadPercent > = 100) {
    18. break;
    19. }
    20. }
    21. } catch (Exception e) {
    22. return false;
    23. }
    24. return true;
    25. }
    26. @Override
    27. //在后台调用publishProgress方法后,会调用这个方法,这个方法中携带的参数就是后台任务中传递过来的参数
    28. protected void onProgressUpdate(Integer... values) {
    29. //在这里更新下载进度
    30. progressDialog.setMessage("Downloaded"+values[0]+"%");
    31. }
    32. @Override
    33. //这个方法是当后台执行完毕并通过return返回语句返回时,来调用的,携带的参数就是返回的数据,可以进行一些UI的操作
    34. protected void onPostExecute(Boolean result) {
    35. progressDialog.dismiss(); //关闭进度对话框
    36. //在这里提示下载结果
    37. if (result) {
    38. Toast.makeText(context,"Download succeeded",Toast.LENGTH_SHORT).show();
    39. } else {
    40. Toast.makeText(context,"Download failed",Toast.LENGTH_SHORT).show();
    41. }
    42. }
    43. }
  • 相关阅读:
    [山东科技大学OJ]1215 Problem C: 编写函数:字符串的复制 之一 (Append Code)
    排序题:数组中的第k个最大元素及出现的次数 - 数组的正态分布排序
    网络、HTTP、HTTPS、Session、Cookie、UDP、TCP
    ArcGIS中将测绘数据投影坐标(平面坐标)转地理坐标(球面经纬度坐标)
    elasticsearch 聚合之 date_histogram 聚合
    pNA修饰肽:H-D-Leu-Thr-Arg-pNA,H-D-Leu-Thr-Arg-pNA,CAS号: 122630-72-2/3326-63-4
    [杂谈]-从硬件角度理解二进制数
    什么台灯最好学生晚上用?开学适合学生用的护眼台灯推荐
    安装node-sass出现报错记录
    【LeetCode-102】二叉树的层序遍历
  • 原文地址:https://blog.csdn.net/XXX_17/article/details/126390161