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 DEFAULTCHARSET=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保持,自動登陸等,正在改進中,最終效果應該類似於虎牙直播的登陸註冊(剛好舉個例子
)