FOE电影购票APP之Android客户端
终于来到本次系列学习的尾声了,这次,我将会和大家一起做一个简单的Android APP,实现通过服务器访问数据库。
还记得上一个博客给大家提前剧透的demo吗,不记得没关系~我再放一次。
首先,我用的Android开发工具是Android Studio。用Eclispe的读者,你们看懂代码就好。
我这里就不说怎么配置JDK,ADT之类的,包括layout文件的布局,甚至是跳转过去的listView, adapter之类的知识。我默认你们都会简单的Android开发咯。哈哈。我在本次博客的侧重点是,如何建立一个connection,并且访问服务器,获得我们想要的数据。
首先是第一个页面的登录功能,我们给登录按钮一个监听事件。
Button btnLogin = (Button)findViewById(R.id.ButtonLogin);//获取登录按钮
btnLogin.setOnClickListener(new View.OnClickListener(){
@Override
public void onClick(View v) {
String usernmae = etUsername.getText().toString();
String password = etPassword.getText().toString();
if (usernmae.isEmpty()) {
Toast.makeText(MainActivity.this, "用户名为空", Toast.LENGTH_SHORT).show();
} else if (password.isEmpty()) {
Toast.makeText(MainActivity.this, "密码为空", Toast.LENGTH_SHORT).show();
} else {
Login(usernmae, password); //在用户名和密码不为空的前提下执行Login函数。
}
}
});
这里给大家再巩固一个知识,就是Android开发中的互联网访问是不可以在主线程中执行的,其实很多其他开发都一样,因为上网需要时间,下载资源等很容易会阻塞主线程,像Uinty的话,也是要放在协程中进行的。而在Android中,也提供了一些方法给我们简单的新开线程并且与界面进行交互。其中,我这次用的是异步任务类(AsyncTask)。
我们来看看Login函数
private void Login(String username, String password) {
String loginUrl = LoginURL + "?Username=" + username + "&Password=" + password;
new MyAsyncTask(MainActivity.this, username).execute(loginUrl);
}
很简单,哦,对了,大家记得在manifest中申请网络权限
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
回过头来,我们的Login函数就是将我们的MainActivity的上下文和username作为MyAyncTask的构造函数参数来构造一个MyAyncTask实例,然后执行execyte(loginUrl)。
现在我们看一下我们的MyAyncTask类
public static class MyAsyncTask extends AsyncTask<String, Integer, String> {
private Context context;
private String uName;
public MyAsyncTask(Context context, String username) {
this.context = context;
this.uName = username;
}
@Override
protected void onPreExecute() {
}
@Override
protected String doInBackground(String... params) {
HttpURLConnection connection = null;
StringBuilder response = new StringBuilder();
try {
URL url = new URL(params[0]);
connection = (HttpURLConnection) url.openConnection();
connection.setRequestMethod("GET");
connection.setConnectTimeout(8000);
connection.setReadTimeout(8000);
InputStream in = connection.getInputStream();
BufferedReader reader = new BufferedReader(new InputStreamReader(in));
String line;
while ((line = reader.readLine()) != null) {
response.append(line);
}
} catch (MalformedURLException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return response.toString();
}
@Override
protected void onProgressUpdate(Integer... Values) {
}
@Override
protected void onPostExecute(String s) {
String text = s.substring(s.indexOf("resMsg=")+7);
Toast.makeText(context, text, Toast.LENGTH_SHORT).show();
if ("登录成功}".equals(text)) {
SharedPreferences userSharePreferences = context.getSharedPreferences("userInfo", Activity.MODE_PRIVATE);
SharedPreferences.Editor editor = userSharePreferences.edit();
editor.putString("username", uName);
editor.commit();
Intent intent = new Intent(context, FilmListActivity.class);
context.startActivity(intent);
}
}
}
如果想透彻理解Android中这一个类的读者,可以自己去找相关的资料查阅,这里我说一下几个点,比较关键。
首先是
public static class MyAsyncTask extends AsyncTask<String, Integer, String>
我们看到这里有三个参数,分别是<输入, 进度, 输出>.第二个参数我们先不管,输入指的是我们执行该类时输入的参数类型,可以看到是和下列函数的传入参数类型一致,我们Login参数传入的就是一个String。
protected String doInBackground(String... params)
而上面函数的返回类型则和输出类型一致,同样和下面一个函数的传入参数类型一致。
protected void onPostExecute(String s)
现在读者能够基本理解这个类了吧,就是在doInBackground中后台访问网络,然后得到结果后,在onPostExecute中执行,起码读者可以这样简单理解。
至于访问的url,我们在上一篇中讲过,具体和数据库的交互,是在服务器中完成的。我们的客户端只需要明白我们给服务器的参数是什么,服务器将会返回什么给我们。
大家可以看到,在登录成功后,我将会跳转到另一个Activity。
这里,我想和大家说一下,我得到第二个Activity中图片的思路。
首先我回访问这个url:
//客户端
String FilmListUrl = "http://192.168.110.1:8080/FoeServlet/FilmList";
new MyAsyncTask(FilmListActivity.this, list, filmListAdapter).execute(FilmListUrl);
我们来看一下,在服务端,会做什么。
//服务器
try {
Statement statement = (Statement)connection.createStatement();
String sql = "select * from " + DBUtil.TABLE_FILMLIST;
ResultSet result = statement.executeQuery(sql);
while (result.next()) {
rs.append("name=");
rs.append(result.getString(1));
rs.append("introduce=");
rs.append(result.getString(2));
rs.append("actor=");
rs.append(result.getString(3));
rs.append("filmPic=");
rs.append(result.getString(4));
rs.append("\n");
}
} catch (SQLException e) {
e.printStackTrace();
}
PrintWriter pw = response.getWriter();
pw.println(rs.toString());
pw.flush();
服务器会访问film_list表,然后将里面的内容全部放在一个String里,返回给客户端。这里,我们是否还记得第一篇博客的一副图片,就是该表的内容。
我们来看看服务器返回给客户端的字符串是怎样的吧。
哈哈,一眼看下去好像很乱,但其实不是的,当然,返回数据怎样组织由读者决定,反正我写的组织方法就是上图。大牛不要见笑。
当客户端收到这一串字符串,我们只要这样处理。就可以提取有用的信息。
@Override
protected void onPostExecute(ArrayList<String> filmList) {
for (int i = 0; i < filmList.size()-1; i++) {
String filmData = filmList.get(i);
String filmName = filmData.substring(filmData.indexOf("name=")+5, filmData.indexOf("introduce="));
String filmIntroduce = filmData.substring(filmData.indexOf("introduce=")+10, filmData.indexOf("actor="));
String filmActor = filmData.substring(filmData.indexOf("actor=")+6, filmData.indexOf("filmPic="));
String filmPicURL = "http://192.168.110.1:8080/FoeServlet/FilmPic?PicID=" + filmData.substring(filmData.indexOf("filmPic=")+8);
Bitmap filmPic = null;
list.add(new Film(filmPic, filmName, filmIntroduce, filmActor));
new MyPicLoad(list, i, filmListAdapter).execute(filmPicURL);
}
filmListAdapter.notifyDataSetChanged();
}
有兴趣的读者不要怕,晚点我会将源码放到github中,读者可以自己去看。我们看到,现在我们获得的图片只是一个ID,因为,我们没用必要在数据库中存储一张图片,我们只需要存储图片的url。这也是你在随便一个网站按F12,看到里面的图片其实都是src=**的原因。所以,我们需要我们拿到的图片ID,进行第二次的网络访问,获得图片。
在第二次访问中,我们需要来自服务器的字节资源读取成图片。
@Override
protected byte[] doInBackground(String... params) {
HttpURLConnection connection = null;
byte[] picByte = null;
try {
URL url = new URL(params[0]);
connection = (HttpURLConnection)url.openConnection();
connection.setRequestMethod("GET");
connection.setReadTimeout(8000);
if (connection.getResponseCode() == 200) {
InputStream fis = connection.getInputStream();
ByteArrayOutputStream bos = new ByteArrayOutputStream();
byte[] bytes = new byte[1024];
int length = -1;
while ((length = fis.read(bytes)) != -1) {
bos.write(bytes, 0, length);
}
picByte = bos.toByteArray();
bos.close();
fis.close();
}
} catch (IOException e) {
e.printStackTrace();
}
return picByte;
}
然后在获得图片后,再更新一次ListView就可以了。
@Override
protected void onPostExecute(byte[] filmPic) {
if (filmPic != null) {
Bitmap bitmap = BitmapFactory.decodeByteArray(filmPic, 0, filmPic.length);
list.get(i).setFilmBitmap(bitmap);
filmListAdapter.notifyDataSetChanged();
}
}
本次的博客,就到这里结束了。