android之访问网络获取网页数据并存入数据库
工具:android studio
通过调用 HttpURLConnection 来获取网页数据,并将网络连接操作部署到异步线程中执行。为了实现这一目标,请创建一个 AsyncTask 来完成相应的网络请求。
该类基于one-way网络的实现方法在publicAction中执行。
完成后的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);
    
     }
    
     }
        通常情况下我们查看的URL地址都是存在重定向的情况。这个问题相对容易解决。通过递归实现或者利用URLConnection提供的功能来处理这种情况,并最终解决问题并得到真实地址
  /** * 直接将重定向交给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 文件以实现功能。
当网页不是重定向时,则使用js进行跳转。我也曾用过该工具用于网页管理,并非专门功能模块开发。例如一个简单的网站登录流程就可以实现数据同步获取。
   <body>
    
  <span style="white-space:pre">	</span><script>	var jump_url="http://active.cliim.cn/AJ8nLm?e1baa1462641130bb5295595b173123b2237fe5268";
    
   	        window.location.href=jump_url;
    
   	        // setTimeout(function(){
    
   	        // 	window.location.href=jump_url;
    
   	        // }, 500 );
    
   	</script>
    
  </body>
        那我们就确实需要读取网页数据了。为了存储这些数据以便后续处理和查看,我们可以将它们存储在一个StringBuffer对象中,并对在线获取的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);
    
     }
    
     }
        除了之外的部分主要是从网页源代码中提取数据。常见的做法包括对字符串进行各种操作。例如,在实际应用中经常使用的操作有:字符串截取、字符定位等。
    <span style="white-space:pre">	</span>tempHTML.substring(tempHTML.indexOf("***"),tempHTML.lastIndexOf("***") + 4);
        获取相要的结果在listview 里显示
 protected void onPostExecute(StringBuffer result) {
    
     if(result != null){
    
 //            ArrayList ls =  new ArrayList<String>(){ { add("str01");  add("str02"); } };
    
         ArrayList ls =  new ArrayList<String>();
    
         ls.add("ClientVersion: android 1.1.1");
    
         ls.add("获取url网页数据");
    
         doHtmlGetRealUrl(result, ls);
    
         for(int i = 0; i<ls.size();i++) {
    
             helper.getReadableDatabase().execSQL("insert into url_table values(null, ?)",
    
                     new String[]{ls.get(i).toString()});
    
  
    
         }
    
  
    
         listview.setAdapter(new ArrayAdapter<String>(context, android.R.layout.simple_list_item_1, ls));
    
     }
    
     super.onPostExecute(result);
    
     }
        在上述循环中,在每次迭代时除了将数据加入列表之外,在每次循环中还会向数据库中插入数据;随后创建了一个名为UrlInfoDatabaseHelper的新类,并继承自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();
    
     }
        附件:
源码下载:<>
