1. 程式人生 > >Android通過Http連線MySQL 實現登陸/註冊(資料庫+伺服器+客戶端),android+mysql

Android通過Http連線MySQL 實現登陸/註冊(資料庫+伺服器+客戶端),android+mysql

寫在最前:

  在實際開發中,相信每個專案都會有使用者登陸註冊功能,這個實現的方法很多,下面是我實現的方法,供大家交流。

  新人發帖,萬分緊張,怎麼樣才能裝作一副經常發帖的樣子不被別人看出來呢-,- ?

  好了,下面進入正題。

一、開發環境的部署

  程式結構:

    android+servlet+service+mysql

  僅供參考:能實現相關功能即可

    作業系統:ubuntu 14.10

    資料庫:mysql-5.5    資料庫工具:emma

    伺服器:tomcat      伺服器工具:Myeclipse 10

    安卓端:真機 android4.4  安卓端工具:eclipse+adt

  注意:

    程式除錯過程可能會產生亂碼,只需保持所有工具編碼方式相同即可。

二、資料庫設計

  資料庫名稱:test

  表名稱:student

  建表語句:

CREATE TABLE `student` (
  `Id` int(11) NOT NULL AUTO_INCREMENT,
  `username` char(20) NOT NULL DEFAULT '',
  `password` char(20) NOT NULL DEFAULT '',
  PRIMARY KEY (`Id`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT
CHARSET=utf8

  表格檢視:

  

三、伺服器端設計

  1、新建Web Project,命名為HelloWeb,同時刪除掉WebRoot的index.jsp

  2、專案結構圖如下:

    這裡我們採用servlet程式設計,所以不需要任何jsp頁面。

    LogLet類和RegLet類分別用於處理客戶端的登陸和註冊請求;Service類用於完成servlet對資料庫的具體操作;DBManager類用於進行資料庫基本操作;

    左側是專案圖,右側是web.xml配置檔案截圖。

  

  3、專案程式碼:

    DBManager.java

      <1> 私有化DBManager的建構函式,定義一個靜態的成員變數,在一個共有方法中例項化該成員變數。若要例項化物件呼叫此方法即可。

        同一時間該類只能存在一個物件。符合sql物件習慣。 (此方式有缺陷,具體自行搜尋)       

<2> 定義資料庫連線、關閉以及增刪改查的基本操作,返回結果集。

package com.db;

import java.sql.*;

public class DBManager {

    // 資料庫連線常量
    public static final String DRIVER = "com.mysql.jdbc.Driver";
    public static final String USER = "root";
    public static final String PASS = "root";
    public static final String URL = "jdbc:mysql://localhost:3306/test";

    // 靜態成員,支援單態模式
    private static DBManager per = null;
    private Connection conn = null;
    private Statement stmt = null;

    // 單態模式-懶漢模式
    private DBManager() {
    }

    public static DBManager createInstance() {
        if (per == null) {
            per = new DBManager();
            per.initDB();
        }
        return per;
    }

    // 載入驅動
    public void initDB() {
        try {
            Class.forName("com.mysql.jdbc.Driver");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    // 連線資料庫,獲取控制代碼+物件
    public void connectDB() {
        System.out.println("Connecting to database...");
        try {
            conn = DriverManager.getConnection(URL, USER, PASS);
            stmt = conn.createStatement();
        } catch (SQLException e) {
            e.printStackTrace();
        }
        System.out.println("SqlManager:Connect to database successful.");
    }

    // 關閉資料庫 關閉物件,釋放控制代碼
    public void closeDB() {
        System.out.println("Close connection to database..");
        try {
            stmt.close();
            conn.close();
        } catch (SQLException e) {
            e.printStackTrace();
        }
        System.out.println("Close connection successful");
    }

    // 查詢
    public ResultSet executeQuery(String sql) {
        ResultSet rs = null;
        try {
            rs = stmt.executeQuery(sql);
        } catch (SQLException e) {
            e.printStackTrace();
        }
        return rs;
    }

    // 增添/刪除/修改
    public int executeUpdate(String sql) {
        int ret = 0;
        try {
            ret = stmt.executeUpdate(sql);
        } catch (SQLException e) {
            e.printStackTrace();
        }
        return ret;
    }
}

    Service.java

      這個簡單,根據傳參得到sql語句,通過DBManager類的 createInstance() 方法例項化物件,呼叫本類的操作方法,完成資料操作。

      寫到這裡,可以預見:下一個類會通過呼叫本類方法完成登陸/註冊的服務。

package com.service;

import java.sql.ResultSet;
import java.sql.SQLException;

import com.db.DBManager;

public class Service {

    public Boolean login(String username, String password) {

        // 獲取Sql查詢語句
        String logSql = "select * from user where username ='" + username
                + "' and password ='" + password + "'";

        // 獲取DB物件
        DBManager sql = DBManager.createInstance();
        sql.connectDB();

        // 操作DB物件
        try {
            ResultSet rs = sql.executeQuery(logSql);
            if (rs.next()) {
                sql.closeDB();
                return true;
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }
        sql.closeDB();
        return false;
    }

    public Boolean register(String username, String password) {
    
        // 獲取Sql查詢語句
        String regSql = "insert into student values('"+ username+ "','"+ password+ "') ";
// 獲取DB物件 DBManager sql = DBManager.createInstance(); sql.connectDB(); int ret = sql.executeUpdate(regSql); if (ret != 0) { sql.closeDB(); return true; } sql.closeDB(); return false; } }

    LogLet.java

      一個簡單的Servlet,用於處理Http請求(get/post)。果然,例項化上一個類的物件,並呼叫了login方法,返回值為布林型別。

      RegLet.java和該類近乎相同,只是在 serv.login(username, password); 換成了 serv.register(username, password);此處省去~

package com.servlet;

import java.io.IOException;
import java.io.PrintWriter;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import com.service.Service;

public class LogLet extends HttpServlet {

    private static final long serialVersionUID = 369840050351775312L;

    public void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {

        // 接收客戶端資訊
        String username = request.getParameter("username");
        username = new String(username.getBytes("ISO-8859-1"), "UTF-8");
        String password = request.getParameter("password");
        System.out.println(username + "--" + password);

        // 新建服務物件
        Service serv = new Service();

        // 驗證處理
        boolean loged = serv.login(username, password);
        if (loged) {
            System.out.print("Succss");
            request.getSession().setAttribute("username", username);
            // response.sendRedirect("welcome.jsp");
        } else {
            System.out.print("Failed");
        }

        // 返回資訊到客戶端
        response.setCharacterEncoding("UTF-8");
        response.setContentType("text/html");
        PrintWriter out = response.getWriter();
        out.print("使用者名稱:" + username);
        out.print("密碼:" + password);
        out.flush();
        out.close();

    }


    public void doPost(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {

    }

}

四、客戶端設計

  1、新建Android App Project,命名為AndroidHTTPDemo

  2、現在開始思考需要什麼東西...

     <1> 登陸和註冊頁面:佈局檔案

        login.xml , register.xml

     <2> 登陸和註冊頁面對應的Activity元件,在activity中進行具體操作

        login.java , register.java

     <3> 能夠實現Http以get/post方式通訊的類

        WebService.java , WebServicePost.java

     <4> 網路通訊許可權

        <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
       <uses-permission android:name="android.permission.INTERNET" />

    OK,專案結構出爐,右側是Manifeast配置檔案的主要內容

  3、現在,我們開始關注具體的程式碼。

    <1> 首先要做的,登陸註冊介面,這個不用多說。我直接放圖,大致就是下面這個樣子,大家可以按照自己愛好設計。

    (注意一點,因為登陸和註冊的xml、activity都是近乎完全一樣,最不一樣的sql語句我們在之前已經處理過了,所以這裡只寫其中的一個即可)

  

    <2> 在伺服器端程式設計時我們瞭解到:伺服器端接收客戶端傳送的資訊,對資訊進行一系列處理後,最終資訊返回到客戶端。

      首先要想的,就是獲取資訊併發送出去,然後接收資訊並顯示出來。

      獲取資訊好辦,getText()嘛,不好辦的是傳送,還有傳送所需的執行緒。

      (網路服務由於耗時問題,放在主執行緒很可能由於網路故障導致ANR;所以要開闢子執行緒留給http網路服務。當然不使用主執行緒也可以,只是不推薦)

    <3> Login.java 有三點需要注意

      第一個是檢測網路狀態,只能檢測流量,無法檢測wifi;

      第二個是在子執行緒中,我們利用獲得的使用者名稱密碼呼叫了http通訊類最後返回的info值,不能直接在子執行緒中更改主執行緒的頁面值,這裡用了handle解決。

      第三個是這裡有get/post兩種http請求方式,兩個實現類,我們需要那一個,只要把另一個註釋掉即可,返回的資料都是一樣的。

package com.httpdemo;

import com.rxz.androidhttpdemo.R;
import com.web.WebService;
import com.web.WebServicePost;

import android.app.Activity;
import android.app.ProgressDialog;
import android.content.Context;
import android.content.Intent;
import android.net.ConnectivityManager;
import android.os.Bundle;
import android.os.Handler;
import android.view.Gravity;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
import android.widget.Toast;

public class Login extends Activity implements OnClickListener {

    // 登陸按鈕
    private Button logbtn;
    // 除錯文字,註冊文字
    private TextView infotv, regtv;
    // 顯示使用者名稱和密碼
    EditText username, password;
    // 建立等待框
    private ProgressDialog dialog;
    // 返回的資料
    private String info;
    // 返回主執行緒更新資料
    private static Handler handler = new Handler();

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

        // 獲取控制元件
        username = (EditText) findViewById(R.id.user);
        password = (EditText) findViewById(R.id.pass);
        logbtn = (Button) findViewById(R.id.login);
        regtv = (TextView) findViewById(R.id.register);
        infotv = (TextView) findViewById(R.id.info);

        // 設定按鈕監聽器
        logbtn.setOnClickListener(this);
        regtv.setOnClickListener(this);

    }

    @Override
    public void onClick(View v) {
        switch (v.getId()) {
        case R.id.login:
            // 檢測網路,無法檢測wifi
            if (!checkNetwork()) {
                Toast toast = Toast.makeText(Login.this,"網路未連線", Toast.LENGTH_SHORT);
                toast.setGravity(Gravity.CENTER, 0, 0);
                toast.show();
                break;
            }
            // 提示框
            dialog = new ProgressDialog(this);
            dialog.setTitle("提示");
            dialog.setMessage("正在登陸,請稍後...");
            dialog.setCancelable(false);
            dialog.show();
            // 建立子執行緒,分別進行Get和Post傳輸
            new Thread(new MyThread()).start();
            break;
        case R.id.register:
            Intent regItn = new Intent(Login.this, Register.class);
            // overridePendingTransition(anim_enter);
            startActivity(regItn);
            break;
        }
        ;
    }

    // 子執行緒接收資料,主執行緒修改資料
    public class MyThread implements Runnable {
        @Override
        public void run() {
            info = WebService.executeHttpGet(username.getText().toString(), password.getText().toString());
            // info = WebServicePost.executeHttpPost(username.getText().toString(), password.getText().toString());
            handler.post(new Runnable() {
                @Override
                public void run() {
                    infotv.setText(info);
                    dialog.dismiss();
                }
            });
        }
    }

    // 檢測網路
    private boolean checkNetwork() {
        ConnectivityManager connManager = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);
        if (connManager.getActiveNetworkInfo() != null) {
            return connManager.getActiveNetworkInfo().isAvailable();
        }
        return false;
    }

}

    <4> WebService.java   

      這裡的IP是你的伺服器IP,不確定時看下是否能用手機ping工具ping通。

      因為我用的是真機,所以模擬器我還真不太清楚,我簡單說一下真機與windows/linux下的伺服器網路連線流程,詳情請百度。

      ① 你的伺服器端程式已釋出到網際網路:這好辦,就是你的IP地址。

      ② 你是在本地電腦上,這要求你的真機和你的電腦在同一個區域網。兩種較方便的方法:路由器/筆記本的無線網絡卡

       是個人都能看出來第二種方便,誰也不能到哪都帶個路由器吧,那麼好,筆記本開啟無線熱點,手機wifi連線熱點,這是客戶端和伺服器就在一個區域網內。

       檢視筆記本ip地址中的無線網絡卡地址([win]ipconfig/[lnx]ifconfig -- wlan),加上你的伺服器埠號(伺服器為開啟狀態),訪問即可。

       conn.setConnectTimeout(3000);需要設定超時時間,否則會執行預設超時時間,30s ?

      接收到的輸入流需要先轉換成位元位,在轉換成string型別。

package com.web;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;

public class WebService {

    private static String IP = "10.42.0.1:8080";

    // 通過Get方式獲取HTTP伺服器資料
    public static String executeHttpGet(String username, String password) {

        HttpURLConnection conn = null;
        InputStream is = null;

        try {
            // 使用者名稱 密碼
            // URL 地址
            String path = "http://" + IP + "/HelloWeb/servlet/MyServlet";
            path = path + "?username=" + username + "&password=" + password;

            conn = (HttpURLConnection) new URL(path).openConnection();
            conn.setConnectTimeout(3000); // 設定超時時間
            conn.setReadTimeout(3000);
            conn.setDoInput(true);
            conn.setRequestMethod("GET"); // 設定獲取資訊方式
            conn.setRequestProperty("Charset", "UTF-8"); // 設定接收資料編碼格式

            if (conn.getResponseCode() == 200) {
                is = conn.getInputStream();
                return parseInfo(is);
            }

        }catch (Exception e) {
            e.printStackTrace();
        } finally {
            // 意外退出時進行連線關閉保護
            if (conn != null) {
                conn.disconnect();
            }
            if (is != null) {
                try {
                    is.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }

        }
        return null;
    }

    // 將輸入流轉化為 String 型 
    private static String parseInfo(InputStream inStream) throws Exception {
        byte[] data = read(inStream);
        // 轉化為字串
        return new String(data, "UTF-8");
    }

    // 將輸入流轉化為byte型 
    public static byte[] read(InputStream inStream) throws Exception {
        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
        byte[] buffer = new byte[1024];
        int len = 0;
        while ((len = inStream.read(buffer)) != -1) {
            outputStream.write(buffer, 0, len);
        }
        inStream.close();
        return outputStream.toByteArray();
    }
}

    <5> WebServicePost.java 和上一個大同小異,只不過引數不是放在url中,而是在HashMap中傳輸,資料傳輸方式略有不同。

      處理方式不變,還有注意別忘了設定超時。

package com.web;

import java.io.InputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.apache.http.HttpEntity;
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.params.CoreConnectionPNames;

public class WebServicePost {

    private static String IP = "10.42.0.1:8080";

    // 通過 POST 方式獲取HTTP伺服器資料
    public static String executeHttpPost(String username, String password) {

        try {
            String path = "http://" + IP + "/HelloWeb/servlet/MyServlet";

            // 傳送指令和資訊
            Map<String, String> params = new HashMap<String, String>();
            params.put("username", username);
            params.put("password", password);

            return sendPOSTRequest(path, params, "UTF-8");
        } catch (Exception e) {
            e.printStackTrace();
        }

        return null;
    }

    // 處理髮送資料請求
    private static String sendPOSTRequest(String path, Map<String, String> params, String encoding) throws Exception {

        List<NameValuePair> pairs = new ArrayList<NameValuePair>();
        if (params != null && !params.isEmpty()) {
            for (Map.Entry<String, String> entry : params.entrySet()) {
                pairs.add(new BasicNameValuePair(entry.getKey(), entry.getValue()));
            }
        }

        UrlEncodedFormEntity entity = new UrlEncodedFormEntity(pairs, encoding);

        HttpPost post = new HttpPost(path);
        post.setEntity(entity);
        DefaultHttpClient client = new DefaultHttpClient();
        // 請求超時
        client.getParams().setParameter(CoreConnectionPNames.CONNECTION_TIMEOUT, 5000);
        // 讀取超時
        client.getParams().setParameter(CoreConnectionPNames.SO_TIMEOUT, 5000);
        HttpResponse response = client.execute(post);

        // 判斷是否成功收取資訊
        if (response.getStatusLine().getStatusCode() == 200) {
            return getInfo(response);
        }

        // 未成功收取資訊,返回空指標
        return null;
    }

    // 收取資料
    private static String getInfo(HttpResponse response) throws Exception {

        HttpEntity entity = response.getEntity();
        InputStream is = entity.getContent();
        // 將輸入流轉化為byte型
        byte[] data = WebService.read(is);
        // 轉化為字串
        return new String(data, "UTF-8");
    }
}

五、執行效果

  以上工作完成後,只需要講伺服器端釋出到本地(附上--mysql-jdbc驅動地址--),安卓端釋出到手機,確保區域網內部,ip正確,即可正常訪問。

  客戶端截圖:測試成功

           

  伺服器端截圖:測試成功

  

六、最後

以上不足之處,還望大家多多指正。如有問題歡迎給我留言。

程式碼並未涉及到Session保持,自動登陸等,正在改進中,最終效果應該類似於虎牙直播的登陸註冊(剛好舉個例子