Android线程学习笔记

Posted 阿蛮家

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android线程学习笔记相关的知识,希望对你有一定的参考价值。

UI线程要处理所有任务时,那些耗时很长的操作——诸如访问网络或查询数据库等——将会阻塞整个UI(线程)。一旦线程被阻塞,所有事件都不能被分发,包括屏幕绘图事件。从用户的角度看来,应用程序看上去像是挂起了。更糟糕的是,如果UI线程被阻塞超过一定时间(目前大约是5秒钟),用户就会被提示那个可恶的“应用程序没有响应”(ANR)对话框。

此外,Andoid的UI组件包并不是线程安全的。因此不允许从工作线程中操作UI——只能从UI线程中操作用户界面。于是,Andoid的单线程模式必须遵守两个规则:

  1. 不要阻塞UI线程。
  2. 不要在UI线程之外访问Andoid的UI组件包。

工作线程

根据以上对单线程模式的描述,要想保证程序界面的响应能力,关键是不能阻塞UI线程。如果操作不能很快完成,应该让它们在单独的线程中运行(“后台”或“工作”线程)。

例如:以下响应鼠标点击的代码实现了在单独线程中下载图片并在ImageView显示:

public void onClick(View v)  
    new Thread(new Runnable()  
        public void run()  
            Bitmap b = loadImageFromNetwork("http://example.com/image.png"); 
            mImageView.setImageBitmap(b); 
         
    ).start(); 

乍看起来,这段代码似乎能运行得很好,因为创建了一个新的线程来处理访问网络的操作。可是它违反了单线程模式的第二条规则:不要在UI线程之外访问Andoid的UI组件包——以上例子在工作线程里而不是UI线程里修改了ImageView。这可能导致不明确、不可预见的后果,要跟踪这种情况也是很困难很耗时间的。

为了解决以上问题,android提供了几种途径来从其它线程中访问UI线程。下面列出了有助于解决问题的几种方法:

比如说,可以使用View.post(Runnable)方法来修正上面的代码:

public void onClick(View v)  
    new Thread(new Runnable()  
        public void run()  
            final Bitmap bitmap = loadImageFromNetwork("http://example.com/image.png"); 
            mImageView.post(new Runnable()  
                public void run()  
                    mImageView.setImageBitmap(bitmap); 
                 
            ); 
         
    ).start(); 

以上代码的执行现在是线程安全的了:网络相关的操作在单独的线程里完成,而ImageView是在UI线程里操纵的。

不过,随着操作变得越来越复杂,这类代码也会变得很复杂很难维护。为了用工作线程完成更加复杂的交互处理,可以考虑在工作线程中用Handler来处理UI线程分发过来的消息。当然,最好的解决方案也许就是继承使用异步任务类AsyncTask,此类简化了一些工作线程和UI交互的操作。

使用异步任务

异步任务AsyncTask 允许以异步的方式对用户界面进行操作。它先阻塞工作线程,再在UI线程中呈现结果,在此过程中不需要对线程和handler进行人工干预。

要使用异步任务,必须继承AsyncTask类并实现doInBackground()回调方法,该对象将运行于一个后台线程池中。要更新UI时,须实现onPostExecute()方法来分发doInBackground()返回的结果,由于此方法运行在UI线程中,所以就能安全地更新UI了。然后就可以在UI线程中调用execute()来执行任务了。

例如,可以利用AsyncTask来实现上面的那个例子:

public void onClick(View v)  
    new DownloadImageTask().execute("http://example.com/image.png"); 
 
 
private class DownloadImageTask extends AsyncTask<String, Void, Bitmap>  
    /** The system calls this to perform work in a worker thread and 
      * delivers it the parameters given to AsyncTask.execute() */ 
    protected Bitmap doInBackground(String... urls)  
        return loadImageFromNetwork(urls[0]); 
     
     
    /** The system calls this to perform work in the UI thread and delivers 
      * the result from doInBackground() */ 
    protected void onPostExecute(Bitmap result)  
        mImageView.setImageBitmap(result); 
     

现在UI是安全的,代码也得到简化,因为任务分解成了工作线程内完成的部分和UI线程内完成的部分。

要全面理解这个类的使用,须阅读AsyncTask的参考文档。

一、Handler+Thread

public class HandlerActivity extends Activity 

    private TextView txtCount;

    private int mCount;

    private Handler mHandler = new Handler()
        @Override
        public void handleMessage(Message msg) 
            super.handleMessage(msg);
            switch (msg.what)
                case 111:
                    int count = (int) msg.obj;
                    txtCount.setText("网络请求的数据:"+count);
                    break;
                default:
                    break;
            
        
    ;

    private class MyThread extends Thread

        @Override
        public void run() 

            while (true)
                //模拟网络请求
                try 
                    sleep(1000);
                 catch (InterruptedException e) 
                    e.printStackTrace();
                

                mCount ++;

                Message msg = mHandler.obtainMessage();
                msg.what = 111;
                msg.obj = mCount;
                mHandler.sendMessage(msg);
            

        
    

    @Override
    protected void onCreate(Bundle savedInstanceState) 
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_handler);
        txtCount = (TextView) findViewById(R.id.txtCount);

        new MyThread().start();
    



二、AsyncTask 从网络下载一张图片显示到UI界面,AsyncTask的几处回调都给了我们机会去中断任务,在任务状态的管理上较之Thread()方式更为灵活。值得注意的是AsyncTask的cancel()方法并不会终止任务的执行,开发者需要自己去检查cancel的状态值来决定是否中止任务。AsyncTask也有隐式的持有外部类对象引用的问题,需要特别注意防止出现意外的内存泄漏。AsyncTask由于在不同的系统版本上串行与并行的执行行为不一致,被不少开发者所诟病,这确实是硬伤,绝大部分的多线程场景都需要明确任务是串行还是并行。

线程优先级为background,对UI线程的执行影响极小。

public class GetInternetImgAsyncTask extends AsyncTask<String,Integer,Bitmap> 
    private ProgressBar mProgressBar;
    private TextView mTextView;
    private ImageView mImageView;

    public GetInternetImgAsyncTask(ProgressBar progressBar, TextView textView, ImageView imageView) 
        this.mProgressBar = progressBar;
        this.mTextView = textView;
        this.mImageView = imageView;
    

    @Override
    protected void onPreExecute() 
        super.onPreExecute();
        mTextView.setText("下载图片");
    

    @Override
    protected Bitmap doInBackground(String... params) 

            URL myFileUrl = null;
            Bitmap bitmap = null;
            InputStream is = null;
            HttpURLConnection conn = null;
            try 
                myFileUrl = new URL(params[0]);
             catch (MalformedURLException e) 
                e.printStackTrace();
            
            try 
                conn = (HttpURLConnection)myFileUrl
                        .openConnection();
                conn.setDoInput(true);
                conn.connect();
                is =conn.getInputStream();
                bitmap = BitmapFactory.decodeStream(is);

                byte[] data = new byte[1024];
                int seg = 0;
                long total = conn.getContentLength();
                long current = 0;
                while (!isCancelled()&&(seg = is.read(data)) != -1)
                    current += seg;
                    int progress = (int) ((long)current/total*100);

                    publishProgress(progress);
                

                is.close();
             catch (IOException e) 
                e.printStackTrace();
            finally
                try 
                    if(is != null)
                        is.close();
                    
                    if( conn != null)
                        conn.disconnect();
                    
                 catch (IOException e) 
                    e.printStackTrace();
                
            
            return bitmap;

    

    @Override
    protected void onProgressUpdate(Integer... values) 
        mProgressBar.setProgress(values[0]);
        mTextView.setText(values[0] + "%");
        super.onProgressUpdate(values);

    

    @Override
    protected void onPostExecute(Bitmap bitmap) 
        if (bitmap != null) 
            mImageView.setImageBitmap(bitmap);
        else 
            mTextView.setText("下载失败");
        
        mProgressBar.setVisibility(View.GONE);
        mTextView.setText("下载完成");
        super.onPostExecute(bitmap);

    


    @Override
    protected void onCancelled() 
        super.onCancelled();
        mTextView.setText("任务取消");
    


执行AsyncTask

public class AsyncTaskActivity extends Activity
    private Button btn_get_img;
    private ProgressBar progressBar;
    private TextView textView;
    private ImageView img;
    private GetInternetImgAsyncTask getInternetImgAsyncTask;

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

        progressBar = (ProgressBar)findViewById(R.id.progressBar02);
        textView = (TextView)findViewById(R.id.textView01);
        img = (ImageView) findViewById(R.id.img_show);
        btn_get_img = (Button) findViewById(R.id.btn_get_img);

        getInternetImgAsyncTask = new GetInternetImgAsyncTask(progressBar,textView,img);

        btn_get_img.setOnClickListener(new View.OnClickListener() 
            @Override
            public void onClick(View v) 
                //开启异步线程下载图片
                Log.e("AsyncTaskActivity", "任务是否被取消 :"+getInternetImgAsyncTask.isCancelled());
                if(!getInternetImgAsyncTask.isCancelled())
                    //为了防止task二次执行导致程序崩溃,需要new一个task
                    getInternetImgAsyncTask = new GetInternetImgAsyncTask(progressBar, textView, img);
                    getInternetImgAsyncTask.execute("http://c.hiphotos.baidu.com/image/pic/item/d439b6003af33a876bcce3f7c35c10385243b5be.jpg");
                
            
        );

    


    @Override
    protected void onDestroy() 
        super.onDestroy();
        //取消异步任务
        if(getInternetImgAsyncTask != null && getInternetImgAsyncTask.getStatus() != AsyncTask.Status.FINISHED)
            getInternetImgAsyncTask.cancel(true);
        

    

三、HandlerThread 定时更新UI界面,在需要对多任务做更精细控制,线程切换更频繁的场景之下,Thread()和AsyncTask都会显得力不从心。HandlerThread却能胜任这些需求甚至更多。HandlerThread将Handler,Thread,Looper,MessageQueue几个概念相结合。Handler是线程对外的接口,所有新的message或者runnable都通过handler post到工作线程。Looper在MessageQueue取到新的任务就切换到工作线程去执行。不同的post方法可以让我们对任务做精细的控制,什么时候执行,执行的顺序都可以控制。HandlerThread最大的优势在于引入MessageQueue概念,可以进行多任务队列管理。HandlerThread背后只有一个线程,所以任务是串行执行的。串行相对于并行来说更安全,各任务之间不会存在多线程安全问题。HandlerThread所产生的线程会一直存活,Looper会在该线程中持续的检查MessageQueue。这一点和Thread(),AsyncTask都不同,thread实例的重用可以避免线程相关的对象的频繁重建和销毁。HandlerThread较之Thread(),AsyncTask需要写更多的代码,但在实用性,灵活度,安全性上都有更好的表现。


public class HandlerThreadActivity extends Activity 

    private TextView txtCount;

    private HandlerThread mHandlerThread;
    private Handler mHandler;

    //处理UI显示的handler
    private Handler mUiHandler = new Handler();

    private boolean isUpdateInfo = false;

    private static final int MSG_UPADATE_INFO = 0x110;

    @Override
    protected void onCreate(Bundle savedInstanceState) 
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_handlerthread);
        txtCount = (TextView) findViewById(R.id.txtCount);

        initHandlerThread();
    

    private void initHandlerThread() 
        mHandlerThread = new HandlerThread("updatethreadhandler");
        mHandlerThread.start();

        mHandler = new Handler(mHandlerThread.getLooper())
            @Override
            public void handleMessage(Message msg) 

                checkForUpdate();

                if(isUpdateInfo)
                    mHandler.sendEmptyMessageDelayed(MSG_UPADATE_INFO, 1000);
                
            
        ;
    

    private void checkForUpdate() 
        try 
            Thread.sleep(1000);
            mUiHandler.post(new Runnable() 
                @Override
                public void run() 
                    String result = "实时更新中,当前大盘指数:<font color='red'>%d</font>";
                    result = String.format(result, (int) (Math.random() * 3000 + 1000));
                    txtCount.setText(html.fromHtml(result));
                
            );
         catch (InterruptedException e) 
            e.printStackTrace();
        
    


    @Override
    protected void onResume() 
        super.onResume();
        isUpdateInfo = true;
        mHandler.sendEmptyMessage(MSG_UPADATE_INFO);
    

    @Override
    protected void onPause() 
        super.onPause();
        isUpdateInfo = false;
        mHandler.removeMessages(MSG_UPADATE_INFO);

    


    @Override
    protected void onDestroy() 
        super.onDestroy();
        mHandlerThread.quit();
    


理解handler.post(runnable)可以参考  深入理解Looper、Handler、Message三者之间的关系  


四、IntentService,和AsyncTask不同,没有和UI线程的交互,也不像HandlerThread的工作线程会一直存活。IntentService背后其实也有一个HandlerThread来串行的处理Message Queue。只不过在所有的Message处理完毕之后,工作线程会自动结束。所以可以把IntentService看做是Service和HandlerThread的结合体,适合需要在工作线程处理UI无关任务的场景。

public class IntentSer extends IntentService 

    public IntentSer()
        super("IntentSer");
    


    private static final String TAG = "IntentSer";
    private String url_path="http://ww2.sinaimg.cn/bmiddle/9dc6852bjw1e8gk397jt9j20c8085dg6.jpg";

    @Override
    public void onCreate() 
        Log.i(TAG, "Service is Created");
        super.onCreate();
    

    @Override
    public void onStart(Intent intent, int startId) 
        super.onStart(intent, startId);
    

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) 
        return super.onStartCommand(intent, flags, startId);
    

    @Override
    public void onDestroy() 
        Log.i(TAG, "Service is Destroyed");
        super.onDestroy();
    

    @Override
    public IBinder onBind(Intent intent) 
        return super.onBind(intent);
    


    @Override
    protected void onHandleIntent(Intent intent) 
        Log.i(TAG, "HandleIntent is execute");

        try 
            //在设备应用目录创建一个文件
            File file = new File(this.getFilesDir(),"weibo.jpg");
            Log.i(TAG,"图片存储路径:" + file.getAbsolutePath());
            FileOutputStream fos = new FileOutputStream(file);

            //获取网络图片的输入流
            InputStream inputStream = new URL(url_path).openStream();
            //把网络图片输入流写入到文件输出流
            byte[] date = new byte[1024];
            int len = -1;
            while ((len = inputStream.read(date))!=-1)
                fos.write(date,0,len);
            
            fos.close();
            inputStream.close();
            Log.i(TAG, "The file download is complete");

         catch (IOException e)
            e.printStackTrace();
        

    

启动IntentService

Intent service = new Intent(getApplicationContext(),IntentSer.class);
startService(service);


注意,Service需要在manifest.xml文件中注册。






以上是关于Android线程学习笔记的主要内容,如果未能解决你的问题,请参考以下文章

Android线程学习笔记

Android学习笔记十:异步处理

Android-Universal-Image-Loader 学习笔记线程池分析

ANDROID_MARS学习笔记_S01原始版_007_Handler及线程的简单使用

Android学习笔记(十四) Handler理论补充

JMeter常用测试元件—学习笔记