這是由之前寫的這一篇
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 intREFRESH_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了
留言列表