1. 程式人生 > >【一步一個腳印】Tomcat+MySQL為自己的APP打造伺服器(4)完結篇

【一步一個腳印】Tomcat+MySQL為自己的APP打造伺服器(4)完結篇

        在這個系列的前幾篇文章中,從最初簡單的伺服器環境搭建MySQL資料庫的安裝Servlet 的原理及使用資料庫的連線及CURD操作Android和伺服器GET/POST資料互動,到最後JSon格式報文的使用,我們已經將這個過程完整的走完一遍,但是其中用的程式碼都是片段式的,沒有一個清晰的結構,甚至有些程式碼只是單純地為了說明用法,還有一些朋友提出說程式碼中有一些自定義的方法沒有說明,所以我們最後來一個總結篇,把之前的程式碼優化規整一下,順便把之前的一些問題明確一下。

        先從 Android 部分開始吧(注意:這裡作為學習的目的,不使用第三方網路通訊庫,直接使用原生 API)——

        之前的文章中說過,在 Android 中進行網路請求使用非同步任務類 AsyncTask 比自己手動 new Thread() 要更便捷,這個我們在【一步一個腳印】Tomcat+MySQL為自己的APP打造伺服器(3-1)Android 和 Service 的互動之GET方式最後也做過示例。但是如果要在專案中使用,明顯不可能每次網路請求都寫一個子類來繼承 Asynctask,不然還要累死人,所以我們需要寫個工具類專門來進行網路請求:

        HttpPostTask.java:

/**
 * 網路通訊非同步任務類
 * 
 * @author WangJ
 */
public class HttpPostTask extends AsyncTask<String, String, String> {

	/** BaseActivity 中基礎問題的處理 handler */
	private Handler mHandler;

	/** 返回資訊處理回撥介面 */
	private ResponseHandler rHandler;

	/** 請求類物件 */
	private CommonRequest request;

	public HttpPostTask(CommonRequest request,
						Handler mHandler,
						ResponseHandler rHandler) {
		this.request = request;
		this.mHandler = mHandler;
		this.rHandler = rHandler;
	}

	@Override
	protected String doInBackground(String... params) {
        StringBuilder resultBuf = new StringBuilder();
		try {
			URL url = new URL(params[0]);

			// 第一步:使用URL開啟一個HttpURLConnection連線
			HttpURLConnection connection = (HttpURLConnection) url.openConnection();

			// 第二步:設定HttpURLConnection連線相關屬性
			connection.setRequestProperty("Content-Type", "application/json;charset=utf-8");
			connection.setRequestMethod("POST"); // 設定請求方法,“POST或GET”
			connection.setConnectTimeout(8000); // 設定連線建立的超時時間
			connection.setReadTimeout(8000); // 設定網路報文收發超時時間
			connection.setDoOutput(true);
			connection.setDoInput(true);

			// 如果是POST方法,需要在第3步獲取輸入流之前向連線寫入POST引數
			DataOutputStream out = new DataOutputStream(connection.getOutputStream());
            out.writeBytes(request.getJsonStr());
			out.flush();

			// 第三步:開啟連線輸入流讀取返回報文 -> *注意*在此步驟才真正開始網路請求
			int responseCode = connection.getResponseCode();
			if (responseCode == HttpURLConnection.HTTP_OK) {
				// 通過連線的輸入流獲取下發報文,然後就是Java的流處理
				InputStream in = connection.getInputStream();
				BufferedReader read = new BufferedReader(new InputStreamReader(in));
				String line;
				while((line = read.readLine()) != null) {
                    resultBuf.append(line);
				}
				return resultBuf.toString();
			} else {
				// 異常情況,如404/500...
				mHandler.obtainMessage(Constant.HANDLER_HTTP_RECEIVE_FAIL,
						"[" + responseCode + "]" + connection.getResponseMessage()).sendToTarget();
			}
		} catch (IOException e) {
			// 網路請求過程中發生IO異常
			mHandler.obtainMessage(Constant.HANDLER_HTTP_SEND_FAIL,
					e.getClass().getName() + " : " + e.getMessage()).sendToTarget();
		}
		return resultBuf.toString();
	}

	@Override
	protected void onPostExecute(String result) {
		if (rHandler != null) {
			if (!"".equals(result)) {
				/* 交易成功時需要在處理返回結果時手動關閉Loading對話方塊,可以靈活處理連續請求多個介面時Loading框不斷彈出、關閉的情況 */

				CommonResponse response = new CommonResponse(result);
				// 這裡response.getResCode()為多少表示業務完成也是和伺服器約定好的
				if ("0".equals(response.getResCode())) { // 正確
					rHandler.success(response);
				} else {
					rHandler.fail(response.getResCode(), response.getResMsg());
				}
			}
		}
	}

}        

        上邊程式碼中 HttpURLConnection 的用法之前已經用過幾次了,沒什麼問題。但是會發現其中出現了幾個新面孔,下面我們來說說為什麼用這幾個新面孔:

        (1)CommonRequest類

        為什麼要引入這個類呢?一方面是為了更強健的功能,畢竟我們現在是用 Servlet 來作為伺服器處理單元,但是實際上在專案中會使用Spring、Struts、Hibernate等框架,我們可以在請求中加入介面號來區分業務請求,而不僅僅是隻上傳一個請求引數的Map;另一方面,是為了程式碼的優化,更符合面向物件的程式設計,網路請求的輸入就是一個請求CommonRequest物件,返回就是一個應答CommonResponse物件。下面來看看CommonRequest的程式碼:

/**
 * 基本請求體封裝類
 * Created by WangJie on 2017-05-03.
 */
public class CommonRequest {
    /**
     * 請求碼,類似於介面號(在本文中用Servlet做伺服器時暫時用不到)
     */
    private String requestCode;
    /**
     * 請求引數
     * (說明:這裡只用一個簡單map類封裝請求引數,對於請求報文需要上送一個數組的複雜情況需要自己再加一個ArrayList型別的成員變數來實現)
     */
    private HashMap<String, String> requestParam;

    public CommonRequest() {
        requestCode = "";
        requestParam = new HashMap<>();
    }

    /**
     * 設定請求程式碼,即介面號,在本例中暫時未用到
     */
    public void setRequestCode(String requestCode) {
        this.requestCode = requestCode;
    }

    /**
     * 為請求報文設定引數
     * @param paramKey 引數名
     * @param paramValue 引數值
     */
    public void addRequestParam(String paramKey, String paramValue) {
        requestParam.put(paramKey, paramValue);
    }

    /**
     * 將請求報文體組裝成json形式的字串,以便進行網路傳送
     * @return 請求報文的json字串
     */
    public String getJsonStr() {
        // 由於Android原始碼自帶的JSon功能不夠強大(沒有直接從Bean轉到JSonObject的API),為了不引入第三方資源這裡我們只能手動拼裝一下啦
        JSONObject object = new JSONObject();
        JSONObject param = new JSONObject(requestParam);
        try {
            // 下邊2個"requestCode"、"requestParam"是和伺服器約定好的請求體欄位名稱,在本文接下來的服務端程式碼會說到
            object.put("requestCode", requestCode);
            object.put("requestParam", param);
        } catch (JSONException e) {
            LogUtil.logErr("請求報文組裝異常:" + e.getMessage());
        }
        // 列印原始請求報文
        LogUtil.logRequest(object.toString());
        return object.toString();
    }
}
        其實就是一個Beans類,只是寫了一個獲取其JSon型別的方法,在傳送請求時可以直接使用commonRequest.getJsonStr()來寫入請求了。

        (2)CommonResponse類

        這個類和 CommonRequest 類的目的其實是一致的,用來封裝應答報文,方便網路請求成功後的處理,直接看程式碼:

/**
 * 常規返回報文格式化(如果有陣列只能是單層陣列,業務邏輯複雜時請服務端優化邏輯,或者分開請求不同的介面)
 *
 * @author WangJ 2016.06.02
 */
public class CommonResponse {

    /**
     * 交易狀態程式碼
     */
    private String resCode = "";

    /**
     * 交易失敗說明
     */
    private String resMsg = "";

    /**
     * 簡單資訊
     */
    private HashMap<String, String> propertyMap;

    /**
     * 列表類資訊
     */
    private ArrayList<HashMap<String, String>> mapList;

    /**
     * 通用報文返回建構函式
     *
     * @param responseString Json格式的返回字串
     */
    public CommonResponse(String responseString) {

        // 日誌輸出原始應答報文
        LogUtil.logResponse(responseString);

        propertyMap = new HashMap<>();
        mapList = new ArrayList<>();

        try {
            JSONObject root = new JSONObject(responseString);

            /* 說明:
                以下名稱"resCode"、"resMsg"、"property"、"list"
                和請求體中提到的欄位名稱一樣,都是和伺服器程式開發者約定好的欄位名字,在本文接下來的服務端程式碼會說到
             */
            resCode = root.getString("resCode");
            resMsg = root.optString("resMsg");

            JSONObject property = root.optJSONObject("property");
            if (property != null) {
                parseProperty(property, propertyMap);
            }

            JSONArray list = root.optJSONArray("list");
            if (list != null) {
                parseList(list);
            }
        } catch (JSONException e) {
            e.printStackTrace();
        }

    }

    /**
     * 簡單資訊部分的解析到{@link CommonResponse#propertyMap}
     *
     * @param property  資訊部分
     * @param targetMap 解析後儲存目標
     */
    private void parseProperty(JSONObject property, HashMap<String, String> targetMap) {
        Iterator<?> it = property.keys();
        while (it.hasNext()) {
            String key = it.next().toString();
            Object value = property.opt(key);
            targetMap.put(key, value.toString());
        }
    }

    /**
     * 解析列表部分資訊到{@link CommonResponse#mapList}
     *
     * @param list 列表資訊部分
     */
    private void parseList(JSONArray list) {
        int i = 0;
        while (i < list.length()) {
            HashMap<String, String> map = new HashMap<>();
            try {
                parseProperty(list.getJSONObject(i++), map);
            } catch (JSONException e) {
                e.printStackTrace();
            }
            mapList.add(map);
        }
    }

    public String getResCode() {
        return resCode;
    }

    public String getResMsg() {
        return resMsg;
    }

    public HashMap<String, String> getPropertyMap() {
        return propertyMap;
    }

    public ArrayList<HashMap<String, String>> getDataList() {
        return mapList;
    }
}

        (3)程式碼中出現了2個Handler

        Handler 機制應該都知道,不說了。網路請求過程中會出現各種問題,比如網路不通、報文IO異常、404、500......等等,這是我們需要在UI上報錯,最簡單的做法就是在 BaseActivity 基類中處理這些狀況(待會BaseActivity 中會說明);其實後邊那個Handler根本不是Handler,只是一個介面,用於網路互動成功後回撥進行業務處理,那看一下這個介面:

public interface ResponseHandler {
	
	/**
	 * 交易成功的處理
	 * @param response 格式化報文
	 */
	void success(CommonResponse response);
	
	/**
	 * 報文通訊正常,但交易內容失敗的處理
	 * @param failCode 返回的交易狀態碼
	 * @param failMsg 返回的交易失敗說明
	 */
	void fail(String failCode, String failMsg);
}

        (4)非同步任務回撥方法onPostExecute()中的處理

        其實在之前的例子中我們已經知道:非同步任務 AsyncTask 的回撥 onPostExecute() 可以在UI執行緒中執行,可以在其中操作UI元件。但是我們這裡發現並沒有在 onPostExecute() 方法中操作,而是將報文封裝成通用請求結果 CommonResponse 交給了 ResponseHandler 這個介面來處理,然後在具體的網路請求中來完成success()、fail()這兩個方法。

        好了,下來看BaseActivity的程式碼:

/**
 * 基類
 *
 * Created by WangJie on 2017-03-14.
 */
public class BaseActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

    }

    protected void sendHttpPostRequest(String url, CommonRequest request, ResponseHandler responseHandler, boolean showLoadingDialog) {
        new HttpPostTask(request, mHandler, responseHandler).execute(url);
        if(showLoadingDialog) {
            LoadingDialogUtil.showLoadingDialog(BaseActivity.this);
        }
    }

    protected Handler mHandler = new Handler(){
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);

            if(msg.what == Constant.HANDLER_HTTP_SEND_FAIL) {
                LogUtil.logErr(msg.obj.toString());

                LoadingDialogUtil.cancelLoading();
                DialogUtil.showHintDialog(BaseActivity.this, "請求傳送失敗,請重試", true);
            } else if (msg.what == Constant.HANDLER_HTTP_RECEIVE_FAIL) {
                LogUtil.logErr(msg.obj.toString());

                LoadingDialogUtil.cancelLoading();
                DialogUtil.showHintDialog(BaseActivity.this, "請求接受失敗,請重試", true);
            }
        }
    };
}
        此處只為完成我們的主題,需要建立一個處理網路請求中發生異常時發過來的異常處理Handler;建立一個子類Activity都可以使用的網路請求方法 sendHttpPostRequest(),當然方法設計各人見解不同,此處只做示例。

        別的工具類程式碼就不佔地了,有需要下原始碼看(鄭重宣告,工具類也是示例,效果不代表個人實力大笑)。下邊我們就寫一個子類Activity看一下使用效果:

public class MainActivity extends BaseActivity {

    private String URL_LOGIN = "http://169.254.170.29:8080/MyWorld_Service/LoginServlet";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        final EditText etName = (EditText) findViewById(R.id.et_name);
        final EditText etPassword = (EditText) findViewById(R.id.et_password);

        Button btnLogin = (Button) findViewById(R.id.btn_login);
        btnLogin.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                login(etName.getText().toString(), etPassword.getText().toString());
            }
        });
    }

    private void login(String name, String password) {
        final TextView tvRequest = (TextView) findViewById(R.id.tv_request);
        final TextView tvResponse = (TextView) findViewById(R.id.tv_response);

        final CommonRequest request = new CommonRequest();
        request.addRequestParam("name", name);
        request.addRequestParam("password", password);
        sendHttpPostRequest(URL_LOGIN, request, new ResponseHandler() {
            @Override
            public void success(CommonResponse response) {
                LoadingDialogUtil.cancelLoading();
                tvRequest.setText(request.getJsonStr());
                tvResponse.setText(response.getResCode() + "\n" + response.getResMsg());
                DialogUtil.showHintDialog(MainActivity.this, "登陸成功啦!", false);
            }

            @Override
            public void fail(String failCode, String failMsg) {
                tvRequest.setText(request.getJsonStr());
                tvResponse.setText(failCode + "\n" + failMsg);
                DialogUtil.showHintDialog(MainActivity.this, true, "登陸失敗", failCode + " : " + failMsg, "關閉對話方塊", new View.OnClickListener() {
                    @Override
                    public void onClick(View v) {
                        LoadingDialogUtil.cancelLoading();
                        DialogUtil.dismissDialog();
                    }
                });
            }
        }, true);
    }
}

        看演示:

        示例演示

        嗯,效果還可以看。但是如果你也用之前的 Servlet 來試還是會出問題的,報文可能沒法正確解析,接下來我們看看服務端程式碼變動了哪塊。   

/**
 * Servlet implementation class LoginServlet
 */
@WebServlet(description = "登入", urlPatterns = { "/LoginServlet" })
public class LoginServlet extends HttpServlet {
	private static final long serialVersionUID = 1L;

	/**
	 * @see HttpServlet#HttpServlet()
	 */
	public LoginServlet() {
		super();
	}

	/**
	 * @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse
	 *      response)
	 */
	protected void doGet(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {
		System.out.println("不支援GET方法;");
	}

	/**
	 * @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse
	 *      response)
	 */
	protected void doPost(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {

		BufferedReader read = request.getReader();
		StringBuilder sb = new StringBuilder();
		String line = null;
		while ((line = read.readLine()) != null) {
			sb.append(line);
		}
		String req = sb.toString();
		System.out.println(req);
		
		// 第一步:獲取 客戶端 發來的請求,恢復其Json格式——>需要客戶端發請求時也封裝成Json格式
		JSONObject object = JSONObject.fromObject(req);
		// requestCode暫時用不上
		// 注意下邊用到的2個欄位名稱requestCode、requestParam要和客戶端CommonRequest封裝時候的名字一致
		String requestCode = object.getString("requestCode");
		JSONObject requestParam = object.getJSONObject("requestParam");
		

		// 第二步:將Json轉化為別的資料結構方便使用或者直接使用(此處直接使用),進行業務處理,生成結果
		// 拼接SQL查詢語句
		String sql = String.format("SELECT * FROM %s WHERE account='%s'", 
				DBNames.Table_Account, 
				requestParam.getString("name"));
		System.out.println(sql);

		// 自定義的結果資訊類
		CommonResponse res = new CommonResponse();
		try {
			ResultSet result = DatabaseUtil.query(sql); // 資料庫查詢操作
//			result.getRow();
			
			if (result.next()) {
				if (result.getString("password").equals(requestParam.getString("password"))) {
					res.setResult("0", "登陸成功");
					res.getProperty().put("custId", result.getString("_id"));
				} else {
					res.setResult("100", "登入失敗,登入密碼錯誤");
				}
			} else {
				res.setResult("200", "該登陸賬號未註冊");
			}
		} catch (SQLException e) {
			res.setResult("300", "資料庫查詢錯誤");
			e.printStackTrace();
		}

		// 第三步:將結果封裝成Json格式準備返回給客戶端,但實際網路傳輸時還是傳輸json的字串
		// 和我們之前的String例子一樣,只是Json提供了特定的字串拼接格式
		// 因為服務端JSon是用到經典的第三方JSon包,功能強大,不用像Android中那樣自己手動轉,直接可以從Bean轉到JSon格式
		String resStr = JSONObject.fromObject(res).toString();
		
		System.out.println(resStr);
		response.getWriter().append(resStr).flush();
	}

}
        我們在程式碼中也使用了CommonResponse類,和客戶端的非常像,只是由於服務端引用JSon包功能的強大,所以沒有像客戶端CommonRequest那樣自己手動拼裝JSon,而是直接用json的API轉的,這時就需要CommonResponse的成員的名字和客戶端拆解時的欄位名一致:
public class CommonResponse {

	private String resCode;
	private String resMsg;

	private HashMap<String, String> property;

	private ArrayList<HashMap<String, String>> list;

	public CommonResponse() {
		super();
		resCode = "";
		resMsg = "";
		property = new HashMap<String, String>();
		list = new ArrayList<HashMap<String, String>>();

	}

	public void setResult(String resCode, String resMsg) {
		this.resCode = resCode;
		this.resMsg = resMsg;
	}

	public String getResCode() {
		return resCode;
	}

	public void setResCode(String resCode) {
		this.resCode = resCode;
	}

	public String getResMsg() {
		return resMsg;
	}

	public void setResMsg(String resMsg) {
		this.resMsg = resMsg;
	}

	public HashMap<String, String> getProperty() {
		return property;
	}

	public void addListItem(HashMap<String, String> map) {
		list.add(map);
	}
	
	public ArrayList<HashMap<String, String>> getList() {
		return list;
	}
}
        可以發現Servlet程式碼中CommonRequest我並沒有像Response一樣處理,因為我懶,當然你處理一下更好,此處我只是拋磚引玉做個例子,不必細究偷笑(作為服務端的外行,程式碼優化什麼的先放放哈)。

        好了,就這麼簡單,當然在實際開發中可能遇到比較複雜的需求,可能程式碼要加入更復雜的控制,但是基本的邏輯就是這樣的。需要注意的問題有這麼幾個:

        (1)客戶端和服務端約定報文欄位的名字,不解釋,我叫王三兒,你要喊我王麻子我肯定不答應;

        (2)客戶端和服務端程式碼中都有JSon的使用,但是看起來不大一樣,因為我們使用的Json包不一樣。JSon只是一個數據型別,只是一種手段不是目的,所以不用太糾結於這個,找對API就行了。

        (3)為啥客戶端移動端都要寫 CommonRequest、CommonResponse這兩個類囁?因為它倆相當於入口和出口,門當戶對嘛!客戶端怎麼封裝的請求,到了服務端就要以同樣的方法解開;同理,應答也是如此。

        作為鞏固,再來一個列表類的報文試試。先建這麼一個表:

        示例表內容

        在Servlet中查詢表中所有內容返回給客戶端,ProductServlet.java:

@WebServlet("/ProductServlet")
public class ProductServlet extends HttpServlet {
	private static final long serialVersionUID = 1L;
       
    /**
     * @see HttpServlet#HttpServlet()
     */
    public ProductServlet() {
        super();
    }

	/**
	 * @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response)
	 */
	protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		response.getWriter().append("Served at: ").append(request.getContextPath());
	}

	/**
	 * @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse response)
	 */
	protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		BufferedReader read = request.getReader();
		StringBuilder sb = new StringBuilder();
		String line = null;
		while ((line = read.readLine()) != null) {
			sb.append(line);
		}
		String req = sb.toString();
		System.out.println(req);
		
		String sql = String.format("SELECT * FROM %s", 
				DBNames.Table_Product);
		System.out.println(sql);

		// 自定義的結果資訊類
		CommonResponse res = new CommonResponse();
		try {
			ResultSet result = DatabaseUtil.query(sql); // 資料庫查詢操作
			while (result.next()) {
				HashMap<String, String> map = new HashMap<>();
				map.put("name", result.getString("name"));
				map.put("describe", result.getString("describe"));
				map.put("price", String.valueOf(result.getDouble("price")));
				res.addListItem(map);
			}
			res.setResCode("0"); // 這個不能忘了,表示業務結果正確
		} catch (SQLException e) {
			res.setResult("300", "資料庫查詢錯誤");
			e.printStackTrace();
		}

		String resStr = JSONObject.fromObject(res).toString();
		response.getWriter().append(resStr).flush();
	}

}
        

        在Activity中請求的和報文返回後的處理:

public class ListActivity extends BaseActivity {
    private String URL_PRODUCT = "http://169.254.170.29:8080/MyWorld_Service/ProductServlet";
    ListView lvProduct;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_list);
        lvProduct = (ListView) findViewById(R.id.lv);

        getListData();
    }

    private void getListData() {
        CommonRequest request = new CommonRequest();
        sendHttpPostRequest(URL_PRODUCT, request, new ResponseHandler() {
            @Override
            public void success(CommonResponse response) {
                LoadingDialogUtil.cancelLoading();

                if (response.getDataList().size() > 0) {
                    ProductAdapter adapter = new ProductAdapter(ListActivity.this, response.getDataList());
                    lvProduct.setAdapter(adapter);
                } else {
                    DialogUtil.showHintDialog(ListActivity.this, "列表資料為空", true);
                }
            }

            @Override
            public void fail(String failCode, String failMsg) {
                LoadingDialogUtil.cancelLoading();
            }
        }, true);
    }

    static class ProductAdapter extends BaseAdapter {
        private Context context;
        private ArrayList<HashMap<String, String>> list;

        public ProductAdapter(Context context, ArrayList<HashMap<String, String>> list) {
            this.context = context;
            this.list = list;
        }

        @Override
        public int getCount() {
            return list.size();
        }

        @Override
        public Object getItem(int position) {
            return list.get(position);
        }

        @Override
        public long getItemId(int position) {
            return position;
        }

        @Override
        public View getView(int position, View convertView, ViewGroup parent) {
            ViewHolder holder;
            if (convertView == null) {
                convertView = LayoutInflater.from(context).inflate(R.layout.item_product, parent, false);
                holder = new ViewHolder();
                holder.tvName = (TextView) convertView.findViewById(R.id.tv_name);
                holder.tvDescribe = (TextView) convertView.findViewById(R.id.tv_describe);
                holder.tvPrice = (TextView) convertView.findViewById(R.id.tv_price);

                convertView.setTag(holder);
            } else {
                holder = (ViewHolder) convertView.getTag();
            }

            HashMap<String, String> map = list.get(position);
            holder.tvName.setText(map.get("name"));
            holder.tvDescribe.setText(map.get("describe"));
            holder.tvPrice.setText(map.get("price"));

            return convertView;
        }

        private static class ViewHolder {
            private TextView tvName;
            private TextView tvDescribe;
            private TextView tvPrice;
        }
    }
}
        來來來,不多解釋,就是取返回結果中的列表資料拿來放到 ListView 中,看效果:

        列表示例

        好了,終於完了,應該沒什麼錯吧,歡迎指正,先行謝過!