探討android更新UI的幾種方法

作為IT新手,總以為只要有時間,有精力,什么東西都能做出來。這種念頭我也有過,但很快就熄滅了,因為現(xiàn)實是殘酷的,就算一開始的時間和精力非常充足,也會隨著項目的推進(jìn)而逐步消磨殆盡。我們會發(fā)現(xiàn),自己越來越消極怠工,只是在無意義的敲代碼,敲的還是網(wǎng)上抄來的代碼,如果不行,繼續(xù)找。
這就是項目進(jìn)度沒有規(guī)劃好而導(dǎo)致的。
最近在做有關(guān)藍(lán)牙的項目,一開始的進(jìn)度都安排得很順利,但是因為測試需要兩部手機(jī),而且還要是android手機(jī),暑假已經(jīng)開始了,同學(xué)們都回家了,加上我手機(jī)的藍(lán)牙壞了,導(dǎo)致我的進(jìn)度嚴(yán)重被打亂!而且更加可怕的是,就算我手機(jī)這邊調(diào)試完畢,我最終的目標(biāo)是實現(xiàn)手機(jī)與藍(lán)牙模塊的通信,那個測試板至今未送過來,所以,我開始消極怠工了。
經(jīng)驗教訓(xùn)非常簡單:根據(jù)整個項目的時間長度規(guī)劃好每天的進(jìn)度,視實際情況的變化而改變規(guī)劃,就算真的是無法開展工作,像是現(xiàn)在這樣抽空出來寫寫博客都要好過無意義的敲代碼。
今天講的內(nèi)容非常簡單,只是講講有關(guān)于android界面更新的方面。
1.利用Looper更新UI界面
如果我們的代碼需要隨時將處理后的數(shù)據(jù)交給UI更新,那么我們想到的方法就是另開一個線程更新數(shù)據(jù)(也必須這么做,如果我們的數(shù)據(jù)更新運算量較大,就會阻塞UI線程),也就是界面更新和數(shù)據(jù)更新是在不同線程中(android采用的是UI單線程模型,所以我們也只能在主線程中對UI進(jìn)行操作),但這會導(dǎo)致另一個問題:如何在兩個線程間通信呢?android提供了Handler機(jī)制來保證這種通信。
先是一個簡單的例子:

public class MainActivity extends Activity {
    private Button mButton;
    private TextView mText;
    
    @SuppressLint("HandlerLeak")
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        
        mButton = (Button)this.findViewById(R.id.button);
        mText = (TextView)this.findViewById(R.id.text);
        
        final Handler handler = new Handler(){
            @Override
            public void handleMessage(Message msg){
                super.handleMessage(msg);
                if(msg.what == 1){
                    mText.setText("更新后");
                }
            }
        };
        
        mText.setText("更新前");
        final Thread thread = new Thread(new Runnable(){

            @Override
            public void run() {
                 Message message = new Message();
                 message.what = 1;
                 handler.sendMessage(message);
            }
            
        });
        mButton.setOnClickListener(new OnClickListener() {
            
            @Override
            public void onClick(View v) {
                 thread.start();
            }
        });
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        // Inflate the menu; this adds items to the action bar if it is present.
        getMenuInflater().inflate(R.menu.main, menu);
        return true;
    }

}
  在Main主線程中新開一個線程,該線程負(fù)責(zé)數(shù)據(jù)的更新,然后將更新后的數(shù)據(jù)放在Message里面,然后通過Handler傳遞給相應(yīng)的UI進(jìn)行更新。
  ![](http://upload-images.jianshu.io/upload_images/2837762-9c705a7608f2d0db.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

使用TextView或者其他組件的時候,如果出現(xiàn)這樣的錯誤:
android.content.res.Resources$NotFoundException:String resource ID #0x86
這樣的錯誤誤導(dǎo)性真大!我以為是我的資源ID用錯了,但就是這個ID,一下子就沒法子了,查了很久,結(jié)果發(fā)現(xiàn)是TextView.setText()要求的是字符串,但我傳入了一個int!就這個問題,原本是傳參錯誤,但android竟然沒有報錯,而且這個錯誤提示也太那個了吧?。?br> Message的任務(wù)很簡單,就是用來傳遞數(shù)據(jù)更新信息,但有幾點也是值得注意的:我們可以使用構(gòu)造方法來創(chuàng)建Message,但出于節(jié)省內(nèi)存資源的考量,我們應(yīng)該使用Message.obtain()從消息池中獲得空消息對象,而且如果Message只是攜帶簡單的int信息,優(yōu)先使用Message.arg1和Message.arg2來傳遞信息,這樣比起使用Bundle更省內(nèi)存,而Message.what用于標(biāo)識信息的類型。
我們現(xiàn)在來了解Handler的工作機(jī)制。
Handler的作用就是兩個:在新啟動的線程中發(fā)送消息和在主線程中獲取和處理消息。像是上面例子中的Handler就包含了這兩個方面:我們在新啟動的線程thread中調(diào)用Handler的sendMessage()方法來發(fā)送消息。發(fā)送給誰呢?從代碼中可以看到,就發(fā)送給主線程創(chuàng)建的Handler中的handleMessage()方法處理。這就是回調(diào)的方式:我們只要在創(chuàng)建Handler的時候覆寫handleMessage()方法,然后在新啟動的線程發(fā)送消息時自動調(diào)用該方法。
要想真正明白Handler的工作機(jī)制,我們就要知道Looper,Message和MessageQueue。
Looper正如字面上的意思,就是一個"循環(huán)者",它的主要作用就是使我們的一個普通線程變成一個循環(huán)線程。如果我們想要得到一個循環(huán)線程,我們必須要這樣:

class LooperThread extends Thread{
     public Handler mHandler;
     
     public void run(){
         Looper.prepare();
         mHandler = new Handler(){
              public void handleMessage(Message msg){
                   //process incoming message here
             }
        };
        Looper.loop();
     }
}
  Looper.prepare()就是用來使當(dāng)前的線程變成一個LooperThread,然后我們在這個線程中用Handler來處理消息隊列中的消息,接著利用Looper.loop()來遍歷消息隊列中的所有消息。
  話是這么說,但是最后處理的是消息隊列中的最后一個消息:
mHandler = new Handler(){
            @Override
            public void handleMessage(Message msg) {
                super.handleMessage(msg);
                mTextView.setText(msg.what + "");
            }
        };
        
        mButton.setOnClickListener(new OnClickListener() {

            @Override
            public void onClick(View v) {
                 LooperThread thread = new LooperThread();
                 thread.setHandler(mHandler);
                 thread.start();
            }
        });
    }

    class LooperThread extends Thread {
        Handler handler;
        
        public void setHandler(Handler handler){
            this.handler = handler;
        }

        @Override
        public void run() {
            Looper.prepare();
            for (int i = 0; i < 10; i++) {
                Message message = Message.obtain();
                message.arg1 = i;
                handler.sendMessage(message);
            }
            Looper.loop();
        }
    }
  結(jié)果顯示的是9??!難道說MessageQueue是"先進(jìn)后出"的隊列?
  這只是因為處理得太快,如果我們這樣子:
try{
  Thread.sleep(1000);
  handler.sendMessage(message);
}catch(InterruptedException e){}
   我們就可以看到TextView從0一直數(shù)到9。
   由此可知道,sendMessage()方法的實現(xiàn)是回調(diào)了handleMessage(),所以說是處理消息隊列中的所有消息也是正確的,因為消息一發(fā)送到消息隊列中就立即被處理。
   Looper線程應(yīng)該怎么使用,得到一個Looper引用我們能干嘛?
  讓我們繼續(xù)思考這個問題。
  每個線程最多只有一個Looper對象,它的本質(zhì)是一個ThreadLocal,而ThreadLocal是在JDK1.2中引入的,它為解決多線程程序的并發(fā)問題提供了一種新思路。
  ThreadLocal并不是一個Thread,它是Thread的局部變量,正確的命名應(yīng)該是ThreadLocalVariable才對。如果是經(jīng)??碼ndroid源碼的同學(xué),有時候也會發(fā)現(xiàn)它的一些變量的命名也很隨便。
  ThreadLocal為每個使用該變量的線程提供獨立的變量副本,所以每一個線程都可以獨立的改變自己的副本而不會影響到其他線程的副本。這種解決方案就是為每一個線程提供獨立的副本,而不是同步該變量。
  但是該變量并不是在線程中聲明的,它是該線程使用的變量,因為對于線程來說,它所使用的變量就是它的本地變量,所以Local就是取該意。
  學(xué)過java的同學(xué)都知道,編寫線程局部變量比起同步該變量來說,實在是太笨拙了,所以我們更多使用同步的方式,而且java對該方式也提供了非常便利的支持。
  現(xiàn)在最大的問題就是:ThreadLocal是如何維護(hù)該變量的副本呢?
  實現(xiàn)的方式非常簡單:在ThreadLocal中有一個Map,用于存儲每一個線程的變量副本,Map中元素的鍵為線程對象,而值對應(yīng)的是該線程的變量副本。
  同樣是為了解決多線程中相同變量的訪問沖突問題,ThreadLocal和同步機(jī)制相比,有什么優(yōu)勢呢?
  使用同步機(jī)制,我們必須通過對象的鎖機(jī)制保證同一時間只有一個線程訪問變量。所以,我們必須分析什么時候?qū)υ撟兞窟M(jìn)行讀寫,什么時候需要鎖定某個對象,又是什么時候該釋放對象鎖等問題,更糟糕的是,我們根本就無法保證這樣做事萬無一失的。
  ThreadLocal是通過為每一個線程提供一個獨立的變量副本,從而隔離了多個線程對數(shù)據(jù)的訪問沖突,所以我們也就沒有必要使用對象鎖這種難用的東西,這種方式更加安全。
  ThreadLocal最大的問題就是它需要為每個線程維護(hù)一個副本,也就是"以空間換時間"的方式。我們知道,內(nèi)存空間是非常寶貴的資源,這也是我們大部分時候都不會考慮該方式的原因。
 為什么Looper是一個ThreadLocal呢?Looper本身最大的意義就是它內(nèi)部有一個消息隊列,而其他線程是可以向該消息隊列中添加消息的,所以Looper本身就是一個ThreadLocal,每個線程都維護(hù)一個副本,添加到消息隊列中的消息都會被處理掉。
mHandler = new Handler(){
            @Override
            public void handleMessage(Message msg) {
                super.handleMessage(msg);
                if(msg.what == 1){
                mTextView.setText(msg.what + "");
                }else{
                    Toast.makeText(MainActivity.this, msg.what + "", Toast.LENGTH_LONG).show();
                }
            }
        };
        
        mButton.setOnClickListener(new OnClickListener() {

            @Override
            public void onClick(View v) {
                 Thread1 thread1 = new Thread1();
                 thread1.setHandler(mHandler);
                 thread1.start();
                 Thread2 thread2 = new Thread2();
                 thread2.setHandler(mHandler);
                 thread2.start();
            }
        });
    }
    
    class  Thread2 extends Thread {
        Handler handler;
        
        public void setHandler(Handler handler){
            this.handler = handler;
        }

        @Override
        public void run() {
                Message message = Message.obtain();
                message.what = 2;
                handler.sendMessage(message);
                   
        }
    }

    class  Thread1 extends Thread {
        Handler handler;
        
        public void setHandler(Handler handler){
            this.handler = handler;
        }

        @Override
        public void run() {
                Message message = Message.obtain(); 
                message.what = 1;
                handler.sendMessage(message);
                   
        }
    }
  上面這段代碼是新建兩個線程,每個線程都維護(hù)一個Handler,然后都向這個Handler發(fā)送消息,結(jié)果就是這兩個消息同時被處理。      Hanlder本身就持有一個MessageQueue和Looper的引用,默認(rèn)情況下是創(chuàng)建該Handler的線程的Looper和該Looper的MessageQueue。
  Hanler只能處理由自己發(fā)出的消息,它會通知MessageQueue,表明它要執(zhí)行一個任務(wù),然后在輪到自己的時候執(zhí)行該任務(wù),這個過程是異步的,因為它不是采用同步Looper的方式而是采用維護(hù)副本的方式解決多線程共享的問題。
  一個線程可以有多個Handler,但是只能有一個Looper,理由同上:維護(hù)同一個Looper的副本。
  到了這里,我們可以發(fā)現(xiàn):新開一個線程用于處理數(shù)據(jù)的更新,在主線程中更新UI,這種方式是非常自然的,而且這也是所謂的觀察者模式的使用(使用回調(diào)的方式來更新UI,幾乎可以認(rèn)為是使用了觀察者模式)。
  我們繼續(xù)就著Looper探討下去。
  因為Handler需要當(dāng)前線程的MessageQueue,所以我們必須通過Looper.prepare()來為Handler啟動MessageQueue,而主線程默認(rèn)是有MessageQueue,所以我們不需要在主線程中調(diào)用prepare()方法。在Looper.loop()后面的代碼是不會被執(zhí)行的,除非我們顯式的調(diào)用Handler.getLooper().quit()方法來離開MessageQueue。
  到了這里,我們之前的問題:LooperThread應(yīng)該如何使用?已經(jīng)有了很好的答案了: LooperThread用于UI的更新,而其他線程向其Handler發(fā)送消息以更新數(shù)據(jù)。因為主線程原本就是一個LooperThread,所以我們平時的習(xí)慣都是在主線程里創(chuàng)建Handler,然后再在其他線程里更新數(shù)據(jù),這種做法也是非常保險的,因為UI組件只能在主線程里面更新。
  當(dāng)然,Handler并不僅僅是用于處理UI的更新,它本身的真正意義就是實現(xiàn)線程間的通信:
new LooperThread().start();
  mButton.setOnClickListener(new OnClickListener() {

            @Override
            public void onClick(View v) {
                final int MESSAGE_HELLO = 0;
                String message = "hello";
                mHandler.obtainMessage(MESSAGE_HELLO, message).sendToTarget();
            }
        });
    }

    class LooperThread extends Thread {

        @Override
        public void run() {
            Looper.prepare();
            mHandler = new Handler() {
                @Override
                public void handleMessage(Message msg) {
                    switch (msg.what) {
                    case MESSAGE_HELLO:
                        Toast.makeText(MainActivity.this, (String) msg.obj,
                                Toast.LENGTH_SHORT).show();
                        break;
                    default:
                        break;
                    }

                }
            };
            Looper.loop();
        }
    }
  上面是Handler非常經(jīng)典的用法:我們通過Handler的obtainMessage()方法來創(chuàng)建一個新的Message(int what, Object obj),然后通過sendToTarget()發(fā)送到創(chuàng)建該Handler的線程中。如果大家做過類似藍(lán)牙編程這樣需要通過socket通信的項目,就會清楚的知道,判斷socket的狀態(tài)是多么重要,而Message的what就是用來存儲這些狀態(tài)值(通常這些狀態(tài)值是final int),值得注意的是,obj是Object,所以我們需要強(qiáng)制轉(zhuǎn)型。但這樣的編碼會讓我們的代碼擁有一大堆常量值,而且switch的使用是不可避免的,如果狀態(tài)值很多,那這個switch就真的是太臃腫了,就連android的藍(lán)牙官方實例也無法避免這點。
  總結(jié)一下:Android使用消息機(jī)制實現(xiàn)線程間的通信,線程通過Looper建立自己的消息循環(huán),MessageQueue是FIFO的消息隊列,Looper負(fù)責(zé)從MessageQueue中取出消息,并且分發(fā)到引用該Looper的Handler對象,該Handler對象持有線程的局部變量Looper,并且封裝了發(fā)送消息和處理消息的接口。
  如果Handler僅僅是用來處理UI的更新,還可以有另一種使用方式:
mHandler = new Handler();
        mRunnable = new Runnable() {

            @Override
            public void run() {
                mTextView.setText("haha");
            }
        };
        mButton.setOnClickListener(new OnClickListener() {

            @Override
            public void onClick(View v) {
                new Thread() {
                    public void run() {
                        mHandler.post(mRunnable);
                    }
                }.start();
            }
        });
    }
  使用Handler的post()方法就顯得UI的更新處理非常簡單:在一個Runnable對象中更新UI,然后在另一個線程中通過Handler的post()執(zhí)行該更新動作。值得注意的是,我們就算不用新開一個新線程照樣可以更新UI,因為UI的更新線程就是Handler的創(chuàng)建線程---主線程。
  表面上Handler似乎可以發(fā)送兩種消息:Runnable對象和Message對象,實際上Runnable對象會被封裝成Message對象。

2.AsyncTask利用線程任務(wù)異步更新UI界面
AsyncTask的原理和Handler很接近,都是通過往主線程發(fā)送消息來更新主線程的UI,這種方式是異步的,所以就叫AsyncTask。使用AsyncTask的場合像是下載文件這種會嚴(yán)重阻塞主線程的任務(wù)就必須放在異步線程里面:

public class MainActivity extends Activity {
    private Button mButton;
    private ImageView mImageView;
    private ProgressBar mProgressBar;

    @SuppressLint("HandlerLeak")
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        mButton = (Button) this.findViewById(R.id.button);
        mImageView = (ImageView) this.findViewById(R.id.image);
        mProgressBar = (ProgressBar) this.findViewById(R.id.progressBar);
        mButton.setOnClickListener(new OnClickListener() {

            @Override
            public void onClick(View v) {
                AsyncTaskThread thread = new AsyncTaskThread();
                thread.execute("http://g.search2.alicdn.com/img/bao/uploaded/i4/"
                        + "i4/12701024275153897/T1dahpFapbXXXXXXXX_!!0-item_pic.jpg_210x210.jpg");
            }
        });
    }

    class AsyncTaskThread extends AsyncTask<String, Integer, Bitmap> {

        @Override
        protected Bitmap doInBackground(String... params) {
            publishProgress(0);
            HttpClient client = new DefaultHttpClient();
            publishProgress(30);
            HttpGet get = new HttpGet(params[0]);
            final Bitmap bitmap;
            try {
                HttpResponse response = client.execute(get);
                bitmap = BitmapFactory.decodeStream(response.getEntity()
                        .getContent());
            } catch (Exception e) {
                return null;
            }
            publishProgress(100);
            return bitmap;
        }

        protected void onProgressUpdate(Integer... progress) {
            mProgressBar.setProgress(progress[0]);
        }

        protected void onPostExecute(Bitmap result) {
            if (result != null) {
                Toast.makeText(MainActivity.this, "成功獲取圖片", Toast.LENGTH_LONG)
                        .show();
                mImageView.setImageBitmap(result);
            } else {
                Toast.makeText(MainActivity.this, "獲取圖片失敗", Toast.LENGTH_LONG)
                        .show();
            }
        }

        protected void onPreExecute() {
            mImageView.setImageBitmap(null);
            mProgressBar.setProgress(0);
        }

        protected void onCancelled() {
            mProgressBar.setProgress(0);
        }
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        // Inflate the menu; this adds items to the action bar if it is present.
        getMenuInflater().inflate(R.menu.main, menu);
        return true;
    }
}
 實際的效果如圖:
Paste_Image.png

當(dāng)我們點擊下載按鈕的時候,就會啟動下載圖片的線程,主線程這里顯示下載進(jìn)度條,然后在下載成功的時候就會顯示圖片,這時我們再點擊按鈕的時候就會清空圖片,進(jìn)度條也重新清零。
仔細(xì)看上面的代碼,我們會發(fā)現(xiàn)很多有趣的東西。
AsyncTask是為了方便編寫后臺線程與UI線程交互的輔助類,它的內(nèi)部實現(xiàn)是一個線程池,每個后臺任務(wù)會提交到線程池中的線程執(zhí)行,然后通過向UI線程的Handler傳遞消息的方式調(diào)用相應(yīng)的回調(diào)方法實現(xiàn)UI界面的更新。
AsyncTask的構(gòu)造方法有三個模板參數(shù):Params(傳遞給后臺任務(wù)的參數(shù)類型),Progress(后臺計算執(zhí)行過程中,進(jìn)度單位(progress units)的類型,也就是后臺程序已經(jīng)執(zhí)行了百分之幾)和Result(后臺執(zhí)行返回的結(jié)果的類型)。

protected Bitmap doInBackground(String... params) {
            publishProgress(0);
            HttpClient client = new DefaultHttpClient();
            publishProgress(30);
            HttpGet get = new HttpGet(params[0]);
            final Bitmap bitmap;
            try {
                HttpResponse response = client.execute(get);
                bitmap = BitmapFactory.decodeStream(response.getEntity()
                        .getContent());
            } catch (Exception e) {
                return null;
            }
            publishProgress(100);
            return bitmap;
        }
   params是一個可變參數(shù)列表,publishProgress()中的參數(shù)就是Progress,同樣是一個可變參數(shù)列表,它用于向UI線程提交后臺的進(jìn)度,這里我們一開始設(shè)置為0,然后在30%的時候開始獲取圖片,一旦獲取成功,就設(shè)置為100%。中間的代碼用于下載和獲取網(wǎng)上的圖片資源。
protected void onProgressUpdate(Integer... progress) {
    mProgressBar.setProgress(progress[0]);
}
  onProgressUpdate()方法用于更新進(jìn)度條的進(jìn)度。
protected void onPostExecute(Bitmap result) {
   if (result != null) {
       Toast.makeText(MainActivity.this, "成功獲取圖片", Toast.LENGTH_LONG).show();
       mImageView.setImageBitmap(result);
   } else {
       Toast.makeText(MainActivity.this, "獲取圖片失敗", Toast.LENGTH_LONG).show();
   }
}
 onPostExecute()方法用于處理Result的顯示,也就是UI的更新。
protected void onPreExecute() {
    mImageView.setImageBitmap(null);
    mProgressBar.setProgress(0);
}

protected void onCancelled() {
    mProgressBar.setProgress(0);
}
  這兩個方法主要用于在執(zhí)行前和執(zhí)行后清空圖片和進(jìn)度。      最后我們只需要調(diào)用AsyncTask的execute()方法并將Params參數(shù)傳遞進(jìn)來進(jìn)行。完整的流程是這樣的:
  UI線程執(zhí)行onPreExecute()方法把ImageView的圖片和ProgressBar的進(jìn)度清空,然后后臺線程執(zhí)行doInBackground()方法,千萬不要在這個方法里面更新UI,因為此時是在另一條線程上,在使用publishProgress()方法的時候會調(diào)用onProgressUpdate()方法更新進(jìn)度條,最后返回result---Bitmap,當(dāng)后臺任務(wù)執(zhí)行完成后,會調(diào)用onPostExecute()方法來更新ImageView。
  AsyncTask本質(zhì)上是一個靜態(tài)的線程池,由它派生出來的子類可以實現(xiàn)不同的異步任務(wù),但這些任務(wù)都是提交到該靜態(tài)線程池中執(zhí)行,執(zhí)行的時候通過調(diào)用doInBackground()方法執(zhí)行異步任務(wù),期間會通過Handler將相關(guān)的信息發(fā)送到UI線程中,但神奇的是,并不是調(diào)用UI線程中的回調(diào)方法,而是AsyncTask本身就有一個Handler的子類InternalHandler會響應(yīng)這些消息并調(diào)用AsyncTask中相應(yīng)的回調(diào)方法。從上面的代碼中我們也可以看到,UI的ProgressBar的更新是在AsyncTask的onProgressUpdate(),而ImageView是在onPostExecute()方法里。這是因為InternalHandler其實是在UI線程里面創(chuàng)建的,所以它能夠調(diào)用相應(yīng)的回調(diào)方法來更新UI。
  AsyncTask就是專門用來處理后臺任務(wù)的,而且它針對后臺任務(wù)的五種狀態(tài)提供了五個相應(yīng)的回調(diào)接口,使得我們處理后臺任務(wù)變得非常方便。
  如果只是普通的UI更新操作,像是不斷更新TextView這種動態(tài)的操作,可以使用Handler,但如果是涉及到后臺操作,像是下載任務(wù),然后根據(jù)后臺任務(wù)的進(jìn)展來更新UI,就得使用AsyncTask,但如果前者我們就使用AsyncTask,那真的是太大材小用了!!
  要想真正理解好AsyncTask,首先就要理解很多并發(fā)知識,像是靜態(tài)線程池這些難以理解的概念是必不可少的,作為新手,其實沒有必要在實現(xiàn)細(xì)節(jié)上過分追究,否則很容易陷入細(xì)節(jié)的泥潭中,我們先要明白它是怎么用的,等用得多了,就會開始思考為什么它能這么用,接著就是怎么才能用得更好,這都是一個自然的學(xué)習(xí)過程,誰也無法越過,什么階段就做什么事。因此,關(guān)于AsyncTask的討論我就先放到一邊,接下來的東西我也根本理解不了,又怎能講好呢?

3.利用Runnable更新UI界面
剩下的方法都是圍繞著Runnable對象來更新UI。
一些組件本身就有提供方法來更新自己,像是ProgressBar本身就有一個post()方法,只要我們傳進(jìn)一個Runnable對象,就能更新它的進(jìn)度。只要是繼承自View的組件,都可以利用post()方法,而且我們還可以使用postDelay()方法來延遲執(zhí)行該Runnable對象。android的這種做法就真的是讓人稱道了,至少我不用為了一個ProgressBar的進(jìn)度更新就寫出一大堆難懂的代碼出來。
還有另一種利用Runnable的方式:Activity.runOnUiThread()方法。這名字實在是太直白了?。∈褂迷摲椒ㄐ枰聠⒁粋€線程:

class ProgressThread extends Thread {
        @Override
        public void run() {
            super.run();
            while (mProgress <= 100) {
                runOnUiThread(new Runnable() {

                    @Override
                    public void run() {
                        mProgressBar.setProgress(mProgress);
                        mProgress++;
                    }
                });
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {


                }
            }
        }
    }

4.總結(jié)
上面提供了三種思路來解決UI更新的問題,有些地方的討論已經(jīng)嚴(yán)重脫離標(biāo)題,那也是沒有辦法,因為要說明一些概念,就必須涉及到并發(fā)的其他相關(guān)知識。方法很多,但它們都有自己適合的場合:
1.如果只是單純的想要更新UI而不涉及到多線程的話,使用View.post()就可以了;
2.需要另開線程處理數(shù)據(jù)以免阻塞UI線程,像是IO操作或者是循環(huán),可以使用Activity.runOnUiThread();
3.如果需要傳遞狀態(tài)值等信息,像是藍(lán)牙編程中的socket連接,就需要利用狀態(tài)值來提示連接狀態(tài)以及做相應(yīng)的處理,就需要使用Handler + Thread的方式;
4.如果是后臺任務(wù),像是下載任務(wù)等,就需要使用AsyncTask。 本來只是因為藍(lán)牙項目而開始這篇博客,但沒想到在寫的過程發(fā)現(xiàn)越來越多的東西,于是也一起寫上來了,寫得不好是一定的,因為是大三菜鳥,正在拼命增強(qiáng)自己薄弱的編程基礎(chǔ)中,如果錯誤的地方,還希望能夠指點迷津。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容