工具:android studio
APK:http://fir.im/ksal
使用URLConnection 连接网络获取网页数据,把联网放在异步线程里执行 使用AsyncTask
extends AsyncTask
onPostExecute(StringBuffer result) 方法用于在执行完后台任务后更新UI,显示结果
连接网络有get 和 post 方法,
public void doGetURL(String url) throws Exception {
URL localURL = new URL(url);
URLConnection connection = localURL.openConnection();
HttpURLConnection httpURLConnection = (HttpURLConnection)connection;
httpURLConnection.setRequestProperty("Accept-Charset", "utf-8");
httpURLConnection.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
if (httpURLConnection.getResponseCode() >= 300) {
throw new Exception("HTTP Request is not success, Response code is " + httpURLConnection.getResponseCode());
}
inputStream = httpURLConnection.getInputStream();
inputStreamReader = new InputStreamReader(inputStream);
reader = new BufferedReader(inputStreamReader);
while ((tempLine = reader.readLine()) != null) {
tempHTML.append(tempLine);
}
}
private void doPostURL(String url) throws Exception{
URL localURL = new URL(url);
URLConnection connection = localURL.openConnection();
HttpURLConnection httpURLConnection = (HttpURLConnection)connection;
httpURLConnection.setDoOutput(true);
httpURLConnection.setRequestMethod("POST");
httpURLConnection.setRequestProperty("Accept-Charset", "utf-8");
httpURLConnection.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
httpURLConnection.setRequestProperty("Content-Length", String.valueOf(url.length()));
outputStream = httpURLConnection.getOutputStream();
outputStreamWriter = new OutputStreamWriter(outputStream);
outputStreamWriter.write(url.toString());
outputStreamWriter.flush();
if (httpURLConnection.getResponseCode() >= 300) {
throw new Exception("HTTP Request is not success, Response code is " + httpURLConnection.getResponseCode());
}
inputStream = httpURLConnection.getInputStream();
inputStreamReader = new InputStreamReader(inputStream);
reader = new BufferedReader(inputStreamReader);
while ((tempLine = reader.readLine()) != null) {
tempHTML.append(tempLine);
}
}
/**
* 直接将重定向交给HttpURLConnection完成,调用它的setInstanceFollowRedirects(true)方法,
* 这样一来重定向对于外部来说是透明的,我们完全感知不到重定向的发生,
* 但是我们没有办法截获重定向的过程,并且对于重定向次数有限制,如果超过4次的重定向,后续的重定向将会被忽略。
* */
public String redirectGetPath(final String str)
throws MalformedURLException {
URL url = new URL(str);
String realURL = null;
HttpURLConnection conn = null;
try {
conn = (HttpURLConnection) url.openConnection();
conn.setConnectTimeout(30000);
conn.setReadTimeout(30000);
conn.setRequestProperty("Accept-Charset", "utf-8");
conn.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
conn.setInstanceFollowRedirects(true);
conn.getResponseCode();// trigger server redirect
realURL = conn.getURL().toString();
// Log.d("TAG", str + "\r\n" + "redirect to \r\n" + realURL);
}catch (Exception e) {
e.printStackTrace();
} finally {
if (conn != null) {
conn.disconnect();
}
}
return realURL;
}
/**
* 方法完全自己接管重定向过程,直接调用HttpURLConnection的setInstanceFollowRedirects(false),
* 传入参数为false,然后递归的方式进行http请求,然后从header参数中取出location字段。
* 看看转了多少次
*
* */
public String recursiveTraceGetPath(String path) {
String realURL = null;
if (mStackDeep.getAndDecrement() > 0) {// 避免异常递归导致StackOverflowError
URL url = null;
HttpURLConnection conn = null;
try {
url = new URL(path);
conn = (HttpURLConnection) url.openConnection();
conn.setRequestProperty("Accept-Charset", "utf-8");
conn.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
conn.setConnectTimeout(30000);
conn.setReadTimeout(30000);
conn.setInstanceFollowRedirects(false);
int code = conn.getResponseCode();// Network block 301,302
if (needRedirect(code)) {
//临时重定向和永久重定向location的大小写有区分
String location = conn.getHeaderField("Location");
if (location == null) {
location = conn.getHeaderField("location");
}
if (!(location.startsWith("http://") || location
.startsWith("https://"))) {
//某些时候会省略host,只返回后面的path,所以需要补全url
URL origionUrl = new URL(path);
location = origionUrl.getProtocol() + "://"
+ origionUrl.getHost() + location;
}
// Log.d("TAG", "redirect to \r\n" + location);
return recursiveTraceGetPath(location);
} else {
// redirect finish.
realURL = path;
}
} catch (MalformedURLException e) {
Log.w("TAG", "recursiveTracePath MalformedURLException");
} catch (IOException e) {
Log.w("TAG", "recursiveTracePath IOException");
} catch (Exception e) {
Log.w("TAG", "unknow exception");
} finally {
if (conn != null) {
conn.disconnect();
}
}
}
return realURL;
}
private boolean needRedirect(int code) {
return (code == HttpStatus.SC_MOVED_PERMANENTLY
|| code == HttpStatus.SC_MOVED_TEMPORARILY
|| code == HttpStatus.SC_SEE_OTHER || code == HttpStatus.SC_TEMPORARY_REDIRECT);
}
自己写的方法可以自定义重定向的次数,但是需要一个jar文件的支持 import org.apache.http.HttpStatus;
但是如果网页不是重定向,而是使用js 跳转,我写网页也用过这个,用来用户登录后获取首页显示的数据,比如一个简单的
那我们就真的需要读取网页数据了。使用StringBuffer tempHTML 存储起来,百度一个html 在线格式化的,格式一下获取的数据,方便阅读
public void doGetURL(String url) throws Exception {
url = redirectGetPath(url);
// url = recursiveTraceGetPath(url);
URL localURL = new URL(url);
URLConnection connection = localURL.openConnection();
HttpURLConnection httpURLConnection = (HttpURLConnection)connection;
httpURLConnection.setRequestProperty("Accept-Charset", "utf-8");
httpURLConnection.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
httpURLConnection.setConnectTimeout(30000);
httpURLConnection.setReadTimeout(30000);
if (httpURLConnection.getResponseCode() >= 300) {
throw new Exception("HTTP Request is not success, Response code is " + httpURLConnection.getResponseCode());
}
String realURL = httpURLConnection.getURL().toString();
Log.d("TAG", url + "\r\n" + "redirect to \r\n" + realURL);
inputStream = httpURLConnection.getInputStream();
inputStreamReader = new InputStreamReader(inputStream);
reader = new BufferedReader(inputStreamReader);
while ((tempLine = reader.readLine()) != null) {
tempHTML.append(tempLine);
}
}
剩下的就是对获取的网页源码的提取了,常用的就是字符串的一些操作,比如字符串截取,字符定位,
tempHTML.substring(tempHTML.indexOf("***"),tempHTML.lastIndexOf("***") + 4);
获取相要的结果在listview 里显示
protected void onPostExecute(StringBuffer result) {
if(result != null){
// ArrayList ls = new ArrayList(){ { add("str01"); add("str02"); } };
ArrayList ls = new ArrayList();
ls.add("ClientVersion: android 1.1.1");
ls.add("获取url网页数据");
doHtmlGetRealUrl(result, ls);
for(int i = 0; i(context, android.R.layout.simple_list_item_1, ls));
}
super.onPostExecute(result);
}
上面的循环里,除了把数据添加在list里,还循环插入了数据库,先新建一个类 UrlInfoDatabaseHelper extends SQLiteOpenHelper
我只是简单存储一下,就只有一个id,一个content
public class UrlInfoDatabaseHelper extends SQLiteOpenHelper {
final String SQL_CREATE_TABLE = "create table url_table (" +
"_id integer primary key autoincrement, " +
"url_content varchar(1000))";
/*
* 构造方法 :
* 参数介绍 :
* 参数① : 上下文对象
* 参数② : 数据库名称
* 参数③ : 数据库版本号
*/
public UrlInfoDatabaseHelper(Context context, String name, int version) {
super(context, name, null, version);
}
@Override
public void onCreate(SQLiteDatabase db) {
db.execSQL(SQL_CREATE_TABLE);
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
System.out.println("call update");
}
}
初始化时
//数据库帮助类
private UrlInfoDatabaseHelper helper;
helper = new UrlInfoDatabaseHelper(context, "url", 1);
然后怎么从数据库里查出来呢
private void showSQlDate(){
Cursor cursor = helper.getReadableDatabase().rawQuery("select * from url_table", null);
//遍历Cursor
// while(cursor.moveToNext()){
// Log.e("TAG",cursor.getString(1));
// }
inflateListView(cursor);
}
/*
* 刷新数据库列表显示
* 1. 关联SimpleCursorAdapter与数据库表, 获取数据库表中的最新数据
* 2. 将最新的SimpleCursorAdapter设置给ListView
*/
private void inflateListView(Cursor cursor) {
SimpleCursorAdapter cursorAdapter = new SimpleCursorAdapter(
getApplicationContext(),//上下文对象
R.layout.item, //List显示布局
cursor,//数据源
new String[]{"url_content"}, //游标数据的名称,实际是Table列名字
new int[]{R.id.textView});//填充到的布局文件
listView.setAdapter(cursorAdapter);
}
这里是把数据库的数据在listview 里显示 ,直接使用 SimpleCursorAdapter
在数据库显示页面,我做了一个新增和删除
新增是显示一个dialog
private void add(){
//步骤2.1:通过LayoutInflater从Android的XML文件中生成View
LayoutInflater inflater = LayoutInflater.from(this);
final View addView = inflater.inflate(R.layout.add_dialgo,null);
//步骤2.2:通过AlertDialog弹出对话框,并且在第一个button,即PositiveButton监听事件,触发操作
new AlertDialog.Builder(this)
.setTitle("添加框")
.setView(addView)
.setPositiveButton("确定", new DialogInterface.OnClickListener() {
//我们希望得到addView中的数据,但是这个inner class,只能获取final的值,所以之前将addView设置为final,也就是所有addView的地址是固定的,而不是动态生成。
public void onClick(DialogInterface dialog, int which) {
EditText nameView = (EditText)addView.findViewById(R.id.show_name);
// addData是下面步骤三,实现SQLite的数据更新和ListView的显示同步add(name,weight);
addData(nameView.getText().toString());
}
})
.setNegativeButton("取消",null)
.show();
}
// 更新数据库和同步ListView,具体如下:
private void addData(String name){
/* 略去数据的判断,例如如果name一样,采用update的方式等等*/
//步骤3.1 在数据库表格中添加数据
helper.getReadableDatabase().execSQL("insert into url_table values(null, ?)",
new String[]{name});
//步骤3.2 同步ListView,更新游标的信息
showSQlDate();
}
删除时,是用户长按屏幕上的某条数据,使用ContextMenu,即用户手指长按某个View触发的菜单
private static final int DELETE_ID = 2;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.content_sql);
helper = new UrlInfoDatabaseHelper(this, "url", 1);
listView = (ListView) findViewById(R.id.listViewSql);
/**
* ContextMenu用户手指长按某个View触发的菜单
* 实现场景:用户长按某个List元素,则弹出ContextMenu,选择菜单“Delete”,按下后,弹出AlertDialog,
* 请用户再去确定是否删除,确定后将数据从SQLite中删除,并更新ListView的显示。
* */
//向ListView注册Context Menu,当系统检测到用户长按某单元是,触发Context Menu弹出
registerForContextMenu(listView);
showSQlDate();
findViewById(R.id.btn_sqlAdd).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View arg0) {
add();
}
});
}
// 步骤2:创建ContextMenu同OptionMenu,用户长按元素后,会弹出菜单
public void onCreateContextMenu(ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) {
menu.add(Menu.NONE, DELETE_ID , Menu.NONE, "Delete");
super.onCreateContextMenu(menu, v, menuInfo);
}
//步骤 3: ContextMenu的触发操作,例子将触发delete() 还可以做编辑等待
public boolean onContextItemSelected(MenuItem item) {
switch(item.getItemId()){
case DELETE_ID:
/* 在此处,我们关键引入 AdapterView.AdapterContextMenuInfo来获取单元的信息。
在有三个重要的信息。
1、id:The row id of the item for which the context menu is being displayed ,
在cursorAdaptor中,实际就是表格的_id序号;
2、position 是list的元素的顺序;
3、view就可以获得list中点击元素的View, 通过view可以获取里面的显示的信息
*/
AdapterView.AdapterContextMenuInfo info = (AdapterView.AdapterContextMenuInfo)item.getMenuInfo();
delete(info.id);
return true;
default:
break;
}
return super.onContextItemSelected(item);
}
//步骤4: 对触发弹框,和Add的相似,确定后,更新数据库和更新ListView的显示,上次学习已有相类的例子,不再重复。其中getNameById是通过id查名字的方法。值得注意的是,为了内部类中使用,delete的参数采用来final的形式。
private void delete(final long rowId){
if(rowId>0){
new AlertDialog.Builder(this)
.setTitle("确定要删除?")
.setPositiveButton("确定", new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
deleteData(rowId);
}
})
.setNegativeButton("取消", null)
.show();
}
}
private void deleteData(long rowId){
String[] str = {String.valueOf(rowId)};
helper.getReadableDatabase().delete("url_table", "_id=?", str);
showSQlDate();
}
附件:
源码下载:http://download.csdn.net/detail/i_do_can/9409977