由于学院需要提交一个学年论文,正好借此机会开始我的博客生活,我会在随后的日子在里不断更新博客。
内容摘要:随着移动互联网技术的的发展以及智能手持终端的普及,实现远程数据的通信成为了智能应用的关键。目前智能终端中大部分采用了Google Android操作系统,因此,我在暑期重点研究了Android远程数据库通信的实现,并实现了一套可行的实现方案。
一、问题的引入
Android在数据存储方面提供了四种方式,其中包括轻量级的Sqlite3数据库。对于单机游戏以及简单应用,这几种存储方式已经能够解决。但是,随着移动互联网技术的日臻成熟以及用户对于移动网络数据的巨大需求,单单采用本地存储数据已经很难满足用户的需求。因此,Android连接远程数据库交互数据已经成为了很多应用、手机游戏必不可少的部分。
Android操作系统在早期已经对Socket、Http网络有了很好的支持,因此,在Android应用中远程通信是很好实现的。对于访问远程数据库,Google官方SDK文档说是支持的。但是,在实际开发中,若果采用直接访问远程数据库时会出现很多莫名奇妙的问题。对于这个问题而言,我的理解是:Android实质是在Linux上搭建了一个符合Java标准的虚拟机(官方称为Dalvik)。Dalvik是一个针对移动终端权衡性能与功耗优化的Java虚拟机,由于受限于移动终端自身硬件限制,Dalvik必然是做过很多精简的,可以说是“阉割”版的Java虚拟机。因此,Android对于直连远程数据库必然是不能够像PC一样完美支持给中数据库的。
既然远程直连数据库从在各种问题,我们只能通过迂回路线完成远程数据库通信了。
二、问题的解决方案
既然直连远程数据有问题,要解决,必然是在中间加一种中间层。我设计的解决方案是在远程加一个中间层,具体如下:
在远程数据库端部署一个服务端,服务端作为中间层,一方面通过jdbc连接数据库,另一方面,服务端通过Socket将查询到的结果反馈给Android终端。以下图说明:
三、解决方案的细化
基础的解决方案已经有了,我们要做的就是具体细化方案。
1、Android终端与服务端的通信及数据
Android终端通过Socket与远程服务端进行连接。Android端通过字节流发送SQL查询指令,服务端数据传输采用JSON格式的字节流返回查询结果。
2、服务端与远程数据库
服务端和远程数据库部署在同一服务器,因此二者只需通过jdbc连接就可以了。
四、解决方案的实现
1、服务端的编写
服务端包括两部分,一部分与数据库连接,另一部分是远程通信。
1.1 服务端与数据库连接,可以将其封装成为一个类。如下:
public class ConectDB {
public Connection conn;
public String strSql;
public Statement sqlStmt;//语句对象不带参数
public PreparedStatement preparedStmt;//语句对象带参数
public ResultSet sqlRst; //结果对象集
private static ConectDB mysql;
/**
* 构造函数
*/
private ConectDB()
{
try {
conn = null;
sqlStmt = null;
sqlRst = null;
preparedStmt = null;
Connect();
} catch (SQLException ex) {
Logger.getLogger(ConectDB.class.getName()).log(Level.SEVERE, null, ex);
} catch (ClassNotFoundException ex) {
Logger.getLogger(ConectDB.class.getName()).log(Level.SEVERE, null, ex);
} catch (InstantiationException ex) {
Logger.getLogger(ConectDB.class.getName()).log(Level.SEVERE, null, ex);
} catch (IllegalAccessException ex) {
Logger.getLogger(ConectDB.class.getName()).log(Level.SEVERE, null, ex);
}
}
public static ConectDB getConetDB()//单例模式
{
if(mysql==null)
{
mysql=new ConectDB();
}
return mysql;
}
Public void Connect() throws SQLException, ClassNotFoundException,InstantiationException, IllegalAccessException{
Stringurl="jdbc:mysql://localhost/projectmanagement?characterEncoding=utf-8";
String user="root";
String pwd="root";
//加载驱动,这一句也可写为:Class.forName("com.mysql.jdbc.Driver");
Class.forName("com.mysql.jdbc.Driver").newInstance();
//建立到MySQL的连接
conn = java.sql.DriverManager.getConnection(url,user,pwd);
}
}
其中将ConectDB的构造函数设置为私有类型,在获得ConectDB的对象时需要调用ConectDB的静态函数getConetDB()来获得,采用这样的单例模式的好处在于永远保持与数据库的连接保持只有一个,避免多个客户端访问时引起数据库连接饱和,致使数据库崩溃。
1.2 服务端远程通信
服务端远程通信采用Socket。思路如下:
首先创建一个ServerSocket的对象mServerSocket,之后执行一个死循环,循环中通过调用mServerSocket.accept()进行阻塞。在有应用请求Socket时,accept的阻塞被解除,开启一个线程来单独处理该应用Socket请求。这时,循环又执行到了accept函数,主线程阻塞。子线程通过Socket的InputStream获得到应用传来的SQL请求,解析后调用ConectDB类中封装好的相关函数向数据库请求执行SQL,SQL被正确执行后会返回ResultSetMetaData类型的结果。为了传输得到结果,需要将ResultSetMetaData类型结果转换为JsonArray类型。最后通过JsonArray.toString().getByte(“UTF-8”)得到字节流,通过OutputStream发送给应用。代码实现如下:
package com.imudges.server;
import androidservice.AndroidService;
import com.imudges.database.ConectDB;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.json.JSONException;
import org.json.JSONObject;
/**
*
* @author CSYYJ
*/
public class Server extends Thread {
private static final int SERVERPORT = 5432;
private static List<Socket> mClientList = new ArrayList<Socket>();
private ExecutorService mExecutorService;//线程池
private ServerSocket mServerSocket;//ServerSocket对象
private ConectDB mysql = null;
public Server() {
try {
mServerSocket = new ServerSocket(SERVERPORT);
mExecutorService = Executors.newCachedThreadPool();//创建一个线程池
System.out.println("start......");
Socket client = null;
while (true) {
client = mServerSocket.accept();
System.out.println("接收到请求!");
mClientList.add(client);
mExecutorService.execute(new ThreadServer(client));
}
} catch (IOException ex) {
Logger.getLogger(Server.class.getName()).log(Level.SEVERE, null, ex);
}
}
public class ThreadServer implements Runnable {
private Socket mSocket;
private BufferedReader mBufferedReader;
private PrintWriter mPrintWriter;
private String mStrMSG;
private InputStream in;
private OutputStream out;
public ThreadServer(Socket socket) throws IOException {
this.mSocket = socket;
in = socket.getInputStream();
mBufferedReader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
out = mSocket.getOutputStream();
mStrMSG = "user:" + this.mSocket.getInetAddress() + " come total:" + mClientList.size();
}
@Override
public void run() {
while (true) {
System.out.println("进入run函数等待数据通信!");
byte buffer[] = new byte[128];
int len = 0;
mStrMSG = "";
try {
while ((len = in.read(buffer)) == 128) {
mStrMSG += new String(buffer, 0, len);
}
mStrMSG += new String(buffer, 0, len);
} catch (IOException ex) {
Logger.getLogger(Server.class.getName()).log(Level.SEVERE, null, ex);
}
mysql = new ConectDB();
mysql.strSql = mStrMSG;
try {
mysql.preparedStmt = mysql.conn.prepareStatement(mysql.strSql);
} catch (SQLException ex) {
Logger.getLogger(AndroidService.class.getName()).log(Level.SEVERE, null, ex);
}
try {
mysql.sqlRst = (ResultSet) mysql.preparedStmt.executeQuery();
} catch (SQLException ex) {
Logger.getLogger(Server.class.getName()).log(Level.SEVERE, null, ex);
}
try {
try {
sendMessage(mSocket, mysql.sqlRst);
} catch (JSONException ex) {
Logger.getLogger(Server.class.getName()).log(Level.SEVERE, null, ex);
}
} catch (IOException ex) {
Logger.getLogger(Server.class.getName()).log(Level.SEVERE, null, ex);
}
System.out.println(mStrMSG);
}
}
private void sendMessage(Socket socket, ResultSet rs) throws IOException, JSONException {
try {
// json数组
MyJsonArray array = new MyJsonArray();
// 获取列数
ResultSetMetaData metaData = rs.getMetaData();
int columnCount = metaData.getColumnCount();
// 遍历ResultSet中的每条数据
while (rs.next()) {
JSONObject jsonObj = new JSONObject();
// 遍历每一列
for (int i = 1; i <= columnCount; i++) {
String columnName = metaData.getColumnLabel(i);
String value = rs.getString(columnName);
jsonObj.put(columnName, value);
}
array.put(jsonObj);
}
System.out.println(array.toString());
out.write(array.toString().getBytes("UTF-8"));
out.flush();
} catch (SQLException ex) {
Logger.getLogger(Server.class.getName()).log(Level.SEVERE, null, ex);
}
}
}
}
2、Android终端的实现
基于实现以上之后,Android端剩余的任务只剩下了通信。Android中Socket的实现了和Java标准是相同的。
首先Android连接socket,mSocket=new Socket(SERVERIP,SERVERPOST),然后获取socket的输入输出流,要注意输入输出流需要统一编码方式,我采用的是“UTF-8”。在获取到数据库输入输出流之后,即可通过输出流向远程服务端发送sql指令,发送完指令之后,通过输入流获取到返回的SQL执行结果。当然在接收时也通过JasonArray接受,然后在解析即可。
代码实现如下:
package com.imudges.socket;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.io.UnsupportedEncodingException;
import java.net.ContentHandler;
import java.net.Socket;
import java.net.UnknownHostException;
import org.json.JSONArray;
import org.json.JSONException;
import com.imudges.tools.ConectIPs;
import com.yangyu.mytitlebar02.R;
import android.content.res.Resources;
import android.os.Handler;
import android.os.Message;
import android.util.Log;
public class ConnectSQL
{
public static ConnectSQL connectSQL=null;
public static final String SERVERIP =ConectIPs.SERVERIP;
private static final int SERVERPOST = ConectIPs.SERVERPOST;
private static Thread mThread = null;
public static Socket mSocket = null;
private static JSONArray array=null;
private OutputStream outputStream=null;
private static Handler mHandler=null;
private InputStream inputStream =null;
boolean isReturns=false;
private ConnectSQL() throws IOException, Exception
{
mSocket = new Socket(SERVERIP, SERVERPOST);
outputStream=mSocket.getOutputStream();
inputStream = mSocket.getInputStream();
}
public static ConnectSQL getConnectSQL() throws IOException, Exception
{
connectSQL=new ConnectSQL();
return connectSQL;
}
private static boolean IsConnected ()
{
boolean flag=true;
try
{
mSocket.sendUrgentData(0xFF);
System.out.println("socket連接成功");
}
catch (Exception e)
{
flag=false;
System.out.println("socket連接斷開");
}
return flag;
}
public JSONArray getJSONArry(String sql) throws IOException, Exception
{
if (mSocket == null)
{
connectSQL=new ConnectSQL();
}
outputStream.write(sql.getBytes("UTF-8"));
outputStream.flush();
byte buffer[] = new byte[1024];
int len = 0;
String mStrMSG = "";
try
{
while ((len = inputStream.read(buffer)) == 1024)
{
mStrMSG += new String(buffer, 0, len,"UTF-8");
}
System.out.println("len:"+len);
mStrMSG += new String(buffer, 0, len,"UTF-8");
Log.v("debug", "lebgth:"+mStrMSG.length());
System.out.println("获得到的str:"+mStrMSG);
} catch (UnsupportedEncodingException e1)
{
// TODO Auto-generated catch block
e1.printStackTrace();
} catch (IOException e1)
{
// TODO Auto-generated catch block
e1.printStackTrace();
}
try
{
array=new JSONArray(mStrMSG);
} catch (JSONException e)
{
e.printStackTrace();
}
System.out.println("array:"+array.toString());
return array;
}
public void close()
{
try
{
mSocket.close();
}
catch (IOException e)
{
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}