关于Android中的消息机制和异步

来源:互联网 发布:怎么申请做淘宝客 编辑:程序博客网 时间:2024/06/05 21:57

Android中的异步


android中的应用开发,不像是写控制台程序,他是一种和UI相关的程序。几乎所有的UI应用程序都会有这样的要求:不能在主线程(即UI线程)中做耗时的操作。因为一般情况下,主线程负责处理消息和更新界面。其实更新界面也是基于消息驱动的。

在android设备上, 我们做的每个操作,比如按下菜单键或返回键,或者点击了界面上的一个按钮,这些事件 都会被封装成一个消息,发送到主线程的消息队列中。而主线程监听在他的消息队列上, 如果消息队列中进入了一个消息,那么主线程便取出这个消息,调用这个消息上的回调方法,如果主线程的消息队列中没有消息,那么主线程便会阻塞在队列上,直到一个消息的到来。这种消息机制可以用下面的一张图来解释(该图片来自百度):


从这张图中可以看到android消息机制的几个角色:

  • MessageQueue:消息队列。和线程绑定,用于存储当前线程的消息
  • Looper:循环器。和线程绑定,用于控制消息循环。例如在消息队列为空时阻塞当前线程。
  • Message:消息实体。
  • Handler:句柄。和线程绑定,用于发送消息,并且负责消息的回调处理。

其实主线程中的所有代码都是由这种消息机制驱动的。比如我们熟悉的onCreate等回调方法,是框架向该应用程序的主线程的消息队列中发送了一个消息,然后由主线程基于这个消息,调用onrCreate等回调方法。

如果在主线程中做耗时的操作,比如IO和网络,那么主线程就会被长时间的占用,他的消息队列中还有其他消息就不能被即使处理,导致应用程序崩溃,这就是著名的ANR(application no response)错误。举个例子,主线程正在从数据库中读取大量的数据,这时你点击了界面上的一个按钮,这个事件被封装成消息发送到主线程的消息队列,等待主线程处理,由于主线程正在读数据,所以这个消息得不到及时的处理。

所以,在安卓应用开发中, 为了避免主线程被阻塞,将耗时的操作放到子线程中是非常重要的。最主要的处理方式是:

  1. 主线程创建一个Handler对象,这个Handler对象在创建完成后就和主线程绑定在一起,他将消息发送到主线程的消息队列中,并且负责这个消息的处理。
  2. 将耗时的操作放到一个新开的子线程中执行,并且传入主线程的Handler,在子线程执行完毕时,使用这个Handler发送一个消息到主线程的消息队列
  3. 主线程的Looper(主线程创建时建立)控制主线程读取到这个消息
  4. 主线程执行这个消息上的回调方法(一般情况下会回调Handler中的handleMessage方法)
代码的形式如下:
   Handler handlerMain = new Handler(){        public void handleMessage(Message msg) {    switch (msg.what) {    case 1:// ...break;case 2:// ...break;case 3:// ...break;default:break;}        };    };    private void downloadFile(){        new Thread(new Runnable() {@Overridepublic void run() {// 在子线程下载文件//...//...//...//下载完成,发送通知Message msg = handlerMain.obtainMessage();msg.what = 1;//msg.sendToTarget();发送消息, 也可以这样写handlerMain.sendMessage(msg);}}).start();    }


除此之外,为了方便于利用消息机制更新界面,Android特意创造了AsyncTask这个类。这个类的底层也是使用上述的消息机制,只不过进行了一些封装而已,此外AsyncTask中还使用了线程池技术。AsyncTask的使用方式如下:
AsyncTask<String, String, String> task = new AsyncTask<String, String, String>(){@Overrideprotected String doInBackground(String... params) {// 在子线程下载文件//...//...//...//下载任务完成后, 会自动发送消息return null;}protected void onPostExecute(String result) {//主线程得到子线程发送的消息后,会回调到这个方法//该方法在主线程中执行//处理消息或更新界面//...//...//...};        };        private void downloadFileAndUpdateUI(){    task.execute(null);    }

较新的android版本中, 还引入了一些用于异步加载的API,这个异步加载的工具其实底层都是利用的Android的消息机制。


异步 or 同步


异步, 顾名思义就是不是同时执行的:这件事我干不了, 交给你来干, 你干完了之后通知我一下, 我再做一些后续工作。这样你来我往, 各自负责一部分事情, 就达到了异步的效果。
可是, 从上面的代码可以看出,这种根据消息机制实现的异步, 在代码上比较混乱, 阅读时需要跳转来阅读, 有时候阅读一个逻辑还需要跨越好几个文件, 也会在同一个文件的不同地方跳来跳去。
所以, 我们可不可以实现这样一种逻辑:这件事我干不了, 交给你来干,你快点干,干完之后也不用通知我了, 我等着你, 你干完之后, 我再干其他相关的事情。这是一种同步的机制,适用于执行时间不长的任务。
举个例子, 在上一篇博客Android4.0网络操作必须放在子线程中中,有一个登录验证的网络操作,在4.0中只能放在子线程中执行,验证通过后要跳转到其他界面。要实现跳转到其他界面, 必须依赖于验证的结果,而验证是在子线程中进行的,跳转必须在主线程中进行,所以就必须使用异步, 再验证完成之后发送消息到主线程,在主线程中跳转。
其实这只是一个非常简单的http请求, 不会耗费很长时间,其实我们可以在主线程中等待这个操作完成后直接跳转界面。
在jdk5中的线程并发库中可以很方便的实现这种线程等待的逻辑。
  1. 首先创建线程池ExecutorService
  2. 调用ExecutorService的submit方法,传入一个任务对象Callable,返回一个结果Future
  3. 在当前线程中调用Future对象的get方法, 等待后台任务执行完成返回结果
代码如下:
/** * 登陆验证, 在主线程中直接调用, 主线程会等待后台线程验证的结果返回 * @param context * @return 验证成功返回true, 反之返回false */public static boolean userLoginCheckWaited(final Context context){//创建单个线程池, 将验证的网络操作放到子线程中ExecutorService singleTheadPool = Executors.newSingleThreadExecutor();//将验证任务提交到线程池中Future<Boolean> fu = singleTheadPool.submit(new Callable<Boolean>() {@Overridepublic Boolean call() throws Exception {return ESDKUtils.userLoginCheck1(context);}});try {return fu.get();    //等待验证结果的返回} catch (Exception e) {e.printStackTrace();return false;} }/*** 网络操作放到后台线程中执行*/private static boolean userLoginCheck1( Context context){//设置登录验证的各项参数        List<BasicNameValuePair> params = new LinkedList<BasicNameValuePair>();        params.add(new BasicNameValuePair("yhid", userName));        params.add(new BasicNameValuePair("yhkl", passwd));        params.add(new BasicNameValuePair("sbid", DeviceTool.getDeviceId(context)));        params.add(new BasicNameValuePair("clientIp", IPTool.getPsdnIp()));        params.add(new BasicNameValuePair("ywxtbm", "BGPTNEW"));        params.add(new BasicNameValuePair("ywxtmc", "办公平台升级"));        URL url = null;        StringBuilder sb = new StringBuilder();        BufferedReader reader = null;        try{                    //设置url地址                url = new URL(URLConstant.USER_LOGIN_CHEAK_ADDRESS);                        String paramString = URLEncodedUtils.format(params, "GBK");//请求参数编码为GBK                        byte[] dataToSend = paramString.getBytes();//post请求中的实体数据            HttpURLConnection connection = (HttpURLConnection) url.openConnection();                        connection.setRequestMethod("POST");            connection.setDoOutput(true);

这样的话, 可以直接在主线程中调用userLoginCheckWaited方法, 而不用再写异步相关的代码, 可以使代码大大简化。调用代码如下:
//开始业务登陆验证, 验证用户名和密码的正确性,在主线程直接调用if(ESDKUtils.userLoginCheckWaited(this)){//跳转到界面}


我们还要明白一点, fu.get()方法是会阻塞的, 它等待后台任务的完成。所以要注意,在主线程中调用时, 它同样也会阻塞主线程。因此这种方式只适用于耗时很少的方法,比如验证登陆只是一个http请求,并且数据量很少,可以使用这种方法。如果是上传下载文件这类的操作,就不能使用这种方式了。

原创粉丝点击
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 扣扣绑定的手机号被别人用了怎么办 我的手机号被别人绑定了快手怎么办 微信密码忘记了没绑定手机号怎么办 扣扣绑定银行卡忘记密码了怎么办 扣扣忘记密码了又换手机号了怎么办 微信订阅号里有删除后的信息怎么办 申请微信公众号邮箱已被占用怎么办 公众号验证没有对公账户怎么办 qq密码忘记了怎么办手机绑定没有 我的扣扣怎么申诉都找不回来怎么办 装修装的不好又没有签合同怎么办 日本免税的零食不小心拆了怎么办 闲鱼同意买家退货了买家不退怎么办 手机里淘宝钱付了不发货怎么办 百度云下载手机储存空间不足怎么办 苹果8的照片储存空间满了怎么办 为什么下载的软件已停止运行怎么办 苹果手机刷机忘记注册邮箱了怎么办 刺激战场模拟器注册已达上限怎么办 用模拟器玩刺激战场注册上限怎么办 微信解除实名认证后退款怎么办 苹果为什么qq收消息有延迟怎么办 qq忘记密码手机号也换了怎么办 扣扣空间圈人时照片服务错误怎么办 删了qq好友怎么找回来怎么办 苹果5s语音控制打开了怎么办 lv迷你水桶包肩带长了怎么办 在香港买个lv包包过海关怎么办 国际快递手表被海关查应该怎么办 把档案放到人才市场后报到证怎么办 皮表带带久了有异味怎么办 英语中用词不当和拼写错误怎么办 爬楼梯的购物车车轮坏了怎么办? 帮别人买东西不给我钱怎么办 老师念错名字有同学指出来你怎么办 老公婚前买的房子婆婆想霸占怎么办 我想查我的基金收益情况怎么办 儿童票买好了但大人退票了怎么办 没有享受到国家政策的农民怎么办? 股票涨了没抛然后一直跌怎么办 苹果手机放久了开不了机怎么办