Screen Shot 2012-04-06 at 下午4.47.14     

 

這是由之前寫的這一篇

http://j796160836.pixnet.net/blog/post/28994669

所延伸出來的

 

眼尖的網友已經發現,之前放上的程式碼範例已經不能Run了
把專案使用的Android版本放在2.3.3以下就可以正確執行
而把它升到4.0就會出錯

有人發現為什麼了嗎?

-----------------------------------------------------------------------------------------------------------------------------------

打開LogCat就可以清楚的看到

Screen Shot 2012-04-06 at 上午12.13.33  

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

實際設定如圖:

Screen Shot 2012-04-06 at 下午2.45.22  

這是其對應的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

arrow
arrow
    全站熱搜
    創作者介紹
    創作者 Johnny 鋼鍊 的頭像
    Johnny 鋼鍊

    清新下午茶

    Johnny 鋼鍊 發表在 痞客邦 留言(55) 人氣()