Android异步之Asynctask与Handler面试七问

提出问题:

1、Android中的异步处理方式?

2、如何使用Handler以及在使用过程中如何避免Handler引起的内存泄露?

3、从源码角度分析MessageQueue,Message,handler,looper,主线程,子thread之间的关系

4、Handler通过sendMessage以及post Runable对象有什么区别

5、如何给一个线程建立消息循环,即如何构建一个looper线程?

6、Asynctask中有哪些方法,分别如何使用,哪些方法在主线程执行,哪些方法在子线程执行,Asynctask中的参数关系

7、Asynctask与使用Handler+thread的优缺点对比(区别)

解决问题:

问题1 在Android中的异步处理方式?

Android中的异步处理方式可以采用

a:Handler+Thread

b:异步任务Asynctask

c:Thread+回调

d:a+c

问题2 如何使用Handler以及在使用过程中如何避免Handler引起的内存泄露?

问题4   Handler通过sendMessage以及post Runable对象有什么区别

public class ThreadHandlerActivity extends Activity {
	private static final int MSG_SUCCESS = 0;// 获取图片成功的标识
	private static final int MSG_FAILURE = 1;// 获取图片失败的标识

	private ImageView mImageView;
	private Button mButton;

	private Thread mThread;

	private Handler mHandler;

	@Override
	public void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.thread_layout);

		mHandler = new MyHandler(this);

		mImageView = (ImageView) findViewById(R.id.imageView);// 显示图片的ImageView
		mButton = (Button) findViewById(R.id.button);
		mButton.setOnClickListener(new OnClickListener() {

			@Override
			public void onClick(View v) {
				if (mThread == null) {
					mThread = new Thread(runnable);
					mThread.start();// 线程启动
				} else {
					Toast.makeText(
							getApplication(),
							getApplication().getString(R.string.thread_started),
							Toast.LENGTH_LONG).show();
				}
			}
		});
	}

	Runnable runnable = new Runnable() {

		@Override
		public void run() {// run()在新的线程中运行
			HttpClient hc = new DefaultHttpClient();
			HttpGet hg = new HttpGet(
					"http://csdnimg.cn/www/images/csdnindex_logo.gif");// 获取csdn的logo
			final Bitmap bm;
			try {
				HttpResponse hr = hc.execute(hg);
				bm = BitmapFactory.decodeStream(hr.getEntity().getContent());
			} catch (Exception e) {
				mHandler.obtainMessage(MSG_FAILURE).sendToTarget();// 获取图片失败
				return;
			}
			mHandler.obtainMessage(MSG_SUCCESS, bm).sendToTarget();// 获取图片成功,向ui线程发送MSG_SUCCESS标识和bitmap对象

			// mImageView.setImageBitmap(bm); //出错!不能在非ui线程操作ui元素

			/*
			 *
			 * 另外一种更简洁的发送消息给ui线程的方法。
			mHandler.post(new Runnable() {
			//其实这个Runnable并没有创建什么线程,而是发送了一条消息
			 * 通过源码可以看出其实post出的runnable对象最终也是转化成message加入到队列
				@Override
				public void run() {
				    // run()方法会在ui线程执行
					mImageView.setImageBitmap(bm);
				}
			});
			*
			*/

		}
	};

	/**
	 * @author ss
	 * 问题:如何避免Handler引起的内存泄露
	 * 1、不使用非静态内部类,继承Handler的时候,要么放在单独的类文件中,要么就使用静态内部类
	 * why----->在java中,非静态的内部类和匿名内部类都会隐式地持有其外部类的引用,而静态的内部类
	 * 不会持有外部类的引用。
	 * 假如在线程中执行如下方法:
	           mHandler.postDelayed(new Runnable() {
	           这里的runnable如果不采用匿名内部类,而是在外面声明,则也应该设置成静态
                 @Override
                 public void run() {
                	 ...
                 }
			 }, 1000 * 60 * 10);
			 外部调用finish()销毁Activity
	 * 如果我们使用非静态的MyHandler, 当我们的代码执行finish方法时,被延迟的消息会在被处理之前存在于
	 * 主线程消息队列中10分钟,我们发送一个target为这个Handler的消息到Looper处理的消息队列时,实际上
	 * 已经发送的消息已经包含了一个Handler实例的引用,只有这样Looper在处理到这条消息时才可以
	 * 调用Handler#handleMessage(Message)完成消息的正确处理。
	 * 但是非静态的MyHandler持有外部ThreadHandlerActivity的引用
	 * 所以这导致了ThreadHandlerActivity无法回收,进行导致ThreadHandlerActivity持有的很多资源都
	 * 无法回收,这就是我们常说的内存泄露。
	 *
	 */
	private static class MyHandler extends Handler {
		//2、当你需要在静态内部类中调用外部的Activity时,我们可以使用弱引用来处理。
		WeakReference<ThreadHandlerActivity> thisLayout;

		MyHandler(ThreadHandlerActivity layout) {
			thisLayout = new WeakReference<ThreadHandlerActivity>(layout);
		}

		public void handleMessage(Message msg) {// 此方法在ui线程运行
			final ThreadHandlerActivity theLayout = thisLayout.get();
			if (theLayout == null) {
				return;
			}

			switch (msg.what) {
			case MSG_SUCCESS:
				theLayout.mImageView.setImageBitmap((Bitmap) msg.obj);// imageview显示从网络获取到的logo
				Toast.makeText(theLayout,
						theLayout.getString(R.string.get_pic_success),
						Toast.LENGTH_LONG).show();
				break;

			case MSG_FAILURE:
				Toast.makeText(theLayout,
						theLayout.getString(R.string.get_pic_failure),
						Toast.LENGTH_LONG).show();
				break;
			}
		}
	}
}

问题3   从源码角度分析MessageQueue,Message,handler,looper,主线程,子thread之间的关系

问题5   如何给一个线程建立消息循环,即如何构建一个looper线程?

public class LooperThreadActivity extends Activity{
	/**
	 * MessageQueue,Message,handler,looper,主线程,子thread之间的关系
	 *
	 * MessageQueue:消息队列,每个线程最多拥有一个
	 *
	 * Message 消息对象
	 *
	 * Looper:与当前线程绑定,保证一个线程只会有一个Looper实例,同时一个Looper也只有一个
	 * MessageQueue不断从MessageQueue中去取消息,交给消息的target属性对应的Handler的dispatchMessage
	 * 去处理
	 *
	 * Looper是MessageQueue的管理者。每一个MessageQueue都不能脱离Looper而存在,Looper
	 * 对象的创建是通过prepare函数来实现的。同时每一个Looper对象和一个线程关联。通过调用
	 * Looper.myLooper()可以获得当前线程的Looper对象 。创建一个Looper对象时,会同时创建
	 * 一个MessageQueue对象。除了主线程有默认的Looper,其他线程默认是没有MessageQueue对象的
	 * ,所以,不能接受Message。如需要接受,自己定义 一个Looper对象(通过prepare函数),
	 * 这样该线程就有了自己的Looper对象和MessageQueue数据结构了。  Looper从MessageQueue中
	 * 取出Message然后,交由Handler的handleMessage进行处理。处理完成后,调用
	 * Message.recycle()将其放入Message Pool中。
	 * */

     private final int MSG_HELLO = 0;
     private Handler mHandler; 

    @Override
     public void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
         setContentView(R.layout.main);
         new CustomThread().start();//新建并启动CustomThread实例  

        findViewById(R.id.send_btn).setOnClickListener(new OnClickListener() { 

             @Override
             public void onClick(View v) {//点击界面时发送消息
                 String str = "hello";
                 Log.d("Test", "MainThread is ready to send msg:" + str);
                 mHandler.obtainMessage(MSG_HELLO, str).sendToTarget();//发送消息到CustomThread实例  

             }
         }); 

     } 

     class CustomThread extends Thread {
         @Override
        public void run() {
             //建立消息循环的步骤
            Looper.prepare();//1、初始化Looper
            //Handler的构造方法,会首先得到当前线程中保存的Looper实例,
            //进而与Looper实例中的MessageQueue关联。
            mHandler = new Handler(){//2、绑定handler到CustomThread实例的Looper对象
                 public void handleMessage (Message msg) {//3、定义处理消息的方法
                     switch(msg.what) {
                     case MSG_HELLO:
                        Log.d("Test", "CustomThread receive msg:" + (String) msg.obj);
                     }
                 }
             };
             Looper.loop();//4、启动消息循环
         }
     }
 } 

问题6 Asynctask中有哪些方法,分别如何使用,哪些方法在主线程执行,哪些方法在子线程执行,Asynctask中的参数关系

轻量级的异步抽象类

定义了三种泛型类型Params,Progress和Result,都是Object类型

onPreExecute() 当任务执行之前开始调用此方法,可以在这里显示进度对话框。

doInBackground(Params...) 此方法在后台线程执行,完成任务的主要工作,通常需要较长的时间。在执行过程中可以调用publicProgress(Progress...)来更新任务的进度。

onProgressUpdate(Progress...) 此方法在主线程执行,用于显示任务执行的进度。

onPostExecute(Result) 此方法在主线程执行,任务执行的结果作为此方法的参数返回。

onCancelled()  用户调用取消方法时,将回调该函数。

实例必须在UI主线程中创建

execute方法必须在主线程中调用

永远不要手动的调用

onPreExecute(),

onPostExecute(Result),

doInBackground(Params...),

onProgressUpdate(Progress...)这几个方法

一个AsyncTask实例只能被Execute执行一次,否则多次Execute时将会出现异常

配置了5个可用线程,

Executor(Executors.newCachedThreadPool())

/**
 * 生成该类的对象,并调用execute方法之后
 * 首先执行的是onProExecute方法
 * 其次执行doInBackgroup方法
 */
public class ProgressBarAsyncTask extends AsyncTask<Integer, Integer, String> {  

    private TextView textView;
    private ProgressBar progressBar;  

    public ProgressBarAsyncTask(TextView textView, ProgressBar progressBar) {
        super();
        this.textView = textView;
        this.progressBar = progressBar;
    }  

    /**
     * 这里的Integer参数对应AsyncTask中的第一个参数
     * 这里的String返回值对应AsyncTask的第三个参数
     * 该方法并不运行在UI线程当中,主要用于异步操作,所有在该方法中不能对UI当中的空间进行设置和修改
     * 但是可以调用publishProgress方法触发onProgressUpdate对UI进行操作
     */
    @Override
    protected String doInBackground(Integer... params) {
    	//Integer... params代表0或者N个Integer类型的值
        NetOperator netOperator = new NetOperator();
        int i = 0;
        for (i = 10; i <= 100; i+=10) {
            netOperator.operator();
            publishProgress(i);
        }
       //这里面params[0]即为execute(1000)中的1000
        return i + params[0].intValue() + "";
    }  

    /**
     * 这里的String参数对应AsyncTask中的第三个参数(也就是接收doInBackground的返回值)
     * 在doInBackground方法执行结束之后在运行,并且运行在UI线程当中 可以对UI空间进行设置
     */
    @Override
    protected void onPostExecute(String result) {
        textView.setText("异步操作执行结束" + result);
    }  

    //该方法运行在UI线程当中,并且运行在UI线程当中 可以对UI空间进行设置
    @Override
    protected void onPreExecute() {
        textView.setText("开始执行异步线程");
    }  

    /**
     * 这里的Intege参数对应AsyncTask中的第二个参数
     * 在doInBackground方法当中,,每次调用publishProgress方法都会触发onProgressUpdate执行
     * onProgressUpdate是在UI线程中执行,所有可以对UI空间进行操作
     */
    @Override
    protected void onProgressUpdate(Integer... values) {
        int vlaue = values[0];
        progressBar.setProgress(vlaue);
    }  

}  

/**
 * 模拟网络耗时
 *
 */
class NetOperator {
    public void operator(){
        try {
            //休眠1秒
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
}  
public class AsyncTaskActivity extends Activity {
	 private Button startTaskBtn;
	    private ProgressBar progressBar;
	    private TextView progress_info;  

	    @Override
	    public void onCreate(Bundle savedInstanceState) {
	        super.onCreate(savedInstanceState);
	        setContentView(R.layout.asyntask_layout);  

	        startTaskBtn = (Button)findViewById(R.id.startTaskBtn);
	        progressBar = (ProgressBar)findViewById(R.id.progressBar);

	        progress_info = (TextView)findViewById(R.id.progress_info);

	        startTaskBtn.setOnClickListener(new OnClickListener() {
	            @Override
	            public void onClick(View v) {
	                ProgressBarAsyncTask asyncTask = new ProgressBarAsyncTask(progress_info, progressBar);
	                asyncTask.execute(1000);
	            }
	        });
	    }
	}  

问题7  Asynctask与使用Handler+thread的优缺点对比(区别)

采用线程+Handler实现异步处理时,当每次执行耗时操作都创建一条新线程进行处理,性能开销会比较大, 如果耗时操作执行的时间比较长,就有可能同时运行着许多线程,系统终将不堪重负. 为了提高性能我们使用AsyncTask实现异步处理,事实上其内部也是采用线程+handler来实现异步处理的.只不过是其内部使用了JDK5提供的线程池技术,有效的降低了线程创建数量及限定了同时运行的线程数,还有一些针对性的对池的优化操作.

AsyncTask规定同一时刻能够运行的线程数为5个,线程池总大小为128。也就是说当我们启动了10个任务时,只有5个任务能够立刻执行,另外的5个任务则需要等待,当有一个任务执行完毕后,第6个任务才会启动,以此类推。而线程池中最大能存放的线程数是128个,当我们尝试去添加第129个任务时,程序就会崩溃。

因此在3.0版本中AsyncTask的改动还是挺大的,在3.0之前的AsyncTask可以同时有5个任务在执行,而3.0之后的AsyncTask同时只能有1个任务在执行。为什么升级之后可以同时执行的任务数反而变少了呢?这是因为更新后的AsyncTask已变得更加灵活,如果不想使用默认的线程池,还可以自由地进行配置

AsyncTask

优点:

1.简单,快捷,只需要在doInBackground ()中处理业务,在onPostExecute()方法中更新UI,代码层次结构简单

2.当相同异步任务的数据特别庞大,AsyncTask这种线程池结构的优势会充分体现出来

缺点:

1.AsyncTask类包含一个全局静态的线程池,默认配置了5个可用线程,如果超过5个线程则会进入到缓冲队列中等待。缓冲队列最多有128个等中的线程

2.AsyncTask可能存在新开大量线程消耗系统资源和导致应用FC(Force Close)的风险

3.AsyncTask在处理大量多种不同异步任务的时候显得不适合。

Handler

优点:

Handler原理是仅仅就是发送了一个消息队列,并没有新开线程带来的资源消耗,对于纷繁复杂的不同小异步任务处理起来相对简单。

缺点:

相对AsyncTask来说显得代码过多,过于臃肿,结构过于复杂,层次不明朗

Demo下载地址:

https://github.com/feifei003603/CustomAsyncTask.git

终于整理完了,好困,觉得有用的小伙伴给个评论吧,有什么不对的地方欢迎拍砖,欢迎补充!

版权声明:本文为博主原创文章,未经博主允许不得转载。

时间: 2024-08-26 06:10:21

Android异步之Asynctask与Handler面试七问的相关文章

android异步请求asyncTask使用—分析getResponseCode()阻塞

在实际应用中经常会遇到比较耗时任务的处理,比如网络连接,数据库操作等情况时,如果这些操作都是放在主线程(UI线程)中,则会造成UI的假死现象,Android中可以使用AsyncTask和Handler两种异步方式来解决这种问题. AsyncTask(异步任务处理) 在使用AsyncTask时处理类需要继承AsyncTask,提供三个泛型参数,并且重载AsyncTask的四个方法(至少重载一个). An asynchronous task is defined by a computation t

android异步类AsyncTask的简单使用

Android为了降低这个开发难度,提供了AsyncTask.AsyncTask就是一个封装过的后台任务类,顾名思义就是异步任务,更通俗地说就是一个执行后台任务的线程 而且他还会自动通知主线程更新UI 优点: 结构清晰,容易理解. 缺点 代码量稍大 下面直接看代码 1 private class AsyncLogin extends AsyncTask<Void,Integer,Boolean>{ 2 private EditText passwordEdit; 3 private EditT

Android异步处理三:Handler+Looper+MessageQueue深入详解

Android Loop&Handle学习总结 - New Start - 博客频道 - CSDN.NET ?????? 昨晚偷懒,这篇博客只写了一个标题,今天早晨一看,还有15的阅读量.实在是对不起那些同学.......换了是我,也会BS这样的LZ吧!sorry 啦 -------------------------------------------------------------------------------------------------------------------

android 异步任务AsyncTask

package com.example.ansyctest; import java.io.ByteArrayOutputStream; import java.io.InputStream; import org.apache.http.HttpResponse; import org.apache.http.client.HttpClient; import org.apache.http.client.methods.HttpGet; import org.apache.http.impl

Android笔记二十六.Android异步任务处理(AsyncTask)

转载请表明出处:http://blog.csdn.net/u012637501(嵌入式_小J的天空) 一.引言 我们知道Android的UI线程主要负责处理用户的按键事件.用户触屏事件及屏幕绘图事件等,对于其他的操作尽量不要在UI线程中实现,因为这些操作很有可能会阻塞UI线程,比如一些耗时操作,会导致UI界面停止响应,从而降低了用户的体验.所以,为了避免UI线程失去响应的问题,Android建议将耗时操作放在新线程中完成,但新线程也可能需要动态更新UI组件:比如需要从网上获取一个网页,然后在Te

Android备注26.Android异步任务(AsyncTask)

转载请表明出处:http://blog.csdn.net/u012637501(嵌入式_小J的天空) 一.引言 我们知道Android的UI线程主要负责处理用户的按键事件.用户触屏事件及屏幕画图事件等,对于其它的操作尽量不要在UI线程中实现,由于这些操作非常有可能会堵塞UI线程,比方一些耗时操作,会导致UI界面停止响应,从而减少了用户的体验.所以,为了避免UI线程失去响应的问题,Android建议将耗时操作放在新线程中完毕.但新线程也可能须要动态更新UI组件:比方须要从网上获取一个网页,然后在T

Android异步任务AsyncTask的使用与原理分析

在上一篇文章<Android缓存机制&一个缓存框架推荐>中说到,在了解了Android缓存机制后我准备自己动手写一个LruCache和DiskLruCache二级缓存的轻量级的图片请求框架,在思考如何搭建这个框架时,纠结于用何种方式去下载图片,是直接new出一个线程呢,还是用看起来稍微高大上档次一点的AsyncTask异步任务来处理?思来想去,还是虚荣心作怪,还是用AsyncTask吧,正好这个工具类我之前用的也比较少,对它的原理也不是很清楚,趁这个机会,好好学一下AsyncTask的

android:异步任务asyncTask介绍及异步任务下载图片(带进度条)

为什么要用异步任务? 在android中只有在主线程才能对ui进行更新操作,而其它线程不能直接对ui进行操作 android本身是一个多线程的操作系统,我们不能把所有的操作都放在主线程中操作 ,比如一些耗时操作.如果放在主线程中 会造成阻塞 而当阻塞事件过长时 系统会抛出anr异常.所以我们要使用异步任务.android为我们提供了一个封装好的组件asynctask. AsyncTask可以在子线程中更新ui,封装简化了异步操作.适用于简单的异步处理.如果多个后台任务时就要使用Handler了

Android异步任务AsyncTask

1 package com.example.asynctask; 2 3 import java.net.MalformedURLException; 4 import java.net.URL; 5 6 import android.app.Activity; 7 import android.os.AsyncTask; 8 import android.os.Bundle; 9 import android.util.Log; 10 import android.view.Menu; 11