zoukankan      html  css  js  c++  java
  • Android学习笔记(三一):线程:Message和Runnable

    程序需要相应用户的操作,最要能在200ms(0.2s)之内,如果超过5秒没有反应,ActivityManager会没有提示就kill了activity。然而,activity可能真的需要时间来进行处理,这往往会用到后台线程-background thread。后台线程可以安全地和UI线程进行交互,其中后台线程是不能修改UI的。我不太确切知晓“不能修改UI”到何种程度,例如在下面的例子进度条状态的修改是允许的,在复杂的例子中可能会出现问题,例如两个后台线程都要处理同一个widget,可能会有不可预知的情况出现,然而就编程而言,确实不应当如此处理,后台进程应避免涉及UI的处理,UI的归UI,处理的规处理。这样才是良好的编程风格或者是一种编程原则。

    通过创建一个Handler子类的对象,每个acvivity只需一个Handler对象。后台进程可通过两种方式Handler进行通信:message和Runnable对象,其结果实质都是将在Handler的队列中放入内容,message是放置信息,可以传递一些参数,Handler获取这些信息并将判度如何处理,而Runnable则是直接给出处理的方法。队列就是依次执行,Handler会处理完一个消息或者执行完某个处理在进行下一步,这样不会出现多个线程同时要求进行UI处理而引发的混乱现象。

    这些队列中的内容(无论Message还是Runnable)可以要求马上执行,延迟一定时间执行或者指定某个时刻执行,如果将他们放置在队列头,则表示具有最高有限级别,立即执行。这些函数包括有:sendMessage(), sendMessageAtFrontOfQueue(), sendMessageAtTime(), sendMessageDelayed()以及用于在队列中加入Runnable的post(), postAtFrontOfQueue(), postAtTime(),postDelay()。

    一般而言,推荐是Messge方式,这样程序设计得可以更为灵活,而Runnable在某些简单明确的方式中使用。我们将通过三种方法编写一个小例子来学习。这个例子是一个进度条,每隔1秒,进度条步进5,如果acvity停止时,进度条归零。

    Android XML

    <?xml version="1.0" encoding="utf-8"?> <LinearLayout ...... />   <ProgressBar android:id="@+id/c15_progress"
       style="?android:attr/progressBarStyleHorizontal" <!-- 这表明采用传统水平进度条的方式-->     android:layout_width="fill_parent"     android:layout_height="wrap_content" /> </LinearLayout>

    例子一:线程开启,采用Message传递后台线程和UI主线程之间的信息

    public class Chapter15Test1 extends Activity{     private ProgressBar bar = null;     private boolean isRunning = false;        /* 我们为这个Acivity创建一个用于和后台程序通信的handler,简单地,只要一收到message,就将progressbar进度增加5。*/    /* 步骤1:创建Handler,并通过handleMessage()给出当收到消息是UI需要进行如何处理,例子简单不对msg的内容进行分析*/     Handler handler= new Handler(){         public void handleMessage(Message msg) {             bar.incrementProgressBy(5);         }     };         protected void onCreate(Bundle savedInstanceState) {         super.onCreate(savedInstanceState);         setContentView(R.layout.chapter_15_test1);         bar=(ProgressBar)findViewById(R.id.c15_progress);     }     /*on Start是UI初始化并显示时调用*/     protected void onStart() {         super.onStart();         bar.setProgress(0);        /*步骤2:建立后台线程处理,采用Thread,其中run()的内容,就是线程并行处理的内容,Thread是Runnable的implements*/         Thread background = new Thread(new Runnable(){             public void run() {                 try{                     for(int i = 0; i < 20 && isRunning; i ++){                         Thread.sleep(1000);                         /* 步骤2.1:发送Message到队列中,参数中的obtainMessage()是用于给出一个新Message,本例无参数,对应的在handler在队列中收到这条消息时,则通过handleMessage()进行处理*/                         handler.sendMessage(handler.obtainMessage());                     }                 }catch(Throwable t){                     //jest end the thread                 }             }                     });        isRunning = true;        /*步骤3:启动线程*/         background.start();     }
        /*onStop是UI停止显示时调用,例如我们按了返回键*/     protected void onStop() {         super.onStop();         isRunning = false;     }  }

    例子2:采用Runnable

    我们在上面的例子的基础上进行修改,如下

        /*步骤1:由于不需要处理Message,也即不需要处理handleMessage()*/     Handler handler= new Handler();    /*步骤1.1:定义处理动作,采用Runnable的实例,通过implements run()来定制处理,这里是简单将进度条步进5。由于我们将在Thread中使用这个实例,所以考虑采用final的方式*/     final Runnable r = new Runnable(){          public void run(){             bar.incrementProgressBy(5);          }      };    /* ... ...在onStart()中的步骤2:线程的处理,和提供message不同,对于runnable方式,采用post */         Thread background = new Thread(new Runnable(){             public void run() {                 try{                     for(int i = 0; i < 20 && isRunning; i ++){                         Thread.sleep(1000);                         handler.post(r);                     }                 }catch(Throwable t){                     //jest end the thread                 }             }                  });         background.start();

    例子3:可以用延迟处理实现定时触发,让程序更为简单

    在这里例子,事实我们是进行定时的处理,利用Handler队列可以设置延期处理的方式,我们并不需要创建一个后台运行的线程,也可以实现

        Handler handler= new Handler();     ... ... 在onStart() ... ...     //利用handler.postDelayed(r,1000),在队列中要求延迟1秒后进行r的处理,而在r的处理中, 最后在handler的队列中加 入一个要求延迟1秒的处理,如是,就可以实现每隔1秒的定期处理。         handler.postDelayed(new Runnable(){             public void run() {                 if(isRunning && Chapter15Test2.step < 20){                     step ++;                     bar.incrementProgressBy(5);                     handler.postDelayed(this, 1000);                 }             }       
            },1000);

    在这个例子中,我们基础某种判度,自动停止向队列加入处理。如果有某种情况,我们需要清除队列中的消息或者理,可以使用removMessages()或者removeCallbacks()的处理,这种对于延迟处理方式是非常有用的,可以中断定期的处理。当然,一般来讲我们希望能够得到某种判度,以使得定期处理能够优雅地结束,而不是简单地从队列中将消息或者处理删除。

    例子4:不知道在UI主线程还是在后台线程

    有时候,我们并不清楚代码将在UI线程还是后台线程运行,例如这些代码封装为一个JAR,提供给其他人调用,我们并不清楚其他人如何使用这些代码。为了解决这个问题Android在activity中提供了runOnUiThread(),如果在UI线程,则马上执行,如果在后台线程,则将Runnable的执行内容加入到后台线程的队列中,这样无论代码在UI线程还是后台线程都能安全地执行。

    我们在例子1的基础上进行试验:

    1、建立一个Runnable,以便我们将在UI和后台Thread中进行试验

        Runnable runAction = new Runnable(){         public void run(){             //注意,我们不能使用Toast.makeText(this,....),因为我们无法确定Runnable具体运行的context             Toast.makeText(getApplicationContext(),"Hello!",Toast.LENGTH_SHORT).show();            //Log.d("WEI","runAction .... is called");         }     };

    由于Toast的显示和隐藏需要一定的时间,而间隔1秒显然不够,我们将例子1的间隔时间1000ms,改为5000ms这样会比较清晰,当然可以采用Log.d的方式来替代。

    2、在UI线程中执行该操作,在后台线程中增加该操作,这个操作无论是在UI还是在后台线程都是可以正确执行的。

    protected void onStart() {     ... ...     Thread background = new Thread(new Runnable(){        public void run() {             try{                 for(int i = 0; i < 20 && isRunning; i ++){                     Thread.sleep(5000);                     handler.sendMessage(handler.obtainMessage());                     runOnUiThread(runAction);                 }             }catch(Throwable t){                 //jest end the thread             }         }               });        
        isRunning = true;     background.start();     runOnUiThread(runAction); }

    例子5:HandlerThread

    在上面的例子中,无论是否使用了后台线程(例子1-2),Handler的处理实际就是UI主线程的处理,一般的使用方式为我们通过后台线程执行某些操作,如果需要进行UI的互动,将消息或者处理方式到Handler的的队列中,然手在UI主线程中进行处理。这是我们通用的情况。

    之前我们讨论过为何UI的归UI,处理的处理,然而,可能有这样的需求,举个例子,在某些情况下,Handler收到消息触发的处理中可能会有Sleep(),这会导致main线程进入sleep状态,不是我们期待的。因此我们希望通过一个线程专门处理Hanlder的消息,这个线程也是依次从Handler的队列中获取信息,逐个进行处理,保证安全,不会出现混乱引发的异常。

    针对此Android提供的HandlerThread。方式使用方法如下:

    //步骤1:创新HandlerThread的一个对象,并开启这个线程,HandlerThread将通过Looper来处理Handler对来中的消息,也就是如果发现Handler中有消息,将在HandlerThread这个线程中进行处理。 HandlerThread ht = new HandlerThread("hander_thread"); //步骤2:启动handerhandler这个线程; ht.start();
    //步骤3:创建handler中,带上Looper的参数,即handlerThread.getLooper()。注意,此处理必须在HandlerThread启动后才能调用,否则会报 ,getLooper()会返回null,则程序异常出错 Handler handler = new Handler(ht.getLooper()){     ....     public void handleMessage(Message msg){     ... ...   /*这里的处理,将不在主线程中执行,而在HandlerThread线程中执行,可以通过Thread.currentThread().getId()或者Thread.currentThread().getName()来确定*/     } };

  • 相关阅读:
    Java 第十一届 蓝桥杯 省模拟赛 洁净数
    Java 第十一届 蓝桥杯 省模拟赛 第十层的二叉树
    Java 第十一届 蓝桥杯 省模拟赛 第十层的二叉树
    Java 第十一届 蓝桥杯 省模拟赛 第十层的二叉树
    Java 第十一届 蓝桥杯 省模拟赛 70044与113148的最大公约数
    Java 第十一届 蓝桥杯 省模拟赛 70044与113148的最大公约数
    20. Valid Parentheses
    290. Word Pattern
    205. Isomorphic Strings
    71. Simplify Path
  • 原文地址:https://www.cnblogs.com/LiaoHao/p/3298500.html
Copyright © 2011-2022 走看看