精华内容
下载资源
问答
  • Java 异步消息处理

    2013-10-15 17:23:17
     在前一节实现异步调用的基础上 , 现在我们来看一下一个完善的 Java 异步消息处理机制 . [ 写在本节之前 ]  在所有这些地方 , 我始终没有提到设计模式这个词 , 而事实上 , 多线程编程几乎每一步都在...

    Java 异步消息处理

    .    它要能适应不同类型的请求:
    本节用 makeString来说明要求有返回值的请求.displayString来说明不需要返回值的请求.
    .    要能同时并发处理多个请求,并能按一定机制调度:
    本节将用一个队列来存放请求,所以只能按FIFO机制调度,你可以改用LinkedList,就可以简单实现一个优先级(优先级高的addFirst,低的addLast).
    .    有能力将调用的边界从线程扩展到机器间(RMI)
    .    分离过度耦合,如分离调用句柄(取货凭证)和真实数据的实现.分离调用和执行的过程,可以尽快地将调返回.

    现在看具体的实现:
    public interface Axman {
      Result resultTest(int count,char c);
      void noResultTest(String str);
    }
    这个接口有两个方法要实现,就是有返回值的调用resultTest和不需要返回值的调用
    noResultTest, 我们把这个接口用一个代理类来实现,目的是将方法调用转化为对象,这样就可以将多个请求(多个方法调)放到一个容器中缓存起来,然后统一处理,因为 Java不支持方法指针,所以把方法调用转换为对象,然后在这个对象上统一执行它们的方法,不仅可以做到异步处理,而且可以将代表方法调用的请求对象序列化后通过网络传递到另一个机器上执行(RMI).这也是Java回调机制最有力的实现.
        一个简单的例子.
        如果 1: A
        如果 2: B
    如果 3: C
    如果有1000个情况,你不至于用1000case?以后再增加呢?
    所以如果C/C++程序员,会这样实现: (cc++定义结构不同)

    type define struct MyStruct{
    int mark;
    (*fn) ();
    } MyList;
       
        然后你可以声明这个结构数据:
        {1,A,
         2,B
         3,C
    }
    做一个循环:
    for(i=0;i<length;i++) {
        if(数据组[i].mark == 传入的值) (数据组[i].*fn)();
    }
    简单说c/c++中将要被调用的涵数可以被保存起来,然后去访问,调用,Java,我们无法将一个方法保存,除了直接调用,所以将要调用的方法用子类来实现,然后把这些子类实例保存起来,然后在这些子类的实现上调用方法:
    interface My{
        void test();
    }

    class A implements My{
        public void test(){
            System.out.println(A):
    }
    }
    class B implements My{
        public void test(){
            System.out.println(B):
    }
    }

    class C implements My{
        public void test(){
            System.out.println(C):
    }
    }

    class MyStruct {
       
        int mark;
        My m;
        public MyStruct(int mark,My m){this.mark = amrk;this.m = m}
    }
    数组:
    { new MyStruct(1,new A()),new MyStruct(2,new B()),new MyStruct(3,new C())}
    for(xxxxxxxxx) if(参数 ==数组[i].mark) 数组[i].m.test();

    这样把要调用的方法转换为对象的保程不仅仅是可以对要调用的方法进行调度,而且可以把对象序列化后在另一台机器上执行,这样就把调用边界从线程扩展到了机器.

    回到我们的例子:
    class Proxy implements Axman{
      private final Scheduler scheduler;
      private final Servant servant;

      public Proxy(Scheduler scheduler,Servant servant){
        this.scheduler = scheduler;
        this.servant = servant;
      }
      public Result resultTest(int count,char c){
        FutureResult futrue = new FutureResult();
        this.scheduler.invoke(new ResultRequest(servant,futrue,count,c));
        return futrue;
      }

      public void noResultTest(String str){
        this.scheduler.invoke(new NoResultRequest(this.servant,str));
      }
    }

    其中scheduler是管理对调用的调度, servant是真正的对方法的执行:

    Servant就是去真实地实现方法:

    class Servant implements Axman{
      public Result resultTest(int count,char c){
        char[] buf = new char[count];
        for(int i = 0;i < count;i++){
          buf[i] = c;
          try{
            Thread.sleep(100);
          }catch(Throwable t){}
        }
        return new RealResult(new String(buf));
      }

      public void noResultTest(String str){
        try{
          System.out.println("displayString :" + str);
          Thread.sleep(10);
        }catch(Throwable t){}
      }
    }
    scheduler 将方法的调用(invkoe)和执行(execute)进行了分离,调用就是开始注册方法到要执行的容器中,这样就可以立即返回出来.真正执行多久就是execute的事了,就象一个人点燃爆竹的引信就跑了,至于那个爆竹什么时候爆炸就不是他能控制的了.
    public class Scheduler extends Thread {
      private final ActivationQueue queue;
      public Scheduler(ActivationQueue queue){
        this.queue = queue;
      }

      public void invoke(MethodRequest request){
        this.queue.putRequest(request);
      }

      public void run(){
        while(true){

          //如果队列中有请求线程,测开始执行请求
          MethodRequest request = this.queue.takeRequest();
          request.execute();
        }
      }
    }
    scheduler中只用一个队列来保存代表方法和请求对象,实行简单的FIFO调用,你要实更复杂的调度就要在这里重新实现:
    class ActivationQueue{
      private static final int MAX_METHOD_REQUEST = 100;
      private final MethodRequest[] requestQueue;
      private int tail;
      private int head;
      private int count;

      public ActivationQueue(){
        this.requestQueue = new MethodRequest[MAX_METHOD_REQUEST];
        this.head = this.count = this.tail = 0;
      }

      public synchronized void putRequest(MethodRequest request){
        while(this.count >= this.requestQueue.length){
          try {
            this.wait();
          }
          catch (Throwable t) {}
        }
        this.requestQueue[this.tail] = request;
        tail = (tail + 1)%this.requestQueue.length;
        count ++ ;
        this.notifyAll();

      }


      public synchronized MethodRequest takeRequest(){
        while(this.count <= 0){
          try {
            this.wait();
          }
          catch (Throwable t) {}
     
        }

        MethodRequest request = this.requestQueue[this.head];
        this.head = (this.head + 1) % this.requestQueue.length;
        count --;
        this.notifyAll();
        return request;
      }
    }

    为了将方法调用转化为对象,我们通过实现MethodRequest对象的execute方法来方法具体方法转换成具体对象:
    abstract class MethodRequest{
      protected final Servant servant;
      protected final FutureResult future;

      protected MethodRequest(Servant servant,FutureResult future){
        this.servant = servant;
        this.future = future;
      }

      public abstract void execute();
    }

    class ResultRequest extends MethodRequest{
      private final int count;
      private final char c;
      public ResultRequest(Servant servant,FutureResult future,int count,char c){
        super(servant,future);
        this.count = count;
        this.c = c;
      }
      public void execute(){
        Result result = servant.resultTest(this.count,this.c);
        this.future.setResult(result);
      }
    }

    class NoResultRequest extends MethodRequest{
      private String str;
      public NoResultRequest(Servant servant,String str){
        super(servant,null);
        this.str = str;
      }

      public void execute(){
        this.servant.noResultTest(str);
      }
    }

    而返回的数据我们也将真实数据的获取和取货凭证逻辑分离:
    package com.axman.jasync;

    public abstract class Result {
      public abstract Object getResult();
    }

    class FutureResult extends Result{
      private Result result;
      private boolean completed;

      public synchronized void setResult(Result result){
        this.result = result;
        this.completed = true;
        this.notifyAll();
      }

      public synchronized Object getResult(){
        while(!this.completed){
          try{
            this.wait();
          }catch(Throwable t){}
        }
        return this.result.getResult();
      }
    }

    class RealResult extends Result{
      private final Object result;

      public RealResult(Object result){
        this.result = result;
      }
      public Object getResult(){
        return this.result;
      }
    }
    OK,现在这个异步消息处理器已经有了模型,这个异步处理器中有昭雪些对象参与呢?
        Servant 忠心做真实的事务
        ActivationQueue将请求缓存起来以便调度
        Scheduler对容器中的请求根据一定原则进行调度执行
        Proxy将特定方法请求转换为特定对象
    所有这些都是这个异步处理器的核心部件,虽然是核心部件,我们就要进行封装而不能随便让调用者来修改,所以我们用工厂模式(KAO,我实在不想提模式但有时找不到其它词来表述)来产生处理器Axman对象:
    package com.axman.jasync;

    public class AxmanFactory {
      public static Axman createAxman() {
        Servant s = new Servant();
        ActivationQueue queue = new ActivationQueue();
        Scheduler st = new Scheduler(queue);
        Proxy p = new Proxy(st,s);
        st.start();
        return p;
      }
    }
    好了,我们现在用两个请求的产生者不停产生请求:
    ResultInvokeThreadv 发送有返回值的请求:
    package com.axman.jasync;

    public class ResultInvokeThread extends Thread{
      private final Axman ao;
      private final char c;
      public ResultInvokeThread(String name,Axman ao){
        this.ao = ao;
        this.c = name.charAt(0);
      }

      public void run(){
        try{
          int i = 0;
          while(true){
            Result result  = this.ao.resultTest(i++,c);
            Thread.sleep(10);
            String  = (String)result.getResult();
            System.out.println(Thread.currentThread().getName() + "  = " + );
          }
        }
        catch(Throwable t){}
      }
    }

    NoResultInvokeThread发送无返回值的请求:
    package com.axman.jasync;

    public class NoResultInvokeThread extends Thread{
      private final Axman ao;
      public NoResultInvokeThread(String name,Axman ao){
        super(name);
        this.ao = ao;
      }

      public void run(){
        try{
          int i = 0;
          while(true){
            String s = Thread.currentThread().getName() + i++;
            ao.noResultTest(s);
            Thread.sleep(20);
          }
        }
        catch(Throwable t){}
      }
    }

    对了,我们还需要一个什么东西来产生一个演示:
    package com.axman.jasync;

    public class Program {
      public static void main(String[] args) {
        Axman ao = AxmanFactory.createAxman();
        new ResultInvokeThread("Axman",ao).start();
        new ResultInvokeThread("Sager",ao).start();
        new NoResultInvokeThread("Macke",ao).start();
      }
    }
    看看结果吧.你可以把不同类型的请求不断地向处理器发送,处理器会不断地接收请求,放到队列中,并同时不断从队列中提出请求进行处理.

     

     

     

     

    在调用一个方法的时候,程序会进入被调用方法体内,执行完这个被调用方法后,才返回执行下一条语句。怎么做到像ajax异步请求一样,发送请求后,没等请求响应就执行下一条语句呢?对于java的异步请求,找了许多教材都没有找到,如thinking in javacore java2 ......等等。受多线程下载工具以及mootoolsRequest的启发,做了一个java版的Request,不知它的性能如何。

     

    Request:请求载体

    public  class Request {
     private RequestContent rc;//请求主体
     public Request(RequestContent rc){
      this.rc=rc;
     }
     protected void start(){  //开始请求
      final Thread  t=new Thread(new Runnable(){
       public void run(){
        try{
         rc.doSomeThing();//响应请求
        }catch (Exception e) {
         e.printStackTrace();
         rc.onFailure(); //如果执行失败
        }
        rc.onSuccess();//如果执行成功
       }}
      );
      t.start();
     }
    }

    RequestContent:请求主体

    abstract class RequestContent {
     void onSuccess(){   //执行成功的动作。用户可以覆盖此方法
      System.out.println("onSuccess");
     }
     void onFailure(){  //执行失败的动作。用户可以覆盖此方法
      System.out.println("onFailure");
     }
     abstract void doSomeThing(); //用户必须实现这个抽象方法,告诉子线程要做什么
    }

     

    Test:测试

      new Request(new RequestContent(){
       void doSomeThing(){
        System.out.println("doSomething");
       }
       void onSuccess(){
        System.out.println("override onSuccess");
       }
      }).start();

     

     


    展开全文
  • 转载请标明出处:http://blog.csdn.net/lmj623565791/article/details/38476887 ,本文出自【张鸿洋的博客】上一篇博客介绍了Android异步消息处理机制,如果你还不了解,可以看:Android 异步消息处理机制 让你深入...

    转载请标明出处:http://blog.csdn.net/lmj623565791/article/details/38476887 ,本文出自【张鸿洋的博客】

    最近创建了一个群,方便大家交流,群号:55032675

    上一篇博客介绍了Android异步消息处理机制,如果你还不了解,可以看:Android 异步消息处理机制 让你深入理解 Looper、Handler、Message三者关系 。那篇博客的最后,提出可以把异步消息处理机制不仅仅是在MainActivity中更新UI,可以用到别的地方,最近也一直在考虑这个问题,有幸,想出来一个实际的案例,将异步消息处理机制用到大量图片的加载的工具类中,其实也特别希望可以写一篇关于大量图片加载的文章,终于有机会了~先简单介绍一下:

    1、概述

    一般大量图片的加载,比如GridView实现手机的相册功能,一般会用到LruCache,线程池,任务队列等;那么异步消息处理可以用哪呢?

    1、用于UI线程当Bitmap加载完成后更新ImageView

    2、在图片加载类初始化时,我们会在一个子线程中维护一个Loop实例,当然子线程中也就有了MessageQueue,Looper会一直在那loop停着等待消息的到达,当有消息到达时,从任务队列按照队列调度的方式(FIFO,LIFO等),取出一个任务放入线程池中进行处理。

    简易的一个流程:当需要加载一张图片,首先把加载图片加入任务队列,然后使用loop线程(子线程)中的hander发送一个消息,提示有任务到达,loop()(子线程)中会接着取出一个任务,去加载图片,当图片加载完成,会使用UI线程的handler发送一个消息去更新UI界面。

    说了这么多,大家估计也觉得云里来雾里去的,下面看实际的例子。

    2、图库功能的实现

    该程序首先扫描手机中所有包含图片的文件夹,最终选择图片最多的文件夹,使用GridView显示其中的图片

    1、布局文件

    <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools"
        android:layout_width="match_parent"
        android:layout_height="match_parent" >
    
        <GridView
            android:id="@+id/id_gridView"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:cacheColorHint="@android:color/transparent"
            android:columnWidth="90dip"
            android:gravity="center"
            android:horizontalSpacing="20dip"
            android:listSelector="@android:color/transparent"
            android:numColumns="auto_fit"
            android:stretchMode="columnWidth"
            android:verticalSpacing="20dip" >
        </GridView>
    
    </RelativeLayout>

    布局文件相当简单就一个GridView

    2、MainActivity

    package com.example.zhy_handler_imageloader;
    
    import java.io.File;
    import java.io.FilenameFilter;
    import java.util.Arrays;
    import java.util.HashSet;
    import java.util.List;
    
    import android.app.Activity;
    import android.app.ProgressDialog;
    import android.content.ContentResolver;
    import android.database.Cursor;
    import android.net.Uri;
    import android.os.Bundle;
    import android.os.Environment;
    import android.os.Handler;
    import android.provider.MediaStore;
    import android.widget.GridView;
    import android.widget.ImageView;
    import android.widget.ListAdapter;
    import android.widget.Toast;
    
    public class MainActivity extends Activity
    {
    	private ProgressDialog mProgressDialog;
    	private ImageView mImageView;
    	
    	/**
    	 * 存储文件夹中的图片数量
    	 */
    	private int mPicsSize;
    	/**
    	 * 图片数量最多的文件夹
    	 */
    	private File mImgDir;
    	/**
    	 * 所有的图片
    	 */
    	private List<String> mImgs;
    
    	private GridView mGirdView;
    	private ListAdapter mAdapter;
    	/**
    	 * 临时的辅助类,用于防止同一个文件夹的多次扫描
    	 */
    	private HashSet<String> mDirPaths = new HashSet<String>();
    
    	private Handler mHandler = new Handler()
    	{
    		public void handleMessage(android.os.Message msg)
    		{
    			mProgressDialog.dismiss();
    			mImgs = Arrays.asList(mImgDir.list(new FilenameFilter()
    			{
    				@Override
    				public boolean accept(File dir, String filename)
    				{
    					if (filename.endsWith(".jpg"))
    						return true;
    					return false;
    				}
    			}));
    			/**
    			 * 可以看到文件夹的路径和图片的路径分开保存,极大的减少了内存的消耗;
    			 */
    			mAdapter = new MyAdapter(getApplicationContext(), mImgs,
    					mImgDir.getAbsolutePath());
    			mGirdView.setAdapter(mAdapter);
    		};
    	};
    
    	@Override
    	protected void onCreate(Bundle savedInstanceState)
    	{
    		super.onCreate(savedInstanceState);
    		setContentView(R.layout.activity_main);
    		mGirdView = (GridView) findViewById(R.id.id_gridView);
    		getImages();
    
    	}
    
    	/**
    	 * 利用ContentProvider扫描手机中的图片,此方法在运行在子线程中 完成图片的扫描,最终获得jpg最多的那个文件夹
    	 */
    	private void getImages()
    	{
    		if (!Environment.getExternalStorageState().equals(
    				Environment.MEDIA_MOUNTED))
    		{
    			Toast.makeText(this, "暂无外部存储", Toast.LENGTH_SHORT).show();
    			return;
    		}
    		// 显示进度条
    		mProgressDialog = ProgressDialog.show(this, null, "正在加载...");
    
    		new Thread(new Runnable()
    		{
    
    			@Override
    			public void run()
    			{
    				Uri mImageUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
    				ContentResolver mContentResolver = MainActivity.this
    						.getContentResolver();
    
    				// 只查询jpeg和png的图片
    				Cursor mCursor = mContentResolver.query(mImageUri, null,
    						MediaStore.Images.Media.MIME_TYPE + "=? or "
    								+ MediaStore.Images.Media.MIME_TYPE + "=?",
    						new String[] { "image/jpeg", "image/png" },
    						MediaStore.Images.Media.DATE_MODIFIED);
    
    				while (mCursor.moveToNext())
    				{
    					// 获取图片的路径
    					String path = mCursor.getString(mCursor
    							.getColumnIndex(MediaStore.Images.Media.DATA));
    					// 获取该图片的父路径名
    					File parentFile = new File(path).getParentFile();
    					String dirPath = parentFile.getAbsolutePath(); 
    					
    					//利用一个HashSet防止多次扫描同一个文件夹(不加这个判断,图片多起来还是相当恐怖的~~)
    					if(mDirPaths.contains(dirPath))
    					{
    						continue; 
    					}
    					else
    					{
    						mDirPaths.add(dirPath);
    					}
    					
    					int picSize = parentFile.list(new FilenameFilter()
    					{
    						@Override
    						public boolean accept(File dir, String filename)
    						{
    							if (filename.endsWith(".jpg"))
    								return true;
    							return false;
    						}
    					}).length;
    					if (picSize > mPicsSize)
    					{
    						mPicsSize = picSize;
    						mImgDir = parentFile;
    					}
    				}
    				mCursor.close();
    				//扫描完成,辅助的HashSet也就可以释放内存了
    				mDirPaths = null ; 
    				// 通知Handler扫描图片完成
    				mHandler.sendEmptyMessage(0x110);
    
    			}
    		}).start();
    
    	}
    }
    

    MainActivity也是比较简单的,使用ContentProvider辅助,找到图片最多的文件夹后,直接handler去隐藏ProgressDialog,然后初始化数据,适配器等;

    但是稍微注意一下:

    1、在扫描图片时,使用了一个临时的HashSet保存扫描过的文件夹,这样可以有效的避免重复扫描。比如,我手机中有个文件夹下面有3000多张图片,如果不判断则会扫描这个文件夹3000多次,处理器时间以及内存的消耗还是很可观的。

    2、在适配器中,保存List<String>的时候,考虑只保存图片的名称,路径单独作为变量传入。一般情况下,图片的路径比图片名长很多,加入有3000张图片,路径长度30,图片平均长度10,则List<String>保存完成路径需要长度为:(30+10)*3000 = 120000 ; 而单独存储只需要:30+10*3000 = 30030 ; 图片越多,节省的内存越客观;

    总之,尽可能的去减少内存的消耗,这些都是很容易做到的~

    3、GridView的适配器

    package com.example.zhy_handler_imageloader;
    
    import java.util.List;
    
    import android.content.Context;
    import android.view.LayoutInflater;
    import android.view.View;
    import android.view.ViewGroup;
    import android.widget.BaseAdapter;
    import android.widget.ImageView;
    
    import com.zhy.utils.ImageLoader;
    
    public class MyAdapter extends BaseAdapter
    {
    
    	private Context mContext;
    	private List<String> mData;
    	private String mDirPath;
    	private LayoutInflater mInflater;
    	private ImageLoader mImageLoader;
    
    	public MyAdapter(Context context, List<String> mData, String dirPath)
    	{
    		this.mContext = context;
    		this.mData = mData;
    		this.mDirPath = dirPath;
    		mInflater = LayoutInflater.from(mContext);
    
    		mImageLoader = ImageLoader.getInstance();
    	}
    
    	@Override
    	public int getCount()
    	{
    		return mData.size();
    	}
    
    	@Override
    	public Object getItem(int position)
    	{
    		return mData.get(position);
    	}
    
    	@Override
    	public long getItemId(int position)
    	{
    		return position;
    	}
    
    	@Override
    	public View getView(int position, View convertView, final ViewGroup parent)
    	{
    		ViewHolder holder = null;
    		if (convertView == null)
    		{
    			holder = new ViewHolder();
    			convertView = mInflater.inflate(R.layout.grid_item, parent,
    					false);
    			holder.mImageView = (ImageView) convertView
    					.findViewById(R.id.id_item_image);
    			convertView.setTag(holder);
    		} else
    		{
    			holder = (ViewHolder) convertView.getTag();
    		}
    		holder.mImageView
    				.setImageResource(R.drawable.friends_sends_pictures_no);
    		//使用Imageloader去加载图片
    		mImageLoader.loadImage(mDirPath + "/" + mData.get(position),
    				holder.mImageView);
    		return convertView;
    	}
    
    	private final class ViewHolder
    	{
    		ImageView mImageView;
    	}
    
    }
    

    可以看到与传统的适配器的写法基本没有什么不同之处,甚至在getView里面都没有出现常见的回调(findViewByTag~用于防止图片的错位);仅仅多了一行代码:

    mImageLoader.loadImage(mDirPath + "/" + mData.get(position),holder.mImageView);是不是用起来还是相当爽的,所有需要处理的细节都被封装了。

    4、ImageLoader

    现在才到了关键的时刻,我们封装的ImageLoader类,当然我们的异步消息处理机制也出现在其中。

    首先是一个懒加载的单例

    /**
    	 * 单例获得该实例对象
    	 * 
    	 * @return
    	 */
    	public static ImageLoader getInstance()
    	{
    
    		if (mInstance == null)
    		{
    			synchronized (ImageLoader.class)
    			{
    				if (mInstance == null)
    				{
    					mInstance = new ImageLoader(1, Type.LIFO);
    				}
    			}
    		}
    		return mInstance;
    	}

    没啥说的,直接调用私有的构造方法,可以看到,默认传入了1(线程池中线程的数量),和LIFO(队列的工作方式)

    private ImageLoader(int threadCount, Type type)
    	{
    		init(threadCount, type);
    	}
    
    	private void init(int threadCount, Type type)
    	{
    		// loop thread
    		mPoolThread = new Thread()
    		{
    			@Override
    			public void run()
    			{
    				try
    				{
    					// 请求一个信号量
    					mSemaphore.acquire();
    				} catch (InterruptedException e)
    				{
    				}
    				Looper.prepare();
    
    				mPoolThreadHander = new Handler()
    				{
    					@Override
    					public void handleMessage(Message msg)
    					{
    						mThreadPool.execute(getTask());
    						try
    						{
    							mPoolSemaphore.acquire();
    						} catch (InterruptedException e)
    						{
    						}
    					}
    				};
    				// 释放一个信号量
    				mSemaphore.release();
    				Looper.loop();
    			}
    		};
    		mPoolThread.start();
    
    		// 获取应用程序最大可用内存
    		int maxMemory = (int) Runtime.getRuntime().maxMemory();
    		int cacheSize = maxMemory / 8;
    		mLruCache = new LruCache<String, Bitmap>(cacheSize)
    		{
    			@Override
    			protected int sizeOf(String key, Bitmap value)
    			{
    				return value.getRowBytes() * value.getHeight();
    			};
    		};
    
    		mThreadPool = Executors.newFixedThreadPool(threadCount);
    		mPoolSemaphore = new Semaphore(threadCount);
    		mTasks = new LinkedList<Runnable>();
    		mType = type == null ? Type.LIFO : type;
    
    	}

    然后在私有构造里面调用了我们的init方法,在这个方法的开始就创建了mPoolThread这个子线程,在这个子线程中我们执行了Looper.prepare,初始化mPoolThreadHander,Looper.loop;如果看过上篇博客,一定知道,此时在这个子线程中维护了一个消息队列,且这个子线程会进入一个无限读取消息的循环中,而mPoolThreadHander这个handler发送的消息会直接发送至此线程中的消息队列。然后看mPoolThreadHander中handleMessage的方法,直接调用了getTask方法取出一个任务,然后放入线程池去执行。如果你比较细心,可能会发现里面还有一些信号量的操作的代码,如果你不了解什么是信号量,可以参考:Java 并发专题 : Semaphore 实现 互斥 与 连接池 。 简单说一下mSemaphore(信号数为1)的作用,由于mPoolThreadHander实在子线程初始化的,所以我在初始化前调用了mSemaphore.acquire去请求一个信号量,然后在初始化完成后释放了此信号量,我为什么这么做呢?因为在主线程可能会立即使用到mPoolThreadHander,但是mPoolThreadHander是在子线程初始化的,虽然速度很快,但是我也不能百分百的保证,主线程使用时已经初始化结束,为了避免空指针异常,所以我在主线程需要使用的时候,是这么调用的:

    /**
    	 * 添加一个任务
    	 * 
    	 * @param runnable
    	 */
    	private synchronized void addTask(Runnable runnable)
    	{
    		try
    		{
    			// 请求信号量,防止mPoolThreadHander为null
    			if (mPoolThreadHander == null)
    				mSemaphore.acquire();
    		} catch (InterruptedException e)
    		{
    		}
    		mTasks.add(runnable);
    		mPoolThreadHander.sendEmptyMessage(0x110);
    	}

    如果mPoolThreadHander没有初始化完成,则会去acquire一个信号量,其实就是去等待mPoolThreadHander初始化完成。如果对此感兴趣的,可以将关于mSemaphore的代码注释,然后在初始化mPoolThreadHander使用Thread.sleep去暂停1秒,就会发现这样的错误。

    初始化结束,就会在getView中调用mImageLoader.loadImage(mDirPath + "/" + mData.get(position),holder.mImageView);方法了,所以我们去看loadImage方法吧

    /**
    	 * 加载图片
    	 * 
    	 * @param path
    	 * @param imageView
    	 */
    	public void loadImage(final String path, final ImageView imageView)
    	{
    		// set tag
    		imageView.setTag(path);
    		// UI线程
    		if (mHandler == null)
    		{
    			mHandler = new Handler()
    			{
    				@Override
    				public void handleMessage(Message msg)
    				{
    					ImgBeanHolder holder = (ImgBeanHolder) msg.obj;
    					ImageView imageView = holder.imageView;
    					Bitmap bm = holder.bitmap;
    					String path = holder.path;
    					if (imageView.getTag().toString().equals(path))
    					{
    						imageView.setImageBitmap(bm);
    					}
    				}
    			};
    		}
    
    		Bitmap bm = getBitmapFromLruCache(path);
    		if (bm != null)
    		{
    			ImgBeanHolder holder = new ImgBeanHolder();
    			holder.bitmap = bm;
    			holder.imageView = imageView;
    			holder.path = path;
    			Message message = Message.obtain();
    			message.obj = holder;
    			mHandler.sendMessage(message);
    		} else
    		{
    			addTask(new Runnable()
    			{
    				@Override
    				public void run()
    				{
    
    					ImageSize imageSize = getImageViewWidth(imageView);
    
    					int reqWidth = imageSize.width;
    					int reqHeight = imageSize.height;
    
    					Bitmap bm = decodeSampledBitmapFromResource(path, reqWidth,
    							reqHeight);
    					addBitmapToLruCache(path, bm);
    					ImgBeanHolder holder = new ImgBeanHolder();
    					holder.bitmap = getBitmapFromLruCache(path);
    					holder.imageView = imageView;
    					holder.path = path;
    					Message message = Message.obtain();
    					message.obj = holder;
    					// Log.e("TAG", "mHandler.sendMessage(message);");
    					mHandler.sendMessage(message);
    					mPoolSemaphore.release();
    				}
    			});
    		}
    
    	}

    这段代码比较长,当然也是比较核心的代码了

    10-29行:首先将传入imageView设置了path,然在初始化了一个mHandler用于设置imageView的bitmap,注意此时在UI线程,也就是这个mHandler发出的消息,会在UI线程中调用。可以看到在handleMessage中,我们从消息中取出ImageView,bitmap,path;然后将path与imageView的tag进行比较,防止图片的错位,最后设置bitmap;

    31行:我们首先去从LruCache中去查找是否已经缓存了此图片

    32-40:如果找到了,则直接使用mHandler去发送消息,这里使用了一个ImgBeanHolder去封装了ImageView,Bitmap,Path这三个对象。然后更新执行handleMessage代码去更新UI

    43-66行:如果没有存在缓存中,则创建一个Runnable对象作为任务,去执行addTask方法加入任务队列

    49行:getImageViewWidth根据ImageView获取适当的图片的尺寸,用于后面的压缩图片,代码按顺序贴下下面

    54行:会根据计算的需要的宽和高,对图片进行压缩。代码按顺序贴下下面

    56行:将压缩后的图片放入缓存

    58-64行,创建消息,使用mHandler进行发送,更新UI

    /**
    	 * 根据ImageView获得适当的压缩的宽和高
    	 * 
    	 * @param imageView
    	 * @return
    	 */
    	private ImageSize getImageViewWidth(ImageView imageView)
    	{
    		ImageSize imageSize = new ImageSize();
    		final DisplayMetrics displayMetrics = imageView.getContext()
    				.getResources().getDisplayMetrics();
    		final LayoutParams params = imageView.getLayoutParams();
    
    		int width = params.width == LayoutParams.WRAP_CONTENT ? 0 : imageView
    				.getWidth(); // Get actual image width
    		if (width <= 0)
    			width = params.width; // Get layout width parameter
    		if (width <= 0)
    			width = getImageViewFieldValue(imageView, "mMaxWidth"); // Check
    																	// maxWidth
    																	// parameter
    		if (width <= 0)
    			width = displayMetrics.widthPixels;
    		int height = params.height == LayoutParams.WRAP_CONTENT ? 0 : imageView
    				.getHeight(); // Get actual image height
    		if (height <= 0)
    			height = params.height; // Get layout height parameter
    		if (height <= 0)
    			height = getImageViewFieldValue(imageView, "mMaxHeight"); // Check
    																		// maxHeight
    																		// parameter
    		if (height <= 0)
    			height = displayMetrics.heightPixels;
    		imageSize.width = width;
    		imageSize.height = height;
    		return imageSize;
    
    	}

    /**
    	 * 根据计算的inSampleSize,得到压缩后图片
    	 * 
    	 * @param pathName
    	 * @param reqWidth
    	 * @param reqHeight
    	 * @return
    	 */
    	private Bitmap decodeSampledBitmapFromResource(String pathName,
    			int reqWidth, int reqHeight)
    	{
    		// 第一次解析将inJustDecodeBounds设置为true,来获取图片大小
    		final BitmapFactory.Options options = new BitmapFactory.Options();
    		options.inJustDecodeBounds = true;
    		BitmapFactory.decodeFile(pathName, options);
    		// 调用上面定义的方法计算inSampleSize值
    		options.inSampleSize = calculateInSampleSize(options, reqWidth,
    				reqHeight);
    		// 使用获取到的inSampleSize值再次解析图片
    		options.inJustDecodeBounds = false;
    		Bitmap bitmap = BitmapFactory.decodeFile(pathName, options);
    
    		return bitmap;
    	}
    接下来看AddTask的代码:

    /**
    	 * 添加一个任务
    	 * 
    	 * @param runnable
    	 */
    	private synchronized void addTask(Runnable runnable)
    	{
    		try
    		{
    			// 请求信号量,防止mPoolThreadHander为null
    			if (mPoolThreadHander == null)
    				mSemaphore.acquire();
    		} catch (InterruptedException e)
    		{
    		}
    		mTasks.add(runnable);
    		mPoolThreadHander.sendEmptyMessage(0x110);
    	}

    可以看到,简单把任务放入任务队列,然后使用mPoolThreadHander发送一个消息到后台的loop中,后台的loop会取出消息执行:mThreadPool.execute(getTask());

    execute执行的就是上面分析的Runnable中的run方法了。

    注意一下:上述代码中还会看到mPoolSemaphore这个信号量的身影,说下用处;因为调用addTask之后,会直接去从任务队列取出一个任务,放入线程池,由于线程池内部其实也维持着一个队列,那么”从任务队列取出一个任务”这个动作会瞬间完成,直接加入线程池维护的队列中;这样会造成比如用户设置了调度队列为LIFO,但是由于”从任务队列取出一个任务”这个动作会瞬间完成,队列中始终维持在空队列的状态,所以让用户感觉LIFO根本没有效果;所以我按照用户设置线程池工作线程的数量设置了一个信号量,这样在保证任务执行完后,才会从任务队列去取任务,使得LIFO有着很好的效果;有兴趣的可以注释了所有的mPoolSemaphore代码,测试下就明白了。

    到此代码基本介绍完毕。细节还是很多的,后面会附上源码,有兴趣的研究下代码,没有兴趣的,可以运行下代码,如果感觉流畅性不错,体验不错,可以作为工具类直接使用,使用也就getView里面一行代码。


    贴一下效果图,我手机最多的文件夹大概3000张图片,加载速度还是相当相当流畅的:


    真机录的,有点丢帧,注意看效果图,中间我疯狂拖动滚动条,但是图片基本还是瞬间显示的。

    说一下,FIFO如果设置为这个模式,在控件中不做处理的话,用户拉的比较慢效果还是不错的,但是用户手机如果有个几千张,瞬间拉到最后,最后一屏图片的显示可能需要喝杯茶了~当然了,大家可以在控件中做处理,要么,拖动的时候不去加载图片,停在来再加载。或者,当手机抬起,给了一个很大的加速度,屏幕还是很快的滑动时停止加载,停下时加载图片。

    LIFO这个模式可能用户体验会好很多,不管用户拉多块,最终停下来的那一屏图片都会瞬间显示~

    最后掰一掰使用异步消息处理机制作为背后的子线程的好处,其实直接用一个子线程也可以实现,但是,这个子线程run中可能需要while(true)然后每隔200毫秒甚至更短的时间去查询任务队列是否有任务,没有则Thread.sleep,然后再去查询;这样如果长时间没有去添加任务,这个线程依然会不断的去查询;

    而异步消息机制,只有在发送消息时才会去执行,当然更准确;当长时间没有任务到达时,也不会去查询,会一直阻塞在这;还有一点,这个机制Android内部实现的,怎么也比我们搞个Thread稳定性、效率高吧~


    源码点击下载







    展开全文
  • Android异步消息处理机制 全解析

    千次阅读 2019-06-06 00:12:29
    Android异步消息处理机制的重要性不言而喻: 中高级工程师面试必问、阅读源码基础。 本文将通过结合源码的形式,全面解析Handler和MessageQueue、Looper的关系.并分析Android异步消息机制的相关原理.

    本文已授权微信公众号"Android技术杂货铺" 发布。转载请申明出处。

    Android异步消息处理机制主要是指Handler的运行机制以及Hanlder所附带的MessageQueue和Looper的工作过程。

    本文将通过结合源码(api-28)的形式,全面解析Handler和MessageQueue、Looper的关系。并分析Android异步消息机制的相关原理。在分析之前,先给出结论性的东西,便于在分析过程中有一个主脉络。
    在这里插入图片描述

    一.Handler

    在分析Handler源码之前,我们尝试在程序中创建两个Handler对象,一个在主线程中创建,一个在子线程中创建,代码如下所示:

    //代码片1
    public class MainActivity extends Activity {
    	
    	private Handler handler1;
    	
    	private Handler handler2;
     
    	@Override
    	protected void onCreate(Bundle savedInstanceState) {
    		super.onCreate(savedInstanceState);
    		setContentView(R.layout.activity_main);
    		handler1 = new Handler();
    		new Thread(new Runnable() {
    			@Override
    			public void run() {
    				handler2 = new Handler();
    			}
    		}).start();
    	}
     
    }
    

    运行代码之后,会报错: java.lang.RuntimeException: Can’t create handler inside thread that has not called Looper.prepare()
    并且提示是第16行代码报错.说是不能在没有调用Looper.prepare() 的线程中创建Handler。

    那我们在第16行代码前面尝试先调用一下Looper.prepare()呢,即代码如下:

    //代码片2
    new Thread(new Runnable() {
    	@Override
    	public void run() {
    		Looper.prepare();
    		handler2 = new Handler();
    	}
    }).start();
    

    运行后发现的确没有报错了。

    或许我们有疑问,为什么在子线程中加了Looper.prepare() 就不再报错了呢? 并且为什么第12行代码没有加Looper.prepare() 却没有报错。

    先带着第一个问题(即 子线程加了Looper.prepare() 后不报错),来看看Handler的源码吧:

    //代码片3
    public Handler(Callback callback, boolean async) {
          ......
          //仅贴出核心代码
            mLooper = Looper.myLooper();
            if (mLooper == null) {
                throw new RuntimeException(
                    "Can't create handler inside thread " + Thread.currentThread()
                            + " that has not called Looper.prepare()");
            }
            mQueue = mLooper.mQueue;
            mCallback = callback;
            mAsynchronous = async;
        }
    

    可以看到,在第5行调用了Looper.myLooper()方法获取了一个Looper对象,如果Looper对象为空,则会抛出一个运行时异常,提示的错误正是 Can’t create handler inside thread that has not called Looper.prepare()!那什么时候Looper对象才可能为空呢?这就要看看Looper.myLooper()中的代码了,如下所示:

    //代码片4
     public static @Nullable Looper myLooper() {
            return sThreadLocal.get();
        }
    

    这个方法非常简单,就是从sThreadLocal对象中调用get()方法。

    从名字我们可以大胆猜测一下,这是从sThreadLocal取出Looper,如果没有Looper存在自然就返回空了。

    既然有get,应该也有set()方法,我们搜索一下(在Looper.java类中) sThreadLocal.set,果然有:

    代码如下:

    //代码片5
     private static void prepare(boolean quitAllowed) {
            if (sThreadLocal.get() != null) {
                throw new RuntimeException("Only one Looper may be created per thread");
            }
            sThreadLocal.set(new Looper(quitAllowed));
        }
    

    上述代码片即为Looper.prepare()方法。

    可以看到,首先判断sThreadLocal中是否已经存在Looper了,如果还没有则创建一个新的Looper设置进去。这样也就完全解释了为什么我们要先调用Looper.prepare()方法,才能创建Handler对象。同时也可以看出每个线程中最多只会有一个Looper对象。

    现在我们来通过源码分析 代码片1第12行代码没有添加Looper.myLooper() 方法也没有报错 的原因。这是由于在程序启动的时候,系统已经帮我们自动调用了Looper.prepare()方法。查看ActivityThread中的main()方法,代码如下所示:

    //代码片6
    public static void main(String[] args) {
       .....
       //核心代码如下
        Looper.prepareMainLooper();
        ActivityThread thread = new ActivityThread();     //创建主线程
        thread.attach(false);
        if (sMainThreadHandler == null) {
            sMainThreadHandler = thread.getHandler();
        }
        AsyncTask.init();
        if (false) {
            Looper.myLooper().setMessageLogging(new LogPrinter(Log.DEBUG, "ActivityThread"));
        }
        Looper.loop();
        throw new RuntimeException("Main thread loop unexpectedly exited");
    }
    

    可以看到,在第5行调用了Looper.prepareMainLooper()方法,而这个方法又会再去调用Looper.prepare()方法,代码如下所示:

    //代码片7
     public static void prepareMainLooper() {
            prepare(false);
            synchronized (Looper.class) {
                if (sMainLooper != null) {
                    throw new IllegalStateException("The main Looper has already been prepared.");
                }
                sMainLooper = myLooper();
            }
        }
    
    //代码片8
    private static void prepare(boolean quitAllowed) {
            if (sThreadLocal.get() != null) {
                throw new RuntimeException("Only one Looper may be created per thread");
            }
            sThreadLocal.set(new Looper(quitAllowed));
        }
    

    因此我们应用程序的主线程中会始终存在一个Looper对象,从而不需要再手动去调用Looper.prepare()方法了。

    简单总结一下: Android的主线程是ActivityThread,主线程的入口为main,在main方法中系统会通过Looper.prepareMainLooper()来创建主线程的Looper对象,同时也会生成其对应的MessageQueue对象,即主线程的Looper对象自动生成,不需手动生成;而子线程的Looper对象需手动通过Looper.prepare()创建。

    学习完了Handler如何创建对象,我们看一下如何发送消息:

    这个流程相信大家也已经非常熟悉了,new出一个Message对象,然后可以使用setData()方法或arg参数等方式为消息携带一些数据,再借助Handler将消息发送出去就可以了,示例代码如下:

    //代码片9
    new Thread(new Runnable() {
    	@Override
    	public void run() {
    		Message message = new Message();
    		message.arg1 = 1;
    		Bundle bundle = new Bundle();
    		bundle.putString("data", "data");
    		message.setData(bundle);
    		handler.sendMessage(message);
    	}
    }).start();
    

    那我们来看一下sendMessage方法干了什么。源码如下:

    //代码片10
     public final boolean sendMessage(Message msg) {
            return sendMessageDelayed(msg, 0);
       }
    
    //代码片11
     public final boolean sendMessageDelayed(Message msg, long delayMillis) {
            if (delayMillis < 0) {
                delayMillis = 0;
            }
            return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
      }
    
    //代码片12
    public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
            MessageQueue queue = mQueue;
            if (queue == null) {
                RuntimeException e = new RuntimeException(
                        this + " sendMessageAtTime() called with no mQueue");
                Log.w("Looper", e.getMessage(), e);
                return false;
            }
            return enqueueMessage(queue, msg, uptimeMillis);
        }
    
    //代码片13
       private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
            msg.target = this;       //把当前的Hanlder实例对象作为msg的target属性
            if (mAsynchronous) {
                msg.setAsynchronous(true);
            }
            return queue.enqueueMessage(msg, uptimeMillis);      //执行接下来即将分析的代码片14
        }
    

    代码片10~代码片13这四个方法都是连贯在一起的。

    sendMessageAtTime()方法接收两个参数,其中msg参数就是我们发送的Message对象,而uptimeMillis参数则表示发送消息的时间,它的值等于自系统开机到当前时间的毫秒数( SystemClock.uptimeMillis() )再加上延迟时间,如果你调用的不是sendMessageDelayed()方法,延迟时间就为0,然后将这两个参数都传递到MessageQueue的enqueueMessage()方法中。

    也说明了handler发出的消息msg,最终会保存到消息队列(MessageQueue )中去。

    二.MessageQueue

    在一中已经讲到handler发出的消息保存到了MessageQueue 中。消息队列在Android中指的是MessageQueue ,MessageQueue主要包括两个操作: 插入和读取,分别对应enqueueMessage和next,其中enqueueMessage的作用是往消息队列中插入一条消息,而next的作用是从消息队列中取出一条消息并将其从消息队列中移除。

    需要说明的是,MessageQueue 虽然叫消息队列,但它的内部实现并不是用的队列,而是通过一个单链表的数据结构来维护消息队列,单链表在插入和删除上比较有优势。

    1. enqueueMessage源码

    我们先来分析MessageQueue 的enqueueMessage源码:

    //代码片14
    boolean enqueueMessage(Message msg, long when) {
    
           .....
           //仅贴出核心代码
           
            synchronized (this) {
               .....
              //仅贴出核心代码
    
                msg.markInUse();
                msg.when = when;
                Message p = mMessages;
                boolean needWake;
                //若消息队列里没有消息,则将当前插入的消息作为队头 & 若此时消息队列处于等待状态,则唤醒
                if (p == null || when == 0 || when < p.when) {
                    // New head, wake up the event queue if blocked.
                    msg.next = p;
                    mMessages = msg;
                    needWake = mBlocked;
                } else {
                   //若消息队列中有消息,则根据消息(message)创建的时间插入到队列中
                    // Inserted within the middle of the queue.  Usually we don't have to wake
                    // up the event queue unless there is a barrier at the head of the queue
                    // and the message is the earliest asynchronous message in the queue.
                    
                    needWake = mBlocked && p.target == null && msg.isAsynchronous();
                    Message prev;
                    for (;;) {
                        prev = p;
                        p = p.next;
                        if (p == null || when < p.when) {
                            break;
                        }
                        if (needWake && p.isAsynchronous()) {
                            needWake = false;
                        }
                    }
                    msg.next = p; // invariant: p == prev.next
                    prev.next = msg;
                }
    
                // We can assume mPtr != 0 because mQuitting is false.
                if (needWake) {
                    nativeWake(mPtr);
                }
            }
            return true;
        }
    

    观察上面的代码核心代码我们就可以看出,所谓的入队其实就是将所有的消息按时间来进行排序,这个时间当然就是我们刚才介绍的uptimeMillis参数。具体的操作方法就根据时间的顺序调用msg.next,从而为每一个消息指定它的下一个消息是什么。当然如果你是通过sendMessageAtFrontOfQueue()方法来发送消息的,它也会调用enqueueMessage()来让消息入队,只不过时间为0,这时会把mMessages赋值为新入队的这条消息,然后将这条消息的next指定为刚才的mMessages,这样也就完成了添加消息到队列头部的操作。

    2. next 源码

    入队操作我们就已经看明白了,那出队操作是在哪里进行的呢?

    这里先告诉结论,在Looper.loop()中有这样一行代码(见 稍后会分析的代码片21的第12行)。

    next()方法在MessageQueue.class中。这段代码比较长,我们只贴出核心代码:

    //代码片15
    Message next() {
          ......
          //仅贴出核心代码
          
          // 该参数用于确定消息队列中是否还有消息,从而决定消息队列应处于出队消息状态 or 等待状态
            int nextPollTimeoutMillis = 0;
            for (;;) {
                if (nextPollTimeoutMillis != 0) {
                    Binder.flushPendingCommands();
                }
    
               // nativePollOnce方法在native层,若是nextPollTimeoutMillis为-1,此时消息队列处于等待状态
                nativePollOnce(ptr, nextPollTimeoutMillis);
    
                synchronized (this) {
                    // Try to retrieve the next message.  Return if found.
                    final long now = SystemClock.uptimeMillis();
                    Message prevMsg = null;
                    Message msg = mMessages;
                    
                    if (msg != null && msg.target == null) {
                        // Stalled by a barrier.  Find the next asynchronous message in the queue.
                        do {
                            prevMsg = msg;
                            msg = msg.next;
                        } while (msg != null && !msg.isAsynchronous());
                    }
    
                   // 出队消息,即 从消息队列中取出消息:按创建Message对象的时间顺序
                    if (msg != null) {
                        if (now < msg.when) {
                            // Next message is not ready.  Set a timeout to wake up when it is ready.
                            nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
                        } else {
                            // Got a message.
                            mBlocked = false;
                            if (prevMsg != null) {
                                prevMsg.next = msg.next;
                            } else {
                                mMessages = msg.next;
                            }
                            msg.next = null;
                            if (DEBUG) Log.v(TAG, "Returning message: " + msg);
                            msg.markInUse();
                            return msg;
                        }
                    } else {
                        // No more messages.
                         // 若 消息队列中已无消息,则将nextPollTimeoutMillis参数设为-1。下次循环时,消息队列则处于等待状态
                        nextPollTimeoutMillis = -1;
                    }    
    
                  ......
                  //仅贴出核心代码
            }    // 因为是死循环,回到分析原处
        }
    

    MessageQueue的next()方法关键思路在源码中我已经通过中文注释标明了,它的简单逻辑就是如果当前MessageQueue中存在mMessages(即待处理消息),就将这个消息出队,然后让下一条消息成为mMessages,否则就进入一个阻塞状态,一直等到有新的消息入队。继续看loop()方法的代码片第12行,每当有一个消息出队,就将它传递到msg.target的dispatchMessage()方法中,那这里msg.target又是什么呢?其实就是Handler啦,你观察一下上面sendMessageAtTime()方法的第6行就可以看出来了。接下来当然就要看一看Handler中dispatchMessage()方法的源码了,如下所示(Handler,class中):

    //代码片16
     public void dispatchMessage(Message msg) {
            if (msg.callback != null) {
                handleCallback(msg);
            } else {
                if (mCallback != null) {
                    if (mCallback.handleMessage(msg)) {
                        return;
                    }
                }
                handleMessage(msg);
            }
        }
    

    在第5~6行进行判断,如果mCallback不为空,则调用mCallback的handleMessage()方法,否则直接调用Handler的handleMessage()方法,并将消息对象作为参数传递过去。那 handleMessage(msg)、handleCallback(msg) 具体是干嘛的呢? 我们继续看:

    //代码片17
    public void handleMessage(Message msg) {
    }
    

    可以看到这是一个空方法,为什么呢,因为消息的最终回调是由我们控制的,我们在创建handler的时候都是复写handleMessage方法,然后根据msg.what进行消息处理。例如:

    //代码片18
    private Handler mHandler = new Handler(){
    		public void handleMessage(android.os.Message msg){
    			switch (msg.what){
    			case value:
    			    //do something
    				break;
    			default:
    			  //do something
    				break;
    			}
    		};
    	};
    

    至于handleCallback的代码如下:

    //代码片19
     private static void handleCallback(Message message) {
            message.callback.run();
        }
    

    执行的是Message 回调的run方法.其实就是我们平时写的如下代码中的run方法:

    //代码片20
    public class MainActivity extends Activity {
     
    	private Handler handler;
     
    	@Override
    	protected void onCreate(Bundle savedInstanceState) {
    		super.onCreate(savedInstanceState);
    		setContentView(R.layout.activity_main);
    		handler = new Handler();
    		new Thread(new Runnable() {
    			@Override
    			public void run() {
    				 //do  something
    			}
    		}).start();
    	}
    }
    

    三.Looper

    Looper的源码分析在一、二部分或多或少提到过.这里集中分析。

    Looper主要是prepare()和loop()两个方法。

    1. Looper.prepare()方法:

    在Handler部分的代码片3、4、5、6、7、8及说明中已经谈论过,这里略过。

    2. Looper.loop()方法

    这段代码比较长,我们还是看核心代码:

    //代码片21
    public static void loop() {
            final Looper me = myLooper();
            if (me == null) {
                throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
            }
            final MessageQueue queue = me.mQueue;
            
            .....
            
            for (;;) {
                Message msg = queue.next(); // might block
                if (msg == null) {
                    // No message indicates that the message queue is quitting.
                    return;
                }
    
              ......
              
                try {
                    msg.target.dispatchMessage(msg);
                    dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
                } finally {
                    if (traceTag != 0) {
                        Trace.traceEnd(traceTag);
                    }
                }
    
              ......
    
                msg.recycleUnchecked();      //释放资源
            }
        }
    

    可以看到,这个方法从第11行开始,进入了一个死循环,然后不断地调用的MessageQueue的next()方法(即代码片15部分,也就是 二中MessageQueue的next()部分),我想你已经猜到了,这个next()方法就是消息队列的出队方法。在二中已经分析过了。

    总结一下,Looper主要作用:

    1. 与当前线程绑定,保证一个线程只会有一个Looper实例,同时一个Looper实例也只有一个MessageQueue。
    2. loop()方法,不断从MessageQueue中去取消息,交给消息的target属性的dispatchMessage去处理。

    到此,这个流程已经解释完毕,让我们首先总结一下

    1.首先Looper.prepare()在本线程中保存一个Looper实例,然后该实例中保存一个MessageQueue对象;因为Looper.prepare()在一个线程中只能调用一次,所以MessageQueue在一个线程中只会存在一个。

    2.Looper.loop()会让当前线程进入一个无限循环,不断从MessageQueue的实例中读取消息,然后回调msg.target.dispatchMessage(msg)方法。

    3.Handler的构造方法,会首先得到当前线程中保存的Looper实例,进而与Looper实例中的MessageQueue相关联。

    4.Handler的sendMessage方法,会给msg的target赋值为handler自身,然后加入MessageQueue中。(代码片13第3行有中文注释说明)

    5.在构造Handler实例时,我们会重写handleMessage方法,也就是msg.target.dispatchMessage(msg)最终调用的方法。

    看完了上述总结后,一个最标准的异步消息处理线程的写法应该是这样:

    //代码片22
    class LooperThread extends Thread {
          public Handler mHandler;
     
          public void run() {
              Looper.prepare();
     
              mHandler = new Handler() {
                  public void handleMessage(Message msg) {
                      // process incoming messages here
                  }
              };
     
              Looper.loop();
          }
      }
    

    那么我们还是要来继续分析一下,为什么使用异步消息处理的方式就可以对UI进行操作了呢?这是由于Handler总是依附于创建时所在的线程,比如我们的Handler是在主线程中创建的,而在子线程中又无法直接对UI进行操作,于是我们就通过一系列的发送消息、入队、出队等环节,最后调用到了Handler的handleMessage()方法中,这时的handleMessage()方法已经是在主线程中运行的,因而我们当然可以在这里进行UI操作了。整个异步消息处理流程的示意图如下图所示:
    在这里插入图片描述

    四 . 其他在子线程操作UI的方法

    另外除了发送消息之外,我们还有以下几种方法可以在子线程中进行UI操作:

    1. Handler的post()方法
    2. View的post()方法
    3. Activity的runOnUiThread()方法

    我们先看Handler的post()方法:

    //代码片23
     public final boolean post(Runnable r)  {
           return  sendMessageDelayed(getPostMessage(r), 0);
      }
    

    这里分析一下getPostMessage®方法:

    //代码片24
    private final Message getPostMessage(Runnable r) {
        Message m = Message.obtain();
        m.callback = r;
        return m;
    }
    

    原来这里还是调用了sendMessageDelayed()方法去发送一条消息啊,并且还使用了getPostMessage()方法将Runnable对象转换成了一条消息.

    //代码片25
     public final boolean sendMessageDelayed(Message msg, long delayMillis)   {
            if (delayMillis < 0) {
                delayMillis = 0;
            }
            return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
     }
    

    到了这里的sendMessageAtTime方法,其实已经和第11个代码片完全一样了.

    然后再来看一下View中的post()方法,代码如下所示:

    //代码片26
     public boolean post(Runnable action) {
            final AttachInfo attachInfo = mAttachInfo;
            if (attachInfo != null) {
                return attachInfo.mHandler.post(action);
            }
    
            // Postpone the runnable until we know on which thread it needs to run.
            // Assume that the runnable will be successfully placed after attach.
            getRunQueue().post(action);
            return true;
        }
    

    如果attachInfo 不为空,就调用Handler中的post()方法。

    如果为空,我们看看一下代码片25第10行代码:

    //代码片27
    private HandlerActionQueue getRunQueue() {
            if (mRunQueue == null) {
                mRunQueue = new HandlerActionQueue();
            }
            return mRunQueue;
        }
    

    即 看 getRunQueue().post(action) 做看什么,只需要在HandlerActionQueue类中查看post()方法即可。
    继续看(HandlerActionQueue.class类)

    //代码片28
    public void post(Runnable action) {
            postDelayed(action, 0);
     }
    
    public void postDelayed(Runnable action, long delayMillis) {
            final HandlerAction handlerAction = new HandlerAction(action, delayMillis);
    
            synchronized (this) {
                if (mActions == null) {
                    mActions = new HandlerAction[4];
                }
                mActions = GrowingArrayUtils.append(mActions, mCount, handlerAction);
                mCount++;
            }
        }
    

    结合这篇文章(点击跳转),可以看出,如果attachInfo 为空,最终也会调用Handler中的post()方法。

    我们最后再来看一下Activity中的runOnUiThread()方法:

    //代码片29
       /**
         * Runs the specified action on the UI thread. If the current thread is the UI
         * thread, then the action is executed immediately. If the current thread is
         * not the UI thread, the action is posted to the event queue of the UI thread.
         *
         * @param action the action to run on the UI thread
         */
        public final void runOnUiThread(Runnable action) {
            if (Thread.currentThread() != mUiThread) {
                mHandler.post(action);
            } else {
                action.run();
            }
        }
    

    如果当前线程不是UI线程,就调用Handler的post()方法;反之直接运行.(也可以结合方法上的注释看)。

    最后,我们总结一下Android异步消息处理机制流程:

    在子线程执行完耗时操作,当Handler发送消息时(Handler.sendMessage(Message msg) 或 Handler.post(Runable r)),就会调用MessageQueue.enqueueMessage,将消息根据时间放入到消息队列中。当通过Looper.loop开启循环后,会不断地从线程池中读取消息,即调用MessageQueue.next方法,然后调用目标Handler(即发送消息的Handler)的dispatchMessage方法传递消息,然后返回到Handler所在的线程,目标Handler收到消息,调用handleMessage方法,接收消息,处理消息。

    对此,Android的异步消息处理机制全部分析完毕。


    推荐阅读:

    1. 你真的懂Handler吗?Handler问答
    展开全文
  • 异步消息处理线程是指线程启动后会进入一个无限循环,每循环一次,从内部的消息队列里面取出一个消息,并回调相应的消息处理函数。一般在任务常驻,比如用户交互任务的情况下使用异步消息处理线程。之前在Android中...

    异步消息处理线程是指线程启动后会进入一个无限循环,每循环一次,从内部的消息队列里面取出一个消息,并回调相应的消息处理函数。一般在任务常驻,比如用户交互任务的情况下使用异步消息处理线程。

    之前在Android中Handler原理里面研究过android里实现异步消息处理线程的方式,基本逻辑如图所示
    这里写图片描述
    今天就用java将其简单的模拟出来加深印象,下面的类图是用工具导出的,不太正规,不过能大概看出类之间的关系
    这里写图片描述

    Message类:消息类

    public class Message {
    
        public int what;
    
        public Object obj;
    
        public Handler target;
    }

    Looper类:用来将普通的线程变为异步消息循环的类,维护了一个消息队列。每个线程有且仅有Looper对象,且是线程封闭的,每个线程都有自己的一份拷贝。

    public class Looper {
    
        private MessageQueue queue;
        private static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
    
        public Looper() {
            queue = new MessageQueue();
        }
    
        public static void prepare(){
            if (sThreadLocal.get() != null) {
                throw new RuntimeException("Only one Looper can be created per thread");
            }
            sThreadLocal.set(new Looper());
        }
    
        public static Looper myLooper() {
            return sThreadLocal.get();
        }
    
        public static MessageQueue myQueue() {
            return myLooper().queue;
        }
    
        public static void loop(){
            while(true){
                Message msg = myQueue().deQueueMessage();
                if (msg == null) {
                    continue;
                }else {
                    msg.target.dispatchMessage(msg);
                }
            }
        }
    }

    MessageQueue 类:消息队列,按照先进先出的原则从队列里面取出消息Message

    public class MessageQueue {
    
        private Queue<Message> queue = new LinkedList<Message>();
    
        public synchronized void enqueueMessage(Message msg){
            queue.offer(msg);
        }
    
        public synchronized Message deQueueMessage(){
            if (queue.isEmpty()) {
                return null;
            }
            return queue.poll();
        }   
    }

    Handler类:一般不直接操作队列,通过handler往消息队列里面加入Message对象。并且暴露handlerMessage方法让程序员重写达到自己的处理的目的。

    public class Handler {
    
        private MessageQueue queue;
        public Handler(){
            queue = Looper.myQueue();
        }
    
        public void sendMessage(Message msg){
            msg.target = this;
            queue.enqueueMessage(msg);
        }
    
    
        public void dispatchMessage(Message msg) {
            handleMessage(msg);
        }
    
        public void handleMessage(Message msg){}
    
    }

    Main 类:程序运行的启动类

    public class Main {
    
        public static void main(String[] args) {
    
            Looper.prepare();
            TestInterface test = new Test();
            test.onCreate();
            Looper.loop();
        }
    }

    TestInfo接口:规范了测试的一个接口

    public interface TestInterface {
    
        void onCreate();
    }

    Test 类:暴露出来的唯一的需要程序员填写代码的类我们在一个定时器里面每一秒钟发送一条消息到主线程,并将其显示出来

    public class Test implements TestInterface{
    
        private static SimpleDateFormat sdf=new SimpleDateFormat("HH:mm:ss");
    
        private Handler handler = new Handler(){
            public void handleMessage(Message msg) {
                if (msg.what == 1) {
                    System.out.println("handler--->" + (String)msg.obj);
                }
            };
        };
    
        @Override
        public void onCreate() {
            new Timer().schedule(new TimerTask() {
                @Override
                public void run() {
                    Message msg = new Message();
                    msg.what = 1;
                    msg.obj = "test" + sdf.format(new Date());
                    handler.sendMessage(msg);
                }
            }, 0,1000);
        }
    }

    测试结果如下:

    handler—>test21:32:43
    handler—>test21:32:44
    handler—>test21:32:45
    handler—>test21:32:46
    handler—>test21:32:47
    handler—>test21:32:48
    handler—>test21:32:49
    …….

    android子线程中不能更新UI,一般都是通过消息机制在UI线程中更新UI的。

    展开全文
  • Android异步消息处理机制详解及源码分析

    万次阅读 多人点赞 2015-05-28 09:20:12
    怎么样,这和你平时用的Handler一样吧,对于Handler异步处理的简单基础示例先说到这,接下来依据上面示例的写法分析原因与源代码原理。 3 分析Android 5.1.1(API 22)异步消息机制源码 3-1 看看Handler的...
  • 异步消息处理机制--线程

    千次阅读 2017-02-11 14:06:23
    多线程编程 执行一条耗时操作,需放在子线程里运行 1、线程的基本用法 新建类继承 或实现接口...~run () { //处理具体的逻辑 } } new MyThread( ).start ( ) ; // 使用则 new一个实例即可 ● class MyThread imp

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 156,342
精华内容 62,536
关键字:

java异步消息处理机制

java 订阅