這是由之前寫的這一篇
http://j796160836.pixnet.net/blog/post/28994669
所延伸出來的
眼尖的網友已經發現,之前放上的程式碼範例已經不能Run了 把專案使用的Android版本放在2.3.3以下就可以正確執行 而把它升到4.0就會出錯
有人發現為什麼了嗎?
打開LogCat就可以清楚的看到
Android 4.0 (ICS)在網路的部份多了一個新的Exception
叫做android.os.NetworkOnMainThreadException 意思很白話的就是:網路的活動跑在主要執行緒上了啦!
ICS很貼心的告訴你,這樣子你的APP可能會因為等待回應太久(超過5秒) 而會被系統強制關閉(收到ANR)
(備註:之前是為了示例方便,所以沒有照多執行緒的寫法撰寫,在這裡當然也就不能執行啦 Android光這點就還蠻嚴謹的)
OK,瞭解為何出錯的原因 也剛好在這裡,帶大家看看多執行緒要怎麼改吧
專案的Layout版面等定義上和之前這篇一模一樣 有特別修改的地方會特別標出
package com.J_Test.httpPostTest;
import java.util.ArrayList;import java.util.List;import org.apache.http.HttpResponse;import org.apache.http.NameValuePair; import org.apache.http.client.entity.UrlEncodedFormEntity; import org.apache.http.client.methods.HttpPost; import org.apache.http.impl.client.DefaultHttpClient; import org.apache.http.message.BasicNameValuePair; import org.apache.http.protocol.HTTP; import org.apache.http.util.EntityUtils; import android.app.Activity; import android.os.Bundle; import android.os.Handler; import android.os.Message; import android.util.Log; import android.view.View; import android.view.View.OnClickListener; import android.widget.Button; import android.widget.EditText; import android.widget.Toast;
public class Main extends Activity implements OnClickListener
{
private EditText txtMessage ;
private Button sendBtn ;
private String uriAPI = "http://192.168.1.3/httptest/httpPostTest.php " ;
/* 「要更新版面」的訊息代碼 /
protected static final int REFRESH_DATA = 0x00000001;
/* 建立 UI Thread 使用的 Handler ,來接收其他 Thread 來的訊息 /
Handler mHandler = new Handler()
{
@Override
public void handleMessage(Message msg)
{
switch (msg. what )
{
// 顯示網路上抓取的資料
case REFRESH_DATA :
String result = null ;
if (msg. obj instanceof String)
result = (String) msg. obj ;
if (result != null )
// 印出網路回傳的文字
Toast.makeText (Main. this , result, Toast. LENGTH_LONG ).show();
break ;
}
}
};
@Override
public void onCreate(Bundle savedInstanceState)
{
super .onCreate(savedInstanceState);
setContentView(R.layout. main );
txtMessage = (EditText) findViewById(R.id. txt_message ); sendBtn = (Button) findViewById(R.id. send_btn );
if ( sendBtn != null ) sendBtn .setOnClickListener( this );
}
@Override public void onClick(View v)
{
if (v == sendBtn )
{
if ( txtMessage != null )
{
// 擷取文字框上的文字
String msg = txtMessage .getEditableText().toString();
// 啟 動一個 Thread( 執行緒 ) ,將要傳送的資料放進 Runnable 中,讓 Thread 執行
Thread t = new Thread( new sendPostRunnable(msg));
t.start();
}
}
}
private String sendPostDataToInternet(String strTxt)
{
/ 建立 HTTP Post 連線 /
HttpPost httpRequest = new HttpPost( uriAPI );
/*
* Post 運作傳送變數必須用 NameValuePair[] 陣列儲存
*/
List<NameValuePair> params = new ArrayList<NameValuePair>();
params.add( new BasicNameValuePair( "data" , strTxt));
try
{
/ 發出 HTTP request /
httpRequest.setEntity( new UrlEncodedFormEntity(params, HTTP. UTF_8 ));
/ 取得 HTTP response /
HttpResponse httpResponse = new DefaultHttpClient()
.execute(httpRequest);
/ 若狀態碼為 200 ok /
if (httpResponse.getStatusLine().getStatusCode() == 200)
{
/ 取出回應字串 /
String strResult = EntityUtils.toString (httpResponse
.getEntity());
// 回傳回應字串
return strResult;
}
} catch (Exception e)
{
e.printStackTrace();
}
return null ;
}
class sendPostRunnable implements Runnable
{
String strTxt = null ;
// 建構子,設定要傳的字串
public sendPostRunnable(String strTxt)
{
this . strTxt = strTxt;
}
@Override
public void run()
{
String result = sendPostDataToInternet( strTxt );
mHandler .obtainMessage( REFRESH_DATA , result).sendToTarget();
}
}
}
特別注意這裡
Handler mHandler = new Handler()
{
@Override
public void handleMessage(Message msg)
{
switch (msg. what )
{
case REFRESH_DATA :
// …………( 相關程式碼)
break ;
}
}
};
這是Android對於多執行緒的寫法
這裡做了一個Handler來接收訊息
因為當你建立執行緒,資料處理與主執行緒無關
當然,新建出來的執行緒,是碰不到UI介面的 (會跳Exception)
所以才會如此設計
訊息,說穿了就是一個int ,可以自行定義訊息的內容
/** 「要更新版面」的訊息代碼 */
protected static final int REFRESH_DATA = 0x00000001;
類別一開始就定義這個常數
然後在
public void handleMessage(Message msg) {
}
的地方就可以使用 msg. what 存取到訊息的內容
這裡用Switch – case的方式撰寫
發訊息的函式如下
mHandler .obtainMessage( REFRESH_DATA , result).sendToTarget();
發訊息的相關方法呼叫可參考官方說明
http://developer.android.com/reference/android/os/Handler.html
文件上可看到,obtainMessage有這幾個多載方法
obtainMessage (int what);
obtainMessage (int what, int arg1, int arg2);
obtainMessage (int what, int arg1, int arg2, Object obj);
obtainMessage (int what, Object obj);
常用的有第一、第二、第四種
尤其是第四種,這訊息可以傳入Object
所以自由度很高
最後,是建立執行緒
// 啟 動一個 Thread( 執行緒 ) ,將要傳送的資料放進 Runnable 中,讓 Thread 執行
Thread t = new Thread( new sendPostRunnable(msg));
t.start();
這裡是用一個臨時的執行緒的方式
在建構子之中傳入一個Runnable來達成
執行緒的部分也可以用匿名class的方式撰寫
// 擷取文字框上的文字
final String msg = txtMessage .getEditableText().toString();
new Thread()
{
public void run()
{
String result = sendPostDataToInternet(msg);
mHandler .obtainMessage( REFRESH_DATA , result) .sendToTarget();
}
}.start();
但這樣的方式要注意,內部類別(Inner class)當中
只能存取外部的常數 (這是Java的規定)
final String msg;
所以為何String前面要加上final了
詳細多執行緒可參考
[Android] 多執行緒-Handler和Thread的關係
http://j796160836.pixnet.net/blog/post/28766165
[Android] 關於Thread執行緒
http://j796160836.pixnet.net/blog/post/29895257
很多網友都問到,
為何我在Android傳送出來的資料 在瀏覽器中怎麼都看不見
因為資料到伺服器上沒有將它存下來 在這裡可以做一些修改
我們把傳到伺服器上的資料 用MySQL存下來,然後再顯示最後輸入的資料
以PHP為例,我們在phpmyadmin上 建立一個資料庫,名稱叫 httpPostTest
實際設定如圖:
這是其對應的SQL
CREATE TABLE IF NOT EXISTS weblog
( log_id
int(11) NOT NULL AUTO_INCREMENT COMMENT ‘編號’, data
varchar(255) NOT NULL COMMENT ‘傳入的資料’, post_time
timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT ‘發佈時間’, PRIMARY KEY (log_id
) ) ENGINE=MyISAM DEFAULT CHARSET=utf8 COMMENT=’訊息記錄’ AUTO_INCREMENT=1 ;
之前使用的 httpPostTest.php 我們做一些修改
<?php
// 資料庫相關資料 $database_dblink = "httpPostTest"; $username_dblink = "root"; $password_dblink = "YOUR_ROOT_PASSWORD";
// 建立資料庫連線 $dblink = mysql_pconnect("localhost", $username_dblink, $password_dblink) or trigger_error(mysql_error(),E_USER_ERROR); mysql_query("SET NAMES utf8",$dblink); mysql_query("SET CHARACTER_SET_CLIENT=utf8",$dblink); mysql_query("SET CHARACTER_SET_RESULTS=utf8",$dblink); mysql_select_db($database_dblink, $dblink);
// 宣告utf-8的編碼 header("Content-Type:text/html; charset=utf-8"); // 接收POST/GET的資料 $data=@$_REQUEST[‘data’];
// 如果有資料 if (strcmp(trim($data), "")!=0) { // 將資料輸入進資料庫 $insertSQL = sprintf("INSERT INTO weblog
(data
) VALUES (‘%s’);", $data); mysql_query($insertSQL, $dblink) or die(mysql_error()); }
// 從資料庫撈出來最後一筆資料 $query_rs = "SELECT * FROM weblog
order by log_id desc limit 0,1"; $rs = mysql_query($query_rs, $dblink) or die(mysql_error());
$row = mysql_fetch_assoc($rs); echo "data=".$row[‘data’]."n"."time=".$row[‘post_time’];
?>
這樣在瀏覽器就會暫存最後一筆的資料了
如果有學過PHP相關的語法
以上的程式碼應該不難才是
最後還是提醒一下,Android還沒有跑出來的朋友 若是在Logcat看到類似Permission denied的字眼
代表你忘記了AndroidManifest.xml的這句摟,詳細看之前寫的這篇 吧
<!– 這裡加入可以存取網路的權限 –>
< uses-permission android:name = "android.permission.INTERNET" />
關於source code的部份,我已經放上gitHub了
https://github.com/j796160836/httpPostTest