最近做了一个Android开发小demo,主要功能是在手机的ActionBar中的搜索栏,根据用户输入的名字从后台(java+Tomcat)数据库中取出该名字对应的一系列信息,然后通过json形式传到前台,并用表格的形式显示到前台。
跟大家分享一下,并且列出几个比较坑的点。但是只是列出了java文件和layout中的xml文件,drawable以及bean类没有列
涉及的知识点主要有:
- 利用HttpUrlConnection进行通信
- 数据库相关操作
- 利用json传输数据
- 后台代码的mvc模式
- Android中用列表显示数据(表格是自己画的)
前台
前台主要涉及两个Activity,一个是SearchActivity,一个是SearchResultActivity。
SearchActivity
SearchActivity,它的主要功能是在ActionBar中的搜索栏可以接收用户输入的数据,并且可以动态提示,点击回车后,会将数据传到SearchResultActivity。
话不多说,上代码:
package com.wzy.search.aty;
import java.util.ArrayList;
import com.wzy.search.R;
import android.annotation.SuppressLint;
import android.app.ActionBar;
import android.app.Activity;
import android.app.SearchManager;
import android.app.SearchableInfo;
import android.content.ComponentName;
import android.content.Context;
import android.graphics.Color;
import android.opengl.Visibility;
import android.os.Bundle;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.inputmethod.EditorInfo;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.ArrayAdapter;
import android.widget.EditText;
import android.widget.ListView;
import android.widget.SearchView;
import android.widget.SearchView.OnCloseListener;
import android.widget.SearchView.OnQueryTextListener;
import android.widget.TextView;
/**
* 该Activity执行前台搜索功能,具备自动提示
* @author wzy
* @version V1.0
*/
@SuppressLint("DefaultLocale")
public class SearchActivity extends Activity {
SearchView searchView = null;
EditText searchText = null;
private ListView searchListView = null;
private ArrayAdapter<String> searchAdapter = null;
private String[] searchNames = null;
private ArrayList<String> searchList = null;
@Override
protected void onCreate(Bundle savedInstanceState) {
// TODO Auto-generated method stub
super.onCreate(savedInstanceState);
setContentView(R.layout.search_person);
ActionBar actionBar = getActionBar();
if (actionBar != null) {
actionBar.setDisplayHomeAsUpEnabled(true); // 给左上角图标左边加上一个返回图标
}
searchListView = (ListView) findViewById(R.id.searchListView);
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
// 自定义菜单,设置搜索栏
getMenuInflater().inflate(R.menu.search_menu, menu);
//获取SearchView对象
searchView = (SearchView) menu.findItem(R.id.search).getActionView();
if (searchView == null) {
Log.e("SearchView", "Fail to get Search");
return true;
}
// 获得searchview文本对象
int search_text_id = searchView.getContext().getResources().getIdentifier("android:id/search_src_text", null, null);
searchText = (EditText) searchView.findViewById(search_text_id);
searchView.setOnSearchClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
// TODO Auto-generated method stub
searchListView.setVisibility(View.VISIBLE);
}
});
searchView.setOnCloseListener(new OnCloseListener() {
@Override
public boolean onClose() {
// TODO Auto-generated method stub
searchListView.setVisibility(View.INVISIBLE);
return false;
}
});
searchNames = new String[]{"abc","bcd","cde","def","efg","wxy","xyz"};
searchAdapter = new ArrayAdapter<String>(getApplicationContext(), android.R.layout.simple_expandable_list_item_1,searchNames);
searchListView.setAdapter(searchAdapter);
searchListView.setTextFilterEnabled(true);
searchListView.setOnItemClickListener(new OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> arg0, View arg1, int arg2, long arg3) {
// TODO Auto-generated method stub
searchText.setText(((TextView)arg1).getText().toString());
searchText.setSelection(((TextView)arg1).getText().toString().length());
Log.e("string", ((TextView)arg1).getText().toString());
}
});
searchView.setOnQueryTextListener(new OnQueryTextListener() {
@Override
public boolean onQueryTextSubmit(String query) {
// TODO Auto-generated method stub
return false;
}
@Override
public boolean onQueryTextChange(String newText) {
// TODO Auto-generated method stub
if (newText.length() != 0) {
setFilterText(newText);
} else {
clearTextFilter();
}
return false;
}
});
//设置搜索图标。设置为false的时候icon固定于文字左边,设置为true在文字右边,随文字动
searchView.setIconified(true);
//获取SearchView文本对象
searchText.setTextColor(Color.WHITE);
//核心代码
//获取搜索服务管理器
SearchManager searchManager = (SearchManager) getSystemService(Context.SEARCH_SERVICE);
// searchable activity的component name,由此系统可通过intent进行唤起
ComponentName componentName = new ComponentName(this, SearchResultActvity.class);
// 通过搜索管理器,从searchable activity中获取相关搜索信息,就是searchable的xml设置。如果返回null,表示该activity不存在,或者不是searchable
SearchableInfo searchableInfo = searchManager.getSearchableInfo(componentName);
if (searchableInfo == null) {
Log.e("SearchableInfo", "Fail to get SearchableInfo");
}
// 将searchable activity的搜索信息与search view关联
searchView.setSearchableInfo(searchableInfo);
return true;
}
@SuppressLint("DefaultLocale")
public void setFilterText(String filterText) {
ArrayList<String> list = new ArrayList<String>();
String[] tempStr;
for (int i = 0; i < searchNames.length; i++) {
String searchNameIgnoreCase = searchNames[i].toLowerCase();
String filterTextIgnoreCase = filterText.toLowerCase();
if (searchNameIgnoreCase.contains(filterTextIgnoreCase)) {
list.add(searchNames[i]);
}
}
if (list.size() >= 0) {
tempStr = new String[list.size()];
int j = 0;
for (String str : list) {
tempStr[j++] = str;
}
searchAdapter = new ArrayAdapter<String>(getApplicationContext(),
android.R.layout.simple_expandable_list_item_1, tempStr);
searchListView.setAdapter(searchAdapter);
}
}
public void clearTextFilter() {
searchAdapter = new ArrayAdapter<String>(getApplicationContext(),
android.R.layout.simple_expandable_list_item_1, searchNames);
searchListView.setAdapter(searchAdapter);
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
// TODO Auto-generated method stub
switch (item.getItemId()) {
case android.R.id.home:
overridePendingTransition(R.anim.tran_return_in, R.anim.tran_return_out);
finish();
break;
case R.id.search:
break;
default:
break;
}
return super.onOptionsItemSelected(item);
}
long lastClickTime = 0;
@Override
public void onBackPressed() {
overridePendingTransition(R.anim.tran_return_in, R.anim.tran_return_out);
finish();
}
}
search_person.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/searchListView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="invisible"
android:background="#454545"/>
</LinearLayout>
SearchResultActivity
上面的代码就把数据传到SearchResultActivity了。下面看该类代码:
package com.wzy.search.aty;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import com.wzy.search.R;
import com.wzy.search.bean.Person;
import com.wzy.search.util.HttpUtil;
import com.wzy.search.util.Material;
import com.wzy.search.util.SerializableMap;
import com.wzy.search.StringUtil;
import com.wzy.bean.PersonData;
import com.wzy.bean.StringBean;
import android.app.ActionBar;
import android.app.Activity;
import android.app.SearchManager;
import android.content.Intent;
import android.os.Bundle;
import android.os.DropBoxManager.Entry;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ListView;
import android.widget.SimpleAdapter;
import android.widget.Toast;
import android.widget.AdapterView.OnItemClickListener;
/**
* 该类返回搜索的结果,并以表格的形式显示
* @author wzy
*
*/
public class SearchResultActvity extends Activity{
private List<PersonData> datas;
private ListView listView = null;
private List<Map<String, String>> personDatas = null;
private SimpleAdapter simpleAdapter = null;
@Override
protected void onCreate(Bundle savedInstanceState) {
// TODO Auto-generated method stub
super.onCreate(savedInstanceState);
setContentView(R.layout.result_names_new);
ActionBar actionBar = getActionBar();
actionBar.setDisplayHomeAsUpEnabled(true);// 给左上角图标左边加上一个返回图标
actionBar.setDisplayShowHomeEnabled(true);// 设置ActionBar程序图标不可见
listView = (ListView) findViewById(R.id.personList); // 获取Listview的引用
personDatas = new ArrayList<Map<String, String>>();
doSearchQuery(getIntent());
// 将获取到的名称包装为SimpleAdapter
simpleAdapter = new SimpleAdapter(SearchResultActvity.this, personDatas, R.layout.nameitems,
new String[] { PersonData.NAME , PersonData.AGE, PersonData.ADDRESS },
new int[] { R.id.tv_name, R.id.tv_age, R.id.tv_address });
// 为ListView设置Adapter
listView.setAdapter(simpleAdapter);
// 为ListView的列表项的单击事件绑定事件监听器
listView.setOnItemClickListener(new OnItemClickListener() {
// 这里可以自己写点击事件
}
});
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.menu, menu);
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case android.R.id.home:
finish();
break;
default:
break;
}
return super.onOptionsItemSelected(item);
}
/**
* activity重新置顶
*/
@Override
protected void onNewIntent(Intent intent) {
// TODO Auto-generated method stub
doSearchQuery(intent);
}
/**
* 对searchable activity的调用仍是标准的intent,我们可以从intent中获取信息,即要搜索的内容
* @param intent
*/
private void doSearchQuery(Intent intent){
if(intent == null)
return;
String queryAction = intent.getAction();
if( Intent.ACTION_SEARCH.equals( intent.getAction())){ //如果是通过ACTION_SEARCH来调用,即如果通过搜索调用
String queryString = intent.getStringExtra(SearchManager.QUERY); //获取搜索内容
Log.e("yep", queryString);
datas = null;
final Map<String, String> params = new HashMap<String, String>();
params.put(StringBean.NAME, queryString);
new Thread(new Runnable() {
@Override
public void run() {
String encode = "utf-8";
try {
datas = HttpUtil.sendHttpRequst(params, encode);
if (datas == null) {
Log.e("connect result", "返回结果异常");
}else {
for (PersonData data : datas) {
System.out.println(data.getAge() + " " + data.getName() + " " + data.getAddress());
Map<String, String> newData = new HashMap<String, String>();
newData.put(PersonData.NAME, data.getName());
newData.put(PersonData.AGE, data.getAge());
newData.put(PersonData.ADDRESS, data.getAddress());
personDatas.add(newData);
}
for (Map<String, String> map : personDatas) {
for (Map.Entry<String, String> entry : map.entrySet()) {
System.out.println(entry.getKey() + " --> " + entry.getValue());
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
}).start();
}
}
}
result_names_new.xml如下所示:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#ffffcc" >
<FrameLayout
android:id="@+id/id_fl"
android:layout_width="match_parent"
android:layout_height="20dip"
android:layout_marginTop="10dip" >
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/search_for_details"
android:textSize="16sp"
android:textStyle="bold" />
</FrameLayout>
<LinearLayout
android:id="@+id/id_ll"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@id/id_fl"
android:layout_marginLeft="10dip"
android:layout_marginRight="10dip"
android:layout_marginTop="30dip"
android:orientation="horizontal" >
<TextView
android:layout_width="0dip"
android:layout_height="40dip"
android:layout_marginRight="-1dp"
android:layout_weight="1.3"
android:singleLine="true"
android:ellipsize="end"
android:background="@drawable/textview_border"
android:gravity="center"
android:text="name"
android:textSize="13sp" />
<TextView
android:layout_width="0dip"
android:layout_height="40dip"
android:layout_marginRight="-1dp"
android:layout_weight="1"
android:singleLine="true"
android:ellipsize="end"
android:background="@drawable/textview_border"
android:gravity="center"
android:text="age"
android:textSize="13sp" />
<TextView
android:layout_width="0dip"
android:layout_height="40dip"
android:layout_weight="1"
android:singleLine="true"
android:ellipsize="end"
android:background="@drawable/textview_border"
android:gravity="center"
android:text="address"
android:textSize="13sp" />
</LinearLayout>
<ListView
android:id="@+id/personList"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignLeft="@id/id_ll"
android:layout_alignRight="@id/id_ll"
android:layout_below="@id/id_ll"
android:layout_marginTop="1dp"
android:headerDividersEnabled="false" >
</ListView>
</RelativeLayout>
nameitems如下所示:
<?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:background="#ffffcc"
android:orientation="horizontal" >
<TextView
android:id="@+id/tv_name"
android:layout_width="0dip"
android:layout_height="40dip"
android:layout_marginRight="-1dp"
android:layout_marginTop="-2dp"
android:layout_weight="1.3"
android:singleLine="true"
android:ellipsize="end"
android:background="@drawable/textview_border"
android:clickable="true"
android:gravity="center"
android:textSize="13sp" />
<TextView
android:id="@+id/tv_age"
android:layout_width="0dip"
android:layout_height="40dip"
android:layout_marginRight="-1dp"
android:layout_marginTop="-2dp"
android:layout_weight="1"
android:singleLine="true"
android:ellipsize="end"
android:background="@drawable/textview_border"
android:clickable="true"
android:gravity="center"
android:textSize="13sp" />
<TextView
android:id="@+id/tv_address"
android:layout_width="0dip"
android:layout_height="40dip"
android:layout_marginTop="-2dp"
android:layout_weight="1"
android:singleLine="true"
android:ellipsize="end"
android:background="@drawable/textview_border"
android:clickable="true"
android:gravity="center"
android:textSize="13sp" />
</LinearLayout>
从xml可以看出来,表格是自己画的,先是画出表头,然后再用ListView,再将list化成和表头一样的框,就成了表格了。只不过需要调整一下间距,否则比较难看。
这里还有几个点:
一是调用HttpUtil方法时,必须使用线程,否则会报错;
二是对于listview的操作,比如初始化和setAdapter,必须放在onCreate方法里,否则会出现问题。
通信
本例使用HttpUrlConnection进行通信,在com.wzy.util包下新建HttpUtil类,主要用于发送前台传到后台的参数,并且接受后台传过来的数据,并且把数据包装成PersonData的形式。
package com.wzy.util;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
i mport java.io.InputStream;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;
import com.siemens.bean.MaterialData;
import android.util.Log;
/**
* 该类用于与后台进行通信,通过json方式,将结果以PersonData形式进行封装
* @author wzy
*
*/
public class HttpUtil {
private final static String PATH = "http://127.0.0.1:8088/PersonServlet/QueryController";
private static URL url;
private static List<PersonData> datas = null;
public HttpUtil() {
}
@SuppressWarnings("deprecation")
public static List<PersonData> sendHttpRequst(Map<String, String> params, String encode) throws Exception {
datas = new ArrayList<PersonData>();
// 对查询的参数进行封装
StringBuffer buffer = new StringBuffer();
if (params != null && !params.isEmpty()) {
for (Map.Entry<String, String> entry : params.entrySet()) {
buffer.append(entry.getKey()).append("=").append(URLEncoder.encode(entry.getValue())).append("&");
}
buffer.deleteCharAt(buffer.length() - 1);
}
// 打印传递的参数
Log.e("data", buffer.toString());
try {
url = new URL(PATH);
// System.out.println("url:" + url.toString());
if (url != null) {
HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection();
Log.e("TEST", urlConnection+"");
if (urlConnection == null) {
System.out.println("urlConnection: " + urlConnection);
return null;
}
urlConnection.setConnectTimeout(3000);
urlConnection.setRequestMethod("POST"); // 设置请求方式
urlConnection.setDoInput(true); // 表示从服务器获取数据
urlConnection.setDoOutput(true); // 表示向服务器发送数据
byte[] data = buffer.toString().getBytes();
// System.out.println("buffer:" + data.toString());
// 设置请求体的是文本类型
urlConnection.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
urlConnection.setRequestProperty("Content-Length", String.valueOf(data.length));
// 获得输出流
OutputStream outputStream = urlConnection.getOutputStream();
outputStream.write(data);
outputStream.close();
// 获得服务器的响应结果和状态码
int responseCode = urlConnection.getResponseCode();
Log.e("RESCODE", responseCode + "");
// 服务器响应成功
if (responseCode == 200) {
// 获取查询结果
InputStream in = urlConnection.getInputStream();
// 结果为JSON格式,对JSON进行解析
datas = parseJSON(in);
}
}
return datas;
} catch (IOException e) {
e.printStackTrace();
Log.e("my error", "这里肯定发生了错误");
}
return null;
}
private static List<PersonData> parseJSON(InputStream in) throws Exception {
String str = read(in);
Gson gson = new Gson();
datas = gson.fromJson(str, new TypeToken<List<PersonData>>(){}.getType());
return datas;
}
private static String read(InputStream in) throws IOException {
byte[] data;
ByteArrayOutputStream bout = new ByteArrayOutputStream();
byte[] buf = new byte[1024];
int len = 0;
while ((len = in.read(buf)) != -1) {
bout.write(buf, 0, len);
}
data = bout.toByteArray();
return new String(data, "utf-8");
}
}
这里就没什么好说的了,需要注意的是,这里用Gson进行的数据解析,讲json转化为PeronData,传给SearchResultActivity
后台
后台代码结构
本例中,后台不需要view层,所以只需要以下包
-com.wzy.bean,封装数据库的表;
-com.wzy.service,对某个表进行操作;
-com.wzy.controller,控制某个service进行操作;
-com.wzy.util,有SqlHelper类,进行数据库的增删改查操作,涉及数据库的都可以用这个类,也可以叫com.wzy.dao包;
本例中,用QueryController接受前台传过来的数据,并用PersonService进行查询操作,返回PersonData数据
QueryController
代码如下:
package com.wzy.controller;
import java.io.IOException;
import java.io.OutputStream;
import java.util.ArrayList;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import com.google.gson.Gson;
import com.wzy.bean.PersonData;
import com.wzy.bean.StringBean;
import com.wzy.service.PersonService;
/**
* 该类用于控制查询相关事务,不包括单表查询。即需要查询多个表,通过该controller,调用相关service进行处理
* @author wzy
*
*/
public class QueryController extends HttpServlet {
private static final long serialVersionUID = 1L;
private String materialName = "";
/**
* @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response)
*/
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// TODO Auto-generated method stub
doPost(request, response);
}
/**
* @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse response)
*/
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// TODO Auto-generated method stub
request.setCharacterEncoding("utf-8");
response.setContentType("text/json;charset=utf-8");
response.setContentType("utf-8");
OutputStream out = response.getOutputStream();
ArrayList<PersonData> datas = null; //定义需要返回的数据
if (request != null) {
name = request.getParameter(StringBean.NAME);
// 调用PersonService的方法,根据名字得到需要返回的ArrayList
PersonService personService = new PersonService();
datas = personService.getPersonByName(name);
// 测试代码,输出查询到的数据
for (PersonData data : datas) {
System.out.println(data);
}
// 通过gson返回数据
Gson gson = new Gson();
out.write(gson.toJson(datas).getBytes("utf-8"));
out.flush();
out.close();
}
}
}
需要注意的是,要用json的话,需要导包,这个可以百度一下,有一个主要包,还有几个支持包,都要导
MaterialService类
package com.wzy.service;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.HashMap;
import com.google.gson.Gson;
import com.wzy.bean.PersonData;
import com.wzy.bean.Person_name;
import com.wzy.utils.DBString;
import com.wzy.utils.SqlHelper;
/**
* 对涉及多表的业务进行操作
* @author wzy
*
*/
public class PersonService {
private String sql = "";
private String[] params = null;
private PersonData data = null;
private ArrayList<PersonData> datas = null;
/**
* 根据name查找,并返回封装name,age,address的数据结构
* @param name
* @return 封装name,age,address的对象的ArrayList
*/
public ArrayList<PersonData> getPersonByName (String name) {
params = new String[]{"%" + name + "%"};
sql = "select "+Person_name.NAME+","+Person_name.AGE+","
+Person_name.ADDRESS+" from "+DBString.TABLE_Person_NAME
+" where "+Person_name.PERSON_NAME+" like ?";
System.out.println("sql test:" + sql);
ResultSet rs = SqlHelper.executeQuery(sql,params);
datas = new ArrayList<>();
try {
while (rs.next()) {
data = new PersonData();
data.setPersonAge(rs.getString(Person_name.PERSON_AGE));
data.setPersonName(rs.getString(Person_name.PERSON_NAME));
data.setPersonAddress(rs.getString(Person_name.ADDRESS));
datas.add(data);
}
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} finally {
SqlHelper.close(SqlHelper.getConn(), SqlHelper.getStat(), rs);
}
return datas;
}
}
这里又有一个坑:
当preparestatement语句中,有?又有%时,需要把%放在params定义中,而不能够放在?旁边,这个错误检查了半天,始终找不出来,最后还是慢慢试出来的。
另外,上面代码提到的Person_name是一个表,里面仅仅存放了所有人的名字,方便用于查找用的。
说明
上面的代码仅供参考,留待以后使用,有一些代码没有贴出来。