Android多线程机制详细解析

或许你曾经需要项目中进行后台工作比如数据库访问或者网络连接,如果你按照以前的做法,直接在点击事件或者onCreate方法中直接调用访问数据库或者服务器的方法,你就会遇到大多数Android程序员都遇到过的这么一个错误:android.view.ViewRootImpl$CalledFromWrongThreadException


报出这个错误的原因?我们首先要来了解一下Android的多线程机制:

在Android中,必须遵循单线程模式,即:
1.UI线程就是主线程,后台任务不能出现在主线程中,也就是不能阻塞UI线程
2.涉及UI更新的部分不能出现在工作线程中,也就是不要在UI线程之外访问Andoid的UI组件包
Android开发必须满足这两个要素,否则程序就会崩溃,Android开发中既有前台与用户的操作(UI的更新),也有后台与数据的连接(后台任务),比如你有一个页面,用户一点击查询,从后台数据库查询数据,返回给前台页面,页面再将数据罗列展示给用户,在这个过程中涉及到了前后台的交互,如果在点击查询的事件中直接调用select数据库的方法,程序就会崩溃。正确的做法应该是:点击查询后,开启子线程,在子线程里调用访问数据库的方法,然后子线程再将拿到的数据发送给主线程,在主线程中进行展示。


Android的消息机制中,主要涉及到这几个对象:Handler,MessageQueue,Looper,Message

什么是Message?

Message是一种消息体,用于装载需要发送的对象,Android中子线程与主线程之间通信的时候,就需要通过消息来传递,你可以理解为子线程与主线程之间进行“聊天”时所发送的“聊天消息”。


什么是MessageQueue?

MessageQueue消息队列是用来存放所有消息的,遵循先进先出规则(FIFO),如果一个线程需要接收来自其它线程的消息,则必须为其创建一个消息队列,将接收到的消息丢进这个队列中,当需要时再取出来。


什么是Looper?

Looper就相当于管理者的角色,管理当前所属线程的MessageQueue,循环不断地管理MessageQueue接收和分发Message。


什么是Handler?
Handler则相当于处理者的角色,处理和接收Looper派发出来的消息。


我们通过一段简单的代码片段来分析整个流程:


  1. public class HandlerActivity extends Activity {  
  2.       
  3.     //主线程的handler  
  4.     private Handler mainhandler;  
  5.     //子线程  
  6.     private Thread myThread;  
  7.     //用来展示数据  
  8.     private TextView textView;  
  9.   
  10.     @Override  
  11.     protected void onCreate(Bundle savedInstanceState) {  
  12.         super.onCreate(savedInstanceState);  
  13.         setContentView(R.layout.activity_handler);  
  14.           
  15.         textView = (TextView)this.findViewById(R.id.hello);  
  16.           
  17.         mainhandler = new Handler(){  
  18.             public void handleMessage(android.os.Message msg) {  
  19.                 switch(msg.what){  
  20.                     case 1:  
  21.                         textView.setText(msg.what+”已经取到数据”);  
  22.                 }  
  23.             };  
  24.         };  
  25.           
  26.         myThread = new Thread(new Runnable(){  
  27.   
  28.             @Override  
  29.             public void run() {  
  30.                 //在此处进行数据库或者网络连接等后台操作  
  31.                 Message msg = new Message();  
  32.                 msg.obj = ”取到的数据:hello”;  
  33.                 msg.what = 1;  
  34.                 mainhandler.sendMessage(msg);  
  35.             }  
  36.               
  37.         });  
  38.         myThread.start();  
  39.           
  40.     }  
  41.       
  42.       
  43. }  
public class HandlerActivity extends Activity {

    //主线程的handler
    private Handler mainhandler;
    //子线程
    private Thread myThread;
    //用来展示数据
    private TextView textView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_handler);

        textView = (TextView)this.findViewById(R.id.hello);

        mainhandler = new Handler(){
            public void handleMessage(android.os.Message msg) {
                switch(msg.what){
                    case 1:
                        textView.setText(msg.what+"已经取到数据");
                }
            };
        };

        myThread = new Thread(new Runnable(){

            @Override
            public void run() {
                //在此处进行数据库或者网络连接等后台操作
                Message msg = new Message();
                msg.obj = "取到的数据:hello";
                msg.what = 1;
                mainhandler.sendMessage(msg);
            }

        });
        myThread.start();

    }


}


 
 

可以看到,首先在主线程中创建了一个mainhandler,用来处理和接收子线程发送过来的消息。再创建了一个子线程myThread,在子线程中进行了访问数据库的操作,访问完成后让message将数据携带上,在这里模拟数据为”取到的数据:hello”,通过msg.obj将数据给message,msg.what是标志位,因为本例只有一个子线程,当有多个子线程的时候,handler需要辨识消息是属于哪个子线程发送的,就是通过what变量来分别。mainhandler.sendMessage(msg)是通过mainhandler将消息发送给主线程,在主线程中,通过handleMessage(android.os.Message msg)方法接收到消息,这里的参数正是子线程发送过来的消息,然后再通过switch分支处理消息,并进行UI更新–>textView.setText(msg.what+”已经取到数据”)。

你会问,刚才的流程哪里有提到Looper以及MessageQueue?

其实,在刚才的流程中,只涉及了一个子线程,当有多个子线程时,主线程就需要与多个子线程进行交互,这个时候主线程接收到的消息就不止一两条,那么这些消息放在哪里呢?就放在MessageQueue中,而刚才说到handler是用来接收和处理消息的,其实handler并不是直接处理message,而是通过Looper来处理,Looper不断地从MessageQueue中循环取消息,一旦发现MessageQueue中有消息,就将其派发给handler去处理。

整个流程图如下:

AsynTask类:

以上讲解了Android中关于多线程之间的通信原理,但或许你会感觉这样比较麻烦,因此Android提供了AsynTask异步任务类,可以简化了一些工作线程和UI交互的操作:
首先,定义一个类继承于AsynTask类,并重写其中的方法:
  1. class LoginTask extends AsyncTask<String, Void, Integer>{  
  2.     @Override  
  3.     protected Integer doInBackground(String… arg0) {  
  4.         // TODO Auto-generated method stub  
  5.         //可在此处进行各种后台操作  
  6.         if(arg0.equals(“”)){  
  7.             //如果传进来的参数为空,返回1  
  8.             return 1;  
  9.         }  
  10.         else{  
  11.             //如果传进来的参数不为空,返回2  
  12.             return 2;  
  13.         }  
  14.     }  
  15.           
  16.     @Override  
  17.     protected void onPostExecute(Integer result) {  
  18.         // TODO Auto-generated method stub  
  19.         super.onPostExecute(result);  
  20.         //这里接收的result即为上面doInBackground返回的结果  
  21.         if(result==1){  
  22.             //更新界面UI  
  23.             Intent intent = new Intent(MainActivity.this, HomePageActivity.class);  
  24.             startActivity(intent);  
  25.         }  
  26.         else{  
  27.             //更新界面UI  
  28.             new AlertDialog.Builder(MainActivity.this).setMessage(“账号或密码错误!”).setPositiveButton(“确定”null).show();  
  29.         }  
  30.     }  
  31.           
  32.           
  33. }  
class LoginTask extends AsyncTask<String, Void, Integer>{
    @Override
    protected Integer doInBackground(String... arg0) {
        // TODO Auto-generated method stub
        //可在此处进行各种后台操作
        if(arg0.equals("")){
            //如果传进来的参数为空,返回1
            return 1;
        }
        else{
            //如果传进来的参数不为空,返回2
            return 2;
        }
    }

    @Override
    protected void onPostExecute(Integer result) {
        // TODO Auto-generated method stub
        super.onPostExecute(result);
        //这里接收的result即为上面doInBackground返回的结果
        if(result==1){
            //更新界面UI
            Intent intent = new Intent(MainActivity.this, HomePageActivity.class);
            startActivity(intent);
        }
        else{
            //更新界面UI
            new AlertDialog.Builder(MainActivity.this).setMessage("账号或密码错误!").setPositiveButton("确定", null).show();
        }
    }


}



其中,AsyncTask泛型有三个参数,第一个表示传入的参数类型,第二个表示进度值,第三个表示传出的结果类型
doInBackground方法是用来填写后台逻辑判断等工作任务,该方法会自动运行并将结果传递给onPostExecute方法
onPostExecute方法接收来自doInBackground的结果,因此,其参数类型要与doInBackground方法的返回参数的类型一致,在这个方法中根据后台传来的结果进行UI的更新。

上面我们已经定义好了一个AsynTask异步任务类,接下来只需要在主线程中调用:

  1. LoginTask loginTask = new LoginTask();  
  2. loginTask.execute(”1”);  //通过execute方法进行运行。  
LoginTask loginTask = new LoginTask();
loginTask.execute("1");  //通过execute方法进行运行。


Android线程如何实现循环调用AsynTask:


  1. Handler handler = new Handler(){  
  2.     Runnable runnable = new Runnable(){  
  3.         public void run(){  
  4.             //要做的事情,此处产生AsynTask实例,并execute  
  5.             handler.postDelayed(this,1000);   //每1秒执行一次  
  6.         }  
  7.     };  
  8. }  
Handler handler = new Handler(){
    Runnable runnable = new Runnable(){
        public void run(){
            //要做的事情,此处产生AsynTask实例,并execute
            handler.postDelayed(this,1000);   //每1秒执行一次
        }
    };
}



关于Looper.prepare()和Looper.loop()方法:

在主线程(UI线程)里,如果创建Handler时不传入Looper对象,那么将直接使用主线程(UI线程)的Looper对象(系统已经帮我们创建了); 在其它线程里,如果创建Handler时不传入Looper对象,那么,这个Handler将不能接收处理消息
Android中如果在子线程中新建Handler实例并在其handlerMessage方法中更新UI,则会报错,因为 android中只有主线程是默认带有Looper对象和消息队列的 ,而子线程是没有的,需要自己生成,所以只需要在生成handler实例前调用Looper.prepare()方法,在生成handler实例后调用Looper.loop()方法即可。
其中,Looper.prepare()表示为当前子线程创建一个消息队列,Looper.loop()表示不断地从消息队列中取数据

在一个Thread中Looper也是唯一的,一个Thread对应一个Looper,建立Handler的Looper来自哪个Thread,Handler就属于哪个Thread。如果子线程也需要接收消息时,则需要创建一个消息队列,即在子线程中Looper.prepare()/Looper.loop()

如果在子线程中,新建的handler是:handler = new Handler(Looper.getMainLooper())则表示等下handler发送的消息都仍然是发给主线程,因为现在它所关联的looper依旧是主线程的looper
如果在子线程中,新建的handler是:handler = new Handler(Looper.myLooper())则表示这个handler发送的消息都是发给当前子线程,因为现在它所关联的looper是当前线程的looper

当然,Android还为我们提供了一个HandlerThread类,用来开启一个含Looper对象的线程


该注意的点:

Handler中最好使用obtainMessage来获取消息对象,Handler.obtainMessage()会从消息池中获取一个Message对象,如果消息池中是空的,才会使用构造方法实例化一个新Message,这样能够节省资源消耗。

Android中即使Activity关闭或者onDestroy了,由它创建的子线程依然会继续在后台跑着,不会跟着结束直到系统资源吃紧才会回收,所以要养成习惯,将要关闭的线程在该Activity的onDestroy方法中进行集中关闭:
可以使用handler的handler.removeCallbacks(test)方法关闭子线程。



            </div>
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值