• Handler: Service中使用Toast


    Handler 的使用在 android App 开发中用的颇多,它的作用也很大,使用 Handler 一般也会使用到多线程,相信大家对 Handler 不会陌生,在这里,重点说一下 android 组件之一 Service 与 TaskTimer 结合 Handler 使用,共享之!


    阅读这篇博客,需要你知道的知识:


    <1> 知道在 Activity 中如何启动、停止 Service 以及 Service 的生命周期。


    <2> 使用过 TimerTask 的Api,不过这个不难,如果之前没有接触过现在拿出几分钟学习一下吧!


    <3> Handler 基本用法,推荐博客:http://blog.csdn.net/androidbluetooth/article/details/6384641


    <4> Looper 基本用法,推荐下载:http://download.csdn.net/detail/AndroidBluetooth/3650576 好好看看,肯定对你有用!


    好嘞,开始说这篇博客的内容。

    界面很简单,就是两个Button,启动之后,效果如下:




    Activity 代码:

    1. package mark.zhang;  
    2.   
    3. import android.app.Activity;  
    4. import android.content.Intent;  
    5. import android.os.Bundle;  
    6. import android.util.Log;  
    7. import android.view.View;  
    8. import android.view.View.OnClickListener;  
    9.   
    10. public class ServiceToastActivity extends Activity {  
    11.   
    12.     @Override  
    13.     public void onCreate(Bundle savedInstanceState) {  
    14.         super.onCreate(savedInstanceState);  
    15.         setContentView(R.layout.main);  
    16.           
    17.         Log.d("mark""activity: " + " " + "当前线程名称:"  
    18.                 + Thread.currentThread().getName() + "," + "当前线程名称:"  
    19.                 + Thread.currentThread().getId());  
    20.         // 启动服务  
    21.         findViewById(R.id.button_startservice).setOnClickListener(  
    22.                 new OnClickListener() {  
    23.   
    24.                     @Override  
    25.                     public void onClick(View v) {  
    26.                         Intent intent = new Intent(ServiceToastActivity.this,  
    27.                                 MyService.class);  
    28.                         startService(intent);  
    29.                     }  
    30.                 });  
    31.         // 停止服务  
    32.         findViewById(R.id.button_stopservice).setOnClickListener(  
    33.                 new OnClickListener() {  
    34.   
    35.                     @Override  
    36.                     public void onClick(View v) {  
    37.                         Intent intent = new Intent(ServiceToastActivity.this,  
    38.                                 MyService.class);  
    39.                         stopService(intent);  
    40.                     }  
    41.                 });  
    42.     }  
    43. }  

    上述代码就是给两个 Button 设置监听器,启动和停止服务。

    然后,在 Service 中 开启一个线程,并在该线程中 Toast 一下!代码如下:

    1. package mark.zhang;  
    2.   
    3. import java.util.Timer;  
    4. import java.util.TimerTask;  
    5.   
    6. import android.app.Service;  
    7. import android.content.Intent;  
    8. import android.os.Handler;  
    9. import android.os.IBinder;  
    10. import android.os.Looper;  
    11. import android.util.Log;  
    12. import android.widget.Toast;  
    13.   
    14. public class MyService extends Service {  
    15.   
    16.     private Handler handler = null;  
    17.     private Timer timer;  
    18.   
    19.       
    20.     @Override  
    21.     public IBinder onBind(Intent intent) {  
    22.         return null;  
    23.     }  
    24.   
    25.     @Override  
    26.     public void onCreate() {  
    27.         super.onCreate();  
    28.           
    29.         new Thread(new Runnable() {  
    30.             public void run() {  
    31.                 Log.d("mark""Service in Thread: " + " " + "当前线程名称:"  
    32.                         + Thread.currentThread().getName() + "," + "当前线程名称:"  
    33.                         + Thread.currentThread().getId());  
    34.                 Toast.makeText(MyService.this"Service中子线程启动!", Toast.LENGTH_LONG).show();//程序奔溃  
    35.             }  
    36.         }).start();  
    37.     }  
    38.   
    39.     @Override  
    40.     public int onStartCommand(Intent intent, int flags, int startId) {  
    41.           
    42.         Log.d("mark""Service: " + " " + "当前线程名称:"  
    43.                 + Thread.currentThread().getName() + "," + "当前线程名称:"  
    44.                 + Thread.currentThread().getId());  
    45.           
    46.         Toast.makeText(this"启动服务成功!", Toast.LENGTH_LONG).show();  
    47.         return super.onStartCommand(intent, flags, startId);  
    48.     }  
    49.   
    50.     @Override  
    51.     public void onDestroy() {  
    52.         super.onDestroy();  
    53.     }  
    54. }  

    点击界面的“启动服务”,打印信息:

    1. D/mark    (  310): activity:   
    2. D/mark    (  310): 当前线程名称:main,当前线程名称:1  
    3. D/mark    (  310): Service:   
    4. D/mark    (  310): 当前线程名称:main,当前线程名称:1  
    5. D/mark    (  310): Service in Thread:   
    6. D/mark    (  310): 当前线程名称:Thread-8,当前线程名称:8  
    从打印信息可以看出,Service 与 Activity 在同一个线程程(main线程)。

    如果你复制我的代码实际运行一下,你会发现子线程中的 Toast 根本没有起作用,并且程序会崩溃,显示异常如下:



    1. 02 05:25:32.828: ERROR/AndroidRuntime(325): java.lang.RuntimeException: Can't create handler inside thread that has not called Looper.prepare()    

    这下应该明白,在子线程直接 Toast 是错误的!根据提示信息,我们需要调用 Looper.prepare(), 根据 Looper 的 Api 说明,我们还应该调用 Looper.loop(),那麽我们修改一下代码,将 new Thread中的代码修改如下:

    1. new Thread(new Runnable() {  
    2.             public void run() {  
    3.                 Log.d("mark""Service in Thread: " + " " + "当前线程名称:"  
    4.                         + Thread.currentThread().getName() + "," + "当前线程名称:"  
    5.                         + Thread.currentThread().getId());  
    6.                 Looper.prepare();  
    7.                 Toast.makeText(MyService.this"Service中子线程启动!", Toast.LENGTH_LONG).show();  
    8.                 Looper.loop();  
    9.             }  
    10.         }).start();  
    ok,这次一切正常。关于 Looper 作用在 http://download.csdn.net/detail/AndroidBluetooth/3650576 中说的很明白,这里不再赘述!


    接着看看在Service中如何使用 TimerTask 以及 Toast。Activity 的代码不变,修改 Service 代码:

    1. package mark.zhang;  
    2.   
    3. import java.util.Timer;  
    4. import java.util.TimerTask;  
    5.   
    6. import android.app.Service;  
    7. import android.content.Intent;  
    8. import android.os.IBinder;  
    9. import android.util.Log;  
    10. import android.widget.Toast;  
    11.   
    12. public class MyService extends Service {  
    13.   
    14.     private Timer timer;  
    15.   
    16.     private TimerTask task = new TimerTask() {  
    17.   
    18.         @Override  
    19.         public void run() {  
    20.   
    21.             Log.d("mark""task: " + " " + "当前线程名称:"  
    22.                     + Thread.currentThread().getName() + "," + "当前线程名称:"  
    23.                     + Thread.currentThread().getId());  
    24.             Toast.makeText(getApplicationContext(), "呵呵,您好!",  
    25.                     Toast.LENGTH_SHORT).show();  
    26.         }  
    27.     };  
    28.   
    29.     @Override  
    30.     public IBinder onBind(Intent intent) {  
    31.         return null;  
    32.     }  
    33.   
    34.     @Override  
    35.     public void onCreate() {  
    36.         super.onCreate();  
    37.         // 当前Task中线程的名称为myservice  
    38.         timer = new Timer("myservice");  
    39.     }  
    40.   
    41.     @Override  
    42.     public int onStartCommand(Intent intent, int flags, int startId) {  
    43.         // 100ms之后,每隔5000ms启动定时器  
    44.         timer.scheduleAtFixedRate(task, 1005000);  
    45.         return super.onStartCommand(intent, flags, startId);  
    46.     }  
    47.   
    48.     @Override  
    49.     public void onDestroy() {  
    50.         super.onDestroy();  
    51.         timer.cancel();  
    52.     }  
    53. }  

    启动服务之后,启动 TimerTask,在 TimerTask 中每隔5秒中 Toast 一下,在终止服务的时候取消 Timer。

    打印信息如下:

    1. D/mark    (  441): activity:   
    2. D/mark    (  441): 当前线程名称:main,当前线程名称:1  
    3. D/mark    (  441): task:   
    4. D/mark    (  441): 当前线程名称:myservice,当前线程名称:8  
    5. D/mark    (  441): task:   
    6. D/mark    (  441): 当前线程名称:myservice,当前线程名称:8  
    7. D/mark    (  441): task:   
    8. D/mark    (  441): 当前线程名称:myservice,当前线程名称:8  
    9. D/mark    (  441): task:   
    10. D/mark    (  441): 当前线程名称:myservice,当前线程名称:8  
    11. D/mark    (  441): task:   
    12. D/mark    (  441): 当前线程名称:myservice,当前线程名称:8  

    可以看出,TimerTask开启一个线程(名称为myservice,线程id是8),按照原来的想法一样,每隔五秒 TimerTask 的run() 方法会执行一次,直到 “停止服务”,但是 Toast 并没有起作用。看来我们需要修改代码。

    当然,可以像上面那样使用 Looper 的两个静态方法prepare()、loop(),可以保证Toast 完美运行,但是肯定还有其它办法,仔细看来,呵呵!

    修改 Service 代码,这次主要使用 Handler:

    1. package mark.zhang;  
    2.   
    3. import java.util.Timer;  
    4. import java.util.TimerTask;  
    5.   
    6. import android.app.Service;  
    7. import android.content.Intent;  
    8. import android.os.Handler;  
    9. import android.os.IBinder;  
    10. import android.os.Looper;  
    11. import android.util.Log;  
    12. import android.widget.Toast;  
    13.   
    14. public class MyService extends Service {  
    15.     private Handler handler;  
    16.   
    17.     private Timer timer;  
    18.   
    19.     private TimerTask task = new TimerTask() {  
    20.   
    21.         @Override  
    22.         public void run() {  
    23.             handler.post(new Runnable() {//**********在线程中使用Toast  
    24.               
    25.             @Override  
    26.             public void run() {  
    27.                 Toast.makeText(getApplicationContext(), "呵呵,您好!",  
    28.                         Toast.LENGTH_SHORT).show();  
    29.                   
    30.                 Log.d("mark""service in Handler run: " + " " + "当前线程名称:"  
    31.                         + Thread.currentThread().getName() + ","  
    32.                         + "当前线程名称:" + Thread.currentThread().getId());  
    33.             }  
    34.             });  
    35.         }  
    36.     };  
    37.   
    38.     @Override  
    39.     public IBinder onBind(Intent intent) {  
    40.         return null;  
    41.     }  
    42.   
    43.     @Override  
    44.     public void onCreate() {  
    45.         super.onCreate();  
    46.         // 为当前线程获得looper  
    47.         handler = new Handler(Looper.getMainLooper());  
    48.         // 当前Task中线程的名称为myservice  
    49.         timer = new Timer("myservice");  
    50.     }  
    51.   
    52.     @Override  
    53.     public int onStartCommand(Intent intent, int flags, int startId) {  
    54.         // 100ms之后,每隔5000ms启动定时器  
    55.         timer.scheduleAtFixedRate(task, 1005000);  
    56.         return super.onStartCommand(intent, flags, startId);  
    57.     }  
    58.   
    59.     @Override  
    60.     public void onDestroy() {  
    61.         super.onDestroy();  
    62.         timer.cancel();  
    63.     }  
    64. }  
    运行程序,一切ok!我们还是分析一下打印信息吧!

    1. D/mark    (  495): activity:   
    2. D/mark    (  495): 当前线程名称:main,当前线程名称:1  
    3. D/mark    (  495): service in Handler run:   
    4. D/mark    (  495): 当前线程名称:main,当前线程名称:1  
    5. D/mark    (  495): service in Handler run:   
    6. D/mark    (  495): 当前线程名称:main,当前线程名称:1  
    7. D/mark    (  495): service in Handler run:   
    8. D/mark    (  495): 当前线程名称:main,当前线程名称:1  
    我们使用 Handler 的 post(Runnable r) 方法,在 run 方法中 Toast,根据 Timer 的定时每隔5秒就会 Toast 一下。

    这里还需要提醒大家一句:在子线程中我们不可以直接 new Handler(),但是在 TimerTask 的 run() 方法中直接 new Handler() 是没有问题的。TimerTask 的确开启一个子线程,但是为什么在这里可以直接创建 Handler 对象呢?


    如果,你有兴趣可以继续:

    学习 android 的 Looper 源码以及 TimerTask 设计理念。


    多说一句:在 Service 中不可以显示对话框,如果想通过 Service 来显示对话框需要使用 Handler 通知 Activity 来显示对话框。

    在 Activity 子线程中显示对话框,可以这样做:

    1. Looper.prepare();  
    2. showMyDialog();  
    3. Looper.loop();  

    或者使用 Handler,呵呵.

    绝大部分参考:网站,小部分是自己在使用过程中的理解,感谢原作者!

  • 相关阅读:
    [译]:Xamarin.Android开发入门——Hello,Android Multiscreen深入理解
    [译]:Xamarin.Android开发入门——Hello,Android Multiscreen快速上手
    [译]:Xamarin.Android开发入门——Hello,Android深入理解
    [译]:Xamarin.Android开发入门——Hello,Android快速上手
    [译]:Orchard入门——使用标签管理内容
    [译]:Orchard入门——部件管理
    swift UITableView cell自适应高度
    设置Launch Image 启动图片(Xcode7)
    APNs 远程推送
    KVO (Key-Value-Observer, 键值观察)
  • 原文地址:https://www.cnblogs.com/sowhat4999/p/4439840.html
Copyright © 2020-2023  润新知