編輯:關於Android編程
線程概覽
線程是任何多任務系統的基石。可以被認為是一個主進程的多個子進程。這樣做的目的就是了增加應用的性能。
應用主線程
當一個Android應用被打開的時候,系統會默認開辟一個線程。這個線程就被叫做是主線程。主線程的主要任務就是處理用戶輸入,即事件處理和view上的用戶交互。任何應用裡的其他組件,默認的,都是在主線程中運行的。
一個應用的任何組件,如果在主線程上執行一個耗時的任務的話,都會使整個應用等待這個任務的完成。如果耗時過長的話就會觸發系統的“Application is unresponsive”警告。顯然,這個是任何應用都不願意出現的狀況。在這種情況下,只能開辟一個單獨的線程來執行這個耗時的任務,這樣才不會干擾主線程上的其他任務。
線程Handler
所以,應用開發中最關鍵的一條就是永遠不要在主線程上執行耗時過長的任務。另外一個同樣重要的規則是另外開辟的單獨的線程任何情況下、絕對不可以直接更新用戶界面。任何對用戶界面的更新都要在主線程中進行。之所以這樣的原因是Android的UI不是線程安全的。在多線程環境下調用非線程安全的代碼會導致斷斷續續的問題以及不可預料的應用行為。
要在子線程中更新用戶界面就只能通過Handler來實現。
一個簡單的Thread例子
這裡會提供幾個簡單的例子來展示線程和Handler是如何使用的。第一步,展示一下耗時任務沒有放在另外開辟的線程中,而放在主線程中出現的問題。首先創建一個Android項目叫做“ThreadExample”,包含一個單獨的空白的activity:ThreadExampleActivity,layout叫做activity_thread_example。
具體的布局文件如下:
看起來是這樣的:
保存。接下來,雙擊ThreadExampleActivity.java進入編輯模式。在這個activity文件中實現button的click方法。這個方法會在用戶點擊按鈕之後被調用。這裡主要是展示耗時任務的問題,所以會在主線程中發起一個20秒的延遲,之後更新TextView對象的文字。
代碼如下:
package com.example.myapp; import android.app.Activity; import android.os.Bundle; import android.view.View; import android.widget.Button; import android.widget.TextView; public class ThreadExampleActivity extends Activity implements View.OnClickListener{ /** * Called when the activity is first created. */ @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_thread_example); Button threadButton = (Button)findViewById(R.id.thread_button); threadButton.setOnClickListener(this); } @Override public void onClick(View v) { long endTime = System.currentTimeMillis() + 20 * 1000; // waiting... while (System.currentTimeMillis() < endTime) { synchronized (this) { try{ wait(endTime - System.currentTimeMillis()); } catch (Exception e) { } } } // update `TextView`, after 20 seconds TextView textView = (TextView)findViewById(R.id.text_view); textView.setText(Button Pressed); } }
以上代價在運行之後,點擊一下按鈕,這個時候整個應用就在20秒的等待中。再次或者多次點擊這個按鈕不會立刻有反應。這時候系統就會彈出一個提醒:應用正在忙:
因此,在按鈕點擊方法中,耗時的操作應該放置在另外一個單獨的線程中。
創建一個新的線程
要創建一個新的線程,並讓代碼在這個線程中執行,需要把這些代碼都放在Runnable接口的Run中。然後需要創建一個新的Thread對象。把Runnable接口的實例作為參數傳給Thread的構造函數中。最後調用Thread實例的start方法來開辟線程並執行線程中的方法。
修改後的代碼如下:
@Override public void onClick(View v) { Runnable runnable = new Runnable() { @Override public void run() { long endTime = System.currentTimeMillis() + 20 * 1000; // waiting... while (System.currentTimeMillis() < endTime) { synchronized (this) { try{ wait(endTime - System.currentTimeMillis()); } catch (Exception e) { } } } } }; Thread thread = new Thread(runnable); thread.start(); }
當應用再次運行起來之後。點擊按鈕之後把造成延時的任務都放在了新的線程中運行,主線程可以及時響應用戶的任何操作,包括無休止的按鈕點擊。事實上,每次的點擊都會創建一個新的線程,這樣任務就可以在多個線程中並發執行。
兩外一個需要注意的地方是,點擊按鈕之後更新TextView的文字的代碼被去掉了。就像之前提到的,要更新界面上的內容只能在主線程中進行。要實現這個功能就需要給單獨開辟的線程引入Handler實例。
實現一個Thread Handler
線程的Handler的實現是放在主線程中的,主要就是用來響應子線程的message並根據這個message來更新主線程的。
Handler繼承自Android的Handler類。用來表明線程的Runnable實例即將執行,或overridehandleMessage方法,這個方法接受和處理子線程發送的message。本例會用Handler來更新用戶界面。
修改後的代碼如下:
package com.example.myapp; import android.app.Activity; import android.os.Bundle; import android.os.Handler; import android.os.Message; import android.view.View; import android.widget.Button; import android.widget.TextView; public class ThreadExampleActivity extends Activity implements View.OnClickListener { Handler handler = new Handler() { @Override public void handleMessage(Message message) { TextView textView = (TextView)findViewById(R.id.text_view); textView.setText(Button Pressed!); } }; //... }
上面的代碼中聲明了一個handler並實現了handleMessage回調方法。當子線程發出message的時候可以被這個方法處理。在這個實例中,只是簡單地在代碼中設置了TextView實例的文字。
現在就剩下修改button點擊事件中創建的線程了。我們需要在這個線程裡發出一個消息告訴handler20秒的延時任務已經執行完成。
修改後的代碼如下:
@Override public void onClick(View v) { Runnable runnable = new Runnable() { @Override public void run() { long endTime = System.currentTimeMillis() + 20 * 1000; // waiting... while (System.currentTimeMillis() < endTime) { synchronized (this) { try{ wait(endTime - System.currentTimeMillis()); } catch (Exception e) { } } } // waiting is over handler.sendEmptyMessage(0); } }; Thread thread = new Thread(runnable); thread.start(); }
這段修改中唯一的修改就是增加了的就是handler調用sendEmptyMessage方法。由於handler實例不需要特別發送什麼message所以這裡只發送空消息。執行代碼之後,點擊按鈕,等待20秒。TextView就會顯示新的文本。
給Handler傳遞消息
之前的代碼調用了handleMessage方法。但是這個方法並沒有發揮出message可以發送數據給handler的優點。下面就會對現有的代碼做出更多的修改來在子線程和handler實例之間傳遞數據。首先,在創建的子線程中會從系統獲取到date和time,並轉換成字符串。這些內容會保存在一個bundle實例中。然後調用handler的obtainMessage方法從message池中獲取一個message實例。最後,這個保存了系統信息的bundle會被添加到message實例中並被sendMessage方法發送給handle實例。
@Override public void onClick(View v) { Runnable runnable = new Runnable() { @Override public void run() { Message message = handler.obtainMessage(); Bundle bundle = new Bundle(); SimpleDateFormat dateFormat = new SimpleDateFormat(HH:mm:ss MM/dd/yyy, Locale.US); String dateString = dateFormat.format(new Date()); bundle.putString(thread_date, dateString); // key is `thread_date` message.setData(bundle); handler.sendMessage(message); } }; Thread thread = new Thread(runnable); thread.start(); }
接下來更新handleMessage方法。用這個方法把接收到的時間顯示在TextView實例中。
Handler handler = new Handler() { @Override public void handleMessage(Message message) { Bundle bundle = message.getData(); String dateString = bundle.getString(thread_date); TextView textView = (TextView)findViewById(R.id.text_view); textView.setText(dateString); } };
最後編譯運行代碼,點擊按鈕測試一下我們的修改是否成功。
總結
本教程就是提供一個對於Android應用實現多線程的概覽。當一個app運行在一個進程中的時候,系統會給這個app穿件一個主線程。主線程的主要功能就是處理用戶輸入,所以任何執行時間過長的任務都會導致主線程無法及時響應用戶後續的輸入。所以,耗時的任務都應該放在另外開辟的子線程中執行。這些都是很基礎的。因為Android用戶界面的各種元素都是非線程安全的,所以對於界面的修改智能在主線程中進行。在主線程中可以使用Handler實例來接受子線程發出的消息來更新界面元素。
前言 這篇文章可以說是java基礎的范疇,為了下一篇Android開發中的常用設計模式做一下鋪墊,也順便反思一下。 正文 設計模式分類 分類方式是多樣的,
之前寫過一個二維碼掃描demo,用的Zxing的框架,點擊下載,後續掃描二維碼中出現一些問題,比如解決壓縮圖片,調整掃描窗口大小等等。後續單位要求做掃描登錄實現,發現難點
本章內容Activity 四種啟動模式的理論知識 代碼理解Activity 四種啟動模式的理論知識standard默認模式,可以不用寫配置。在這個模式下,都會默認創建一個
Android中所有控件都繼承自android.view.View,其中android.view.ViewGroup是View的一個重要子類,絕大部分的布局都繼承自Vie