Android學習筆記三十八:Android4.0 Socket異常,需要另外開闢執行緒進行Socket程式設計
Socket socket = new Socket();
socket.connect(new InetSocketAddress(ConstData.TCP_IP,
ConstData.TCP_PORT), 2000);
通不過去,直接異常處理,這是因為android 3.0+以上 已經不建議在activity中新增耗時操作,要介面和資料脫離。4.0以上的通訊都必須放到執行緒裡去做 不能在UI執行緒。
解決辦法,另起執行緒或Service處理socket。
如果一定要想在UI執行緒操作,新增如下程式碼:
StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder()
.detectDiskReads().detectDiskWrites().detectNetwork()
.penaltyLog().build());
StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder()
.detectLeakedSqlLiteObjects().penaltyLog().penaltyDeath()
.build());
經測試,另起一個service,在service的onStartCommand()發郵件,會令service無法啟動,可能因為service也在主執行緒中。
如果在主執行緒中進行socket,則丟擲:NetworkOnMainThreadException異常。
最近單位來了一個Android4.1平臺的360街景專案。在編寫該專案demo的過程中,為了省事,打算直接在UI執行緒中訪問網路資料來源並生成Bitmap以填充相應的檢視。訪問網路模組的封裝採用了HttpClient的方式進行構建。編寫完工後執行程式,發現檢視顯示的還是本地的預設圖樣。在確認了網路許可權已被開啟的情況下,我開始懷疑是不是HttpClient封裝的粒度過大,導致其適用範圍受限的問題。於是乾脆採用Java平臺最底層的Socket套接字方式來實現網路訪問,可是結果還是一樣的,仍舊無法得到網路資料。經過除錯發現,在客戶端發出請求之後,根本無法連線到服務端,也就無法解析後續的服務端的響應內容了。
以前在Android2.3.3平臺上研發怎麼沒有這個現象?難道是Android4.0的單執行緒模式的“禁令”較之以往更為嚴格了。為了使應用程式具有更好的互動性和更少的延遲時間,強勢要求開發人員在UI主執行緒中只能執行與UI相關的工作(如:更新檢視、與使用者互動等),其他方面的工作一律禁止執行。為了驗證這個相反,在stackoverflow.com檢索了相關議題,從一位Google工程師的解答中基本得到了印證。也就是說,如果你非要在UI主執行緒中執行其他工作(如:訪問網路、檔案操作等),其實有這種程式設計強迫症的人在專案初期還是很多的,筆者就是其中的一位。你需要為UI主執行緒所在的Activity設定執行緒策略,告知平臺請賦予我在UI主執行緒中進行其他工作的許可權。具體做法有如下:
在你的Application、Activity或其它應用容器中新增如下程式碼:
[java] view plaincopyprint?
-
public void onCreate() {
-
if (DEVELOPER_MODE) {
-
StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder()
-
.detectDiskReads() // 捕捉讀取磁碟
-
.detectDiskWrites() // 捕捉寫入磁碟
-
.detectNetwork() // 捕捉網路訪問 或使用detectAll() 火力全開
-
.penaltyLog() // 捕捉LogCat日誌
-
.build());
-
StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder()
-
.detectLeakedSqlLiteObjects()
-
.detectLeakedClosableObjects()
-
.penaltyLog()
-
.penaltyDeath()
-
.build());
-
}
-
super.onCreate();
-
}
public void onCreate() {
if (DEVELOPER_MODE) {
StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder()
.detectDiskReads() // 捕捉讀取磁碟
.detectDiskWrites() // 捕捉寫入磁碟
.detectNetwork() // 捕捉網路訪問 或使用detectAll() 火力全開
.penaltyLog() // 捕捉LogCat日誌
.build());
StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder()
.detectLeakedSqlLiteObjects()
.detectLeakedClosableObjects()
.penaltyLog()
.penaltyDeath()
.build());
}
super.onCreate();
}
StrictMode類
StrictMode是一個開發者工具類,從Android 2.3平臺開始被引入。可以用於捕捉髮生在應用程式UI主執行緒中耗時的IO操作、網路訪問或方法呼叫;也可以用來改進應用程式,使得UI主執行緒在處理IO操作和訪問網路時顯得更平滑,避免其被阻塞,導致ANR警告。更多有關StrictMode的資訊,請參見http://developer.android.com/reference/android/os/StrictMode.html。
這種非常規的做法,是在專案初期和開發模式下為了達到更高的效率,而採取一種提高生產效率做法。在產品交付和運維時,我們還是要嚴格遵守Android平臺程序與執行緒安全管理機制。接下來是在實際開發中應該遵循的兩個原則:
UI主執行緒
在UI主執行緒中,只處理與UI相關及使用者互動的工作,耗時的工作一律交由後臺工作執行緒去搭理。常見的耗時工作處理方式有:
AsyncTask;
Handler、MessageQueue、Looper;
ExecutorService(execute/submit)
工作執行緒
在工作執行緒中,只做自己分內的事。決不干涉UI主執行緒的工作。在執行過程中如果存在涉及到UI的操作(如:更新檢視、重繪等),一律將其轉交給UI主執行緒進行處理。常見的轉交方式有:
Activity.runOnUiThread(new Runnable(){...});
View.post(new Runnable(){...});
View.postDelay(Runnable(){...},long)
示例
最後,提供AsyncMultiThreadActivity演示Android多執行緒與UI互動的方式,僅供讀者參考使用。
[java] view plaincopyprint?
-
public class AsyncMultiThreadActivity extends Activity {
-
private TextView txView;
-
private Button button;
-
/** Called when the activity is first created. */
-
@Override
-
public void onCreate(Bundle savedInstanceState) {
-
Log.i("RootyInfo", "oncreate");
-
super.onCreate(savedInstanceState);
-
setContentView(R.layout.main);
-
txView=(TextView)findViewById(R.id.textView1);
-
button=(Button)findViewById(R.id.button1);
-
button.setOnClickListener(new OnClickListener() {
-
@Override
-
public void onClick(View v) {
-
//建立一個用於展示前三種後臺執行緒和UI執行緒互動的執行緒
-
new TestThread(MainActivity.this).start();
-
//建立一個用於展示AsyncTask實現互動的TestAsyncTask
-
new TestAsyncTask().execute("Test"," AsyncTask");
-
}
-
});
-
}
-
class TestAsyncTask extends AsyncTask<String, Integer, String> {
-
//TestAsyncTask被後臺執行緒執行後,被UI執行緒被呼叫,一般用於初始化介面控制元件,如進度條
-
@Override
-
protected void onPreExecute() {
-
super.onPreExecute();
-
}
-
//doInBackground執行完後由UI執行緒呼叫,用於更新介面操作
-
@Override
-
protected void onPostExecute(String result) {
-
txView.setText(result);
-
super.onPostExecute(result);
-
}
-
//在PreExcute執行後被啟動AysncTask的後臺執行緒呼叫,將結果返回給UI執行緒
-
@Override
-
protected String doInBackground(String... params) {
-
StringBuffer sb=new StringBuffer();
-
for (String string : params) {
-
sb.append(string);
-
}
-
return sb.toString();
-
}
-
}
-
//用於執行緒間通訊的Handler
-
class TestHandler extends Handler {
-
public TestHandler(Looper looper) {
-
super(looper);
-
}
-
@Override
-
public void handleMessage(Message msg) {
-
System.out.println("123");
-
txView.setText((String)msg.getData().get("tag"));
-
super.handleMessage(msg);
-
}
-
}
-
//後臺執行緒類
-
class TestThread extends Thread {
-
Activity activity;
-
public TestThread(Activity activity) {
-
this.activity = activity;
-
}
-
@Override
-
public void run() {
-
// 演示Activity.runOnUIThread(Runnable)方法的實現
-
activity.runOnUiThread(new Runnable() {
-
@Override
-
public void run() {
-
txView.setText("Test runOnUIThread");
-
}
-
});
-
// 演示Activity.runOnUIThread(Runnable)方法的實現
-
txView.post(new Runnable() {
-
@Override
-
public void run() {
-
txView.setText("Test View.post(Runnable)");
-
}
-
});
-
// 演示Activity.runOnUIThread(Runnable)方法的實現
-
txView.postDelayed(new Runnable() {
-
@Override
-
public void run() {
-
txView.setText("Test View.postDelay(Runnable,long)");
-
}
-
}, 1000);
-
// 演示Handler方法的實現
-
Message msg=new Message();
-
Bundle bundle=new Bundle();
-
bundle.putString("tag", "Test Handler");
-
msg.setData(bundle);
-
new TestHandler(Looper.getMainLooper()).sendMessage(msg);
-
super.run();
-
}
-
}
-
}