最近一直在学习安卓怎么样访问服务端数据,所以自己就做了一个安卓客户端实现访问SSH架构服务端实现登录,并使安卓客户端保持sessionid时刻处于登录状态,在登陆状态安卓能够访问服务端数据而不被拦截成功获取服务端数据(注:大家都知道web服务端为了辨别用户身份和安全考虑,session为空是不能访问后台数据的)。
下面先看安卓客户端的项目结构:
安卓客户端界面效果图(因为这是个人测试用,所以页面没有做太多设计,只是简单实现了功能):
下面开始代码(这个不多做解释了):
<span style="font-size:14px;">package com.zk.util;
import com.zk.domain.User;
public class Cache {
private User user;
private Cache(){}
//** 构造单例 *//
private static class CacheHolder{
private static final Cache INSTANCE = new Cache();
}
public Cache getInstance(){
return CacheHolder.INSTANCE;
}
public User getUser() {
return user;
}
public void setUser(User user) {
this.user = user;
}
}</span>
实现登录代码如下:
注:这里边有几个要点:
1、 因为安卓客户端的界面都是由主线程控制,但是一些耗时的操作是不能放在主线程中的,例如:这里的访问网络操作,还有像实现文件的上传下载等操作。
2、就是代码的层次结构问题,为了让代码更好的解耦,使代码能够更好的复用,我们在写代码的过程中也要用到类似java web中的MVC思想。
<span style="font-size:14px;">package com.zk.sshclinet;
import java.io.Serializable;
import org.json.JSONException;
import org.json.JSONObject;
import com.zk.domain.User;
import com.zk.protocol.LoginProtocol;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.R.integer;
import android.app.Activity;
import android.content.Intent;
import android.view.Menu;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
public class MainActivity extends Activity {
private final static int ERROR_INFO=1;
private final static int ERROR_NULL=2;
private final static int ERROR_LOGIN=3;
private final static int SUCCESS=4;
private EditText edName,edPwd;
private Button loginbtn;
private TextView errorInfo;
private User user;
String userName,pwd;
JSONObject obj;
private LoginProtocol loginProtocol;
private Handler handler = new Handler(){
public void handleMessage(Message msg){
switch (msg.what) {
case ERROR_NULL:
errorInfo.setText("用户名或密码不能为空!");
break;
case ERROR_LOGIN:
errorInfo.setText("用户名或密码错误!");
break;
case SUCCESS:
Intent intent = new Intent(MainActivity.this,TabShow.class);
intent.putExtra("session", obj.toString());
startActivity(intent);
break;
default:
break;
}
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initResourceRefs();
initSetting();
}
public void initResourceRefs(){
edName=(EditText) findViewById(R.id.name);
edPwd=(EditText) findViewById(R.id.pwd);
loginbtn=(Button) findViewById(R.id.login);
errorInfo=(TextView) findViewById(R.id.errorInfo);
loginProtocol = new LoginProtocol();
user = new User();
}
public void initSetting(){
loginbtn.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View arg0) {
new Thread(){
public void run(){
Message msg = new Message();
if(edName.getText().equals("") || edPwd.getText().equals("")){
msg.what=ERROR_NULL;
}else{
user.setUsername(edName.getText().toString());
user.setPassword(edPwd.getText().toString());
// System.out.println(edName.getText().toString());
try {
obj = loginProtocol.checkLogin(user);
if(obj.get("msg").equals("true")){
msg.what=SUCCESS;
}else{
msg.what=ERROR_LOGIN;
}
} catch (JSONException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
handler.sendMessage(msg);
}
}.start();
}
});
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.main, menu);
return true;
}
}</span>
下面介绍工具类:
注:1、pack(url)此函数只在登录时链接服务端并把用户名和密码传到服务端;
2、linkSer(url,sessionid)此函数是在用户登陆后持有sessionid,把其他数据和sessionid一块传到服务端,服务端通过sessionid确定用户身份和登录状态,只有用户sessionid不为空且未超时的时候才能访问服务端数据否则会被拦截。这里边的JSESSIONID是SSH框架中的sessionid名称,其他框架的要替换成与之相应的名称。
3、destroy();函数存在的必要性。在我开发测试的过程中遇到过这样的问题,当安卓客户端第一次未登录成功,修改用户名或密码后再次点击登录的时候,在ssh服务端控制台打印出从客户端传过来的用户名和密码,发现还是上一次输入的错误的用户名和密码,并没有 把你第二次输入的用户名和密码传过来,所以这里就要在每次客户端向服务端传参数的时候做一下清空才做。
<span style="font-size:14px;">package com.zk.protocol;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.util.ArrayList;
import java.util.List;
import org.apache.http.HttpResponse;
import org.apache.http.NameValuePair;
import org.apache.http.client.HttpClient;
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 org.json.JSONException;
import org.json.JSONObject;
import com.zk.domain.User;
public class BaseProtocol {
StringBuilder sb = new StringBuilder();
String data;
private HttpClient httpClient;
private HttpPost httpPost;
private HttpResponse httpResponse;
private List<NameValuePair> naPairs = new ArrayList<NameValuePair>();
public BaseProtocol() {
httpClient = new DefaultHttpClient();
}
/**
* 向服务端发送请求
* @param url
* @throws Exception
*/
protected void pack(String url)throws Exception {
httpClient = new DefaultHttpClient();
httpPost = new HttpPost(url);
httpPost.setEntity(new UrlEncodedFormEntity(naPairs,HTTP.UTF_8));
httpResponse=httpClient.execute(httpPost);
}
/**
* 向服务端发送请求并传送sessinID
* @param url
* @param sessionid
* @throws Exception
*/
protected void linkSer(String url,String sessionid)throws Exception {
httpClient = new DefaultHttpClient();
httpPost = new HttpPost(url);
httpPost.setHeader("Cookie","JSESSIONID=" + sessionid);
httpPost.setEntity(new UrlEncodedFormEntity(naPairs,HTTP.UTF_8));
httpResponse=httpClient.execute(httpPost);
}
/**
* 得到服务端返回数据
* @throws Exception
*/
protected void parse()throws Exception{
if(httpResponse.getStatusLine().getStatusCode()==200){
//data=EntityUtils.toString(httpResponse.getEntity(),"UTF-8");
BufferedReader bufferedReader2 = new BufferedReader(
new InputStreamReader(httpResponse.getEntity().getContent()));
for (String s = bufferedReader2.readLine(); s != null; s = bufferedReader2
.readLine()) {
sb.append(s);
}
}
}
/**
* 向服务端发送的信息
* @param key
* @param value
*/
public void addNameValuePair(String key,String value){
naPairs.add(new BasicNameValuePair(key, value));
}
/**
* 清空原有数据避免在服务端获取到老数据
*/
public void destroy(){
naPairs.clear();//清空原有数据避免取到老数据
}
/**
* 获取到json字符串
* @return
*/
public String getString(){
return sb.toString();
}
/**
* 格式化信息为json数据
* @return
* @throws JSONException
* @throws UnsupportedEncodingException
*/
public JSONObject getJsonObject() throws JSONException, UnsupportedEncodingException{
System.out.println("data===="+sb.toString());
return new JSONObject(sb.toString());
}
}</span>
实现登录操作(这个很简单不多做解释):
<span style="font-size:14px;">package com.zk.protocol;
import org.json.JSONException;
import org.json.JSONObject;
import com.zk.domain.User;
public class LoginProtocol extends BaseProtocol {
private final static String url="http://192.168.0.58:8859/mis/login.action";
public JSONObject checkLogin(User user) throws JSONException{
System.out.print(user.getUsername());
try {
destroy();
addNameValuePair("username",user.getUsername());
addNameValuePair("password",user.getPassword());
pack(url);
parse();
JSONObject obj = getJsonObject();
return obj;
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
JSONObject jsonObject = new JSONObject();
jsonObject.accumulate("msg", "false");
return jsonObject;
}
}
}
</span>
输入正确用户名和密码点击登录后跳转到登录成功页面代码如下:
注:登录成功页获取到了安卓客户端从服务端获取到的sessionid等数据,在这里把用户的所在部门和真实姓名显示在了界面的头部,并添加了获取用户数据按钮,点击获取用户数据,以下程序会把获取到的当前用户的数据传到下一个页面(这里之所以做了一次跳转没有直接在这里获取所有用户数据,是因为想到了程序的可扩展性,可以把本页面开发成一个tabhost切换标签实现不同功能,而获取所有用户数据只是其中功能之一)。
<span style="font-size:14px;">package com.zk.sshclinet;
import org.json.JSONException;
import org.json.JSONObject;
import com.google.gson.Gson;
import com.zk.domain.User;
import com.zk.protocol.GetDataProtocol;
import android.R.integer;
import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.TextView;
public class TabShow extends Activity {
private Intent intent;
private TextView topTitle,info;
JSONObject jsonObject;
private String sessionid;
private String realname;
private String dept;
private String userid;
private Button btn;
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.tabshow);
try {
initResourceRefs();
} catch (JSONException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
initSetting();
}
public void initResourceRefs() throws JSONException{
intent = getIntent();
topTitle=(TextView) findViewById(R.id.title);
btn = (Button) findViewById(R.id.btn);
info=(TextView) findViewById(R.id.info);
jsonObject=new JSONObject(intent.getStringExtra("session").toString());
sessionid=jsonObject.getString("sessionId");
realname = jsonObject.getString("realname");
dept=jsonObject.getString("dept");
userid=jsonObject.getString("userid");
}
public void initSetting(){
topTitle.setText("当前用户:"+dept+"-"+realname);
btn.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View arg0) {
// TODO Auto-generated method stub
Intent nextIntent = new Intent(TabShow.this,PersonList.class);
nextIntent.putExtra("session", jsonObject.toString());
startActivity(nextIntent);
}
});
}
}
</span>
点击获取用户数据跳转到第三个页面代码如下:
注:这里是用户登录成功后再次向服务端发出请求,做过web项目的都知道,web项目的客户端的session是由浏览器负责管理的,但是,在安卓项目中并没有负责管理session的部分,所以我们要自己设置管理session。以下代码在传给工具类服务端访问地址的同时也传递了用来辨别用户身份和登录状态的sessionid。
package com.zk.sshclinet;
import java.util.ArrayList;
import java.util.List;
import org.json.JSONException;
import org.json.JSONObject;
import com.zk.domain.User;
import com.zk.protocol.GetDataProtocol;
import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.ListView;
import android.widget.TextView;
import android.widget.Toast;
public class PersonList extends Activity {
private final static String url = "http://192.168.0.58:8859/mis/getAllPerson.action";
private final static int SUCCESSL=1;
private final static int ERROR=2;
private GetDataProtocol getDataProtocol;
private ListView lv;
private JSONObject objs,jsonObject;
private Intent intent;
private String sessionid;
private List<User> uList;
private Handler handler = new Handler(){
public void handleMessage(Message msg){
switch (msg.what) {
case ERROR:
Toast.makeText(PersonList.this, "获取数据出现错误!", 0).show();
case SUCCESSL:
if(uList != null){
lv.setAdapter(new Myadpter());
}else{
Toast.makeText(PersonList.this, "未获取到数据!", 0).show();
}
break;
default:
break;
}
}
};
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.personlist);
initResourceRefs();
try {
initSetting();
} catch (JSONException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
public void initResourceRefs(){
intent = getIntent();
uList = new ArrayList<User>();
lv = (ListView) findViewById(R.id.lv_person);
getDataProtocol = new GetDataProtocol();
}
public void initSetting() throws JSONException{
jsonObject=new JSONObject(intent.getStringExtra("session").toString());
sessionid=jsonObject.getString("sessionId");
new Thread(){
public void run(){
Message msg = new Message();
try {
uList = getDataProtocol.getdata(url, sessionid);
System.out.println("11111111111111111111111111111");
msg.what=SUCCESSL;
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
msg.what=ERROR;
}
handler.sendMessage(msg);
}
}.start();
}
public class Myadpter extends BaseAdapter{
@Override
public int getCount() {
return uList.size();
}
@Override
public View getView(int postion, View convertView, ViewGroup arg2) {
User user = uList.get(postion);
View view = View.inflate(getApplicationContext(), R.layout.list_item, null);
TextView bm = (TextView) view.findViewById(R.id.bm);
TextView rname = (TextView) view.findViewById(R.id.rname);
bm.setText(user.getDept());
rname.setText(user.getRealName());
return view;
}
@Override
public Object getItem(int arg0) {
return null;
}
@Override
public long getItemId(int arg0) {
return 0;
}
}
}
再次访问后台数据的工具类代码如下:
注:这里边代码很简单,不过细心的读者一定会发现里边Gson,Gson是一个解析json数据的库,这个一会在介绍到服务端的时候一块介绍。
package com.zk.protocol;
import java.util.List;
import org.json.JSONObject;
import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;
import com.zk.domain.User;
public class GetDataProtocol extends BaseProtocol {
public List<User> getdata(String url,String sessionid) throws Exception{
destroy();
linkSer(url,sessionid);
parse();
String str = getString();
System.out.println("****************"+str);
Gson gson = new Gson();
List<User> retList = gson.fromJson(str,
new TypeToken<List<User>>() {
}.getType());
return retList;
}
}
下面是一个用到的实体类代码如下:
<span style="font-size:14px;">package com.zk.domain;
import java.sql.Timestamp;
public class User {
private String userid;
private String dept;
private String realName;
private String seqNo;
private String username;
private String password;
private Integer roleCode;
private String flag;
private Timestamp createTime;
private String ip;
public String getUserid() {
return userid;
}
public void setUserid(String userid) {
this.userid = userid;
}
public String getDept() {
return dept;
}
public void setDept(String dept) {
this.dept = dept;
}
public String getRealName() {
return realName;
}
public void setRealName(String realName) {
this.realName = realName;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getSeqNo() {
return seqNo;
}
public void setSeqNo(String seqNo) {
this.seqNo = seqNo;
}
public Integer getRoleCode() {
return roleCode;
}
public void setRoleCode(Integer roleCode) {
this.roleCode = roleCode;
}
public String getFlag() {
return flag;
}
public void setFlag(String flag) {
this.flag = flag;
}
public Timestamp getCreateTime() {
return createTime;
}
public void setCreateTime(Timestamp createTime) {
this.createTime = createTime;
}
public String getIp() {
return ip;
}
public void setIp(String ip) {
this.ip = ip;
}
}
</span>
至此,客户端后台代码已经介绍完毕,下面是界面文件都很简单不多做介绍:
1、activity_main.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:background="@drawable/bg"
tools:context=".MainActivity"
>
<EditText
android:id="@+id/name"
android:layout_marginTop="60dip"
style="@style/edtText1"
android:hint="请输入用户名..."
/>
<EditText
android:id="@+id/pwd"
style="@style/edtText1"
android:hint="请输入密码..."
/>
<Button
android:id="@+id/login"
android:layout_width="match_parent"
android:layout_height="40dip"
android:layout_marginLeft="20dip"
android:layout_marginRight="20dip"
android:background="@drawable/login_but"
android:text="登录"/>
<TextView
android:id="@+id/errorInfo"
style="@style/errorInfostl"
android:text=""/>
"
</LinearLayout>
2、tabshow.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:background="@drawable/red">
<TextView
android:id="@+id/title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="5dip"
android:textSize="20sp"
android:gravity="center_vertical"
android:text="当前用户:" />
</LinearLayout>
<TextView
android:id="@+id/info"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="成功登录......." />
<Button
android:id="@+id/btn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="获取用户数据" />
</LinearLayout>
3、personlist.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >
<ListView
android:id="@+id/lv_person"
android:layout_width="match_parent"
android:layout_height="match_parent" >
</ListView>
</LinearLayout>
4、list_item.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="60dip"
android:orientation="vertical" >
<TextView
android:id="@+id/bm"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="#10eb77"
android:layout_marginLeft="5dip"
android:layout_marginTop="5dip"
android:textSize="20sp"
android:text="" />
<TextView
android:id="@+id/rname"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="80dip"
android:textSize="25sp"
android:text="" />
</LinearLayout>
另外,还有要注意的是因为安卓客户端要访问网络,所以要给客户端添加访问网络的permission,还有就是要在AndroidManifest.xml中注册新添加的activity。代码如下:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.zk.sshclinet"
android:versionCode="1"
android:versionName="1.0" >
<uses-sdk
android:minSdkVersion="8"
android:targetSdkVersion="18" />
<uses-permission android:name="android.permission.INTERNET"/>
<application
android:allowBackup="true"
android:icon="@drawable/ic_launcher"
android:label="@string/app_name"
android:theme="@style/AppTheme" >
<activity
android:name="com.zk.sshclinet.MainActivity"
android:label="@string/app_name" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity
android:name="com.zk.sshclinet.TabShow"></activity>
<activity
android:name="com.zk.sshclinet.PersonList"></activity>
</application>
</manifest>
至此,安卓客户端介绍完毕。
下面开始直接介绍ssh服务端如何配置不在介绍SSH服务端如何搭建等问题。
首先,我们知道SSH服务端是在struts中配置对请求的响应。那么,安卓的请求和web页面的请求的配置方式有什么不一样呢,大家肯定会知道java web项目中应用AJAX时struts的配置方式,起始安卓客户端的请求的配置方法和ajax的配置方法基本一样,下面看代码:
<action name="login" class="cn.zk.sso.action.LoginAction" method="login"></action>
<action name="getAllPerson" class="cn.zk.mis.weekly.action.WeeklyPersonAction" method="getAllPerson"></action>
上面大家应该看到了配置方式中没有配置返回值等内容。
下面看服务端处理登录时的代码如下:
注:在这里边我去掉了很多东西,正常情况下,验证登录都要检查session是否为空,要考虑为空和不为空的情况,因为代码是在太多,这里要是重点介绍安卓部分,所以去掉了检查session和cookie实现自动登录,还有密码的MD5加密等问题。
public String login() throws Exception{
System.out.println("sessionID="+getSession().getId());
request = ServletActionContext.getRequest();
response = ServletActionContext.getResponse();
username=request.getParameter("username");
pwd=request.getParameter("password");
msg= loginService.getUserByNamePwd(username,pwd);
if(msg!=""){ msg="false"; }else{ msg="true"; }
response.setContentType("text/html");
resquest.setCharacterEncoding("UTF-8"); //避免中文乱码 POST方式提交
response.setContentType("text/json;charset=UTF-8");
JSONObject jObject = new JSONObject();
jObject.accumulate("msg", msg);
jObject.accumulate("sessionId", getSession().getId());
jObject.accumulate("userid", sysUser.getUserid());
jObject.accumulate("realname", sysUser.getRealName());
jObject.accumulate("dept", sysUser.getDept());
response.getWriter().print(jObject.toString());
System.out.println(jObject.toString());
response.getWriter().flush();
response.getWriter().close();
return null;
}
获取所有用户部分代码如下:
注:这里说一下Gson的问题,首先使用Gson在项目中导入相应JAR包,在上面项目结构截图中能够看到相应jar包,具体使用方法点击“Gson使用方法查看”。
<span style="font-size:14px;">public String getAllPerson() throws IOException{
HttpServletResponse response = ServletActionContext.getResponse();
HttpServletRequest request = ServletActionContext.getRequest();
uList = weeklyPersonService.getAllPerson();
response.setContentType("text/html");
request.setCharacterEncoding("UTF-8"); //避免中文乱码 POST方式提交
response.setContentType("text/json;charset=UTF-8");
Gson gson = new Gson();
gson.toJson(uList);
JSONObject jsObject = new JSONObject();
jsObject.accumulate("msg", "true");
response.getWriter().print(gson.toJson(uList));
System.out.println(jsObject.toString());
response.getWriter().flush();
response.getWriter().close();
return null;
}
</span>
项目资源已上传下载请点击“android集合SSH访问服务端项目”