• Android(java)学习笔记148:网易新闻RSS客户端应用编写逻辑过程


    1.我们的项目需求是编写一个新闻RSS浏览器,RSS(Really Simple Syndication)是一种描述和同步网站内容的格式,是使用最广泛的XML应用。RSS目前广泛用于网上新闻频道,blog和wiki,主要的版本有0.91, 1.0, 2.0。使用RSS订阅能更快地获取信息,网站提供RSS输出,有利于让用户获取网站内容的最新更新。网络用户可以在客户端借助于支持RSS的聚合工具软件,在不打开网站内容页面的情况下阅读支持RSS输出的网站内容。

    例如如下的网易RSS订阅

    2.由于我们这里是模拟Android手机APP访问获取服务器里面的数据的工作场景,所以我们这里在电脑上模拟一个服务器环境,这里我们安装Apache(web服务器软件)

           Apache HTTP Server(简称Apache)是Apache软件基金会的一个开放源码的网页服务器,可以在大多数计算机操作系统中运行,由于其多平台和安全性被广泛使用,是最流行的Web服务器端软件之一。它快速、可靠并且可通过简单的API扩展,将Perl/Python等解释器编译到服务器中。[2]
    Apache HTTP服务器是一个模块化的服务器,源于NCSAhttpd服务器,经过多次修改,成为世界使用排名第一的Web服务器软件。
    它可以运行在几乎所有广泛使用的计算机平台上。
     
    3.安装配置好了Apache之后;我们这里编写一个news.xml文件,放在了Apache安装目录下htdocs文件下图片资源放在img文件夹内部,如下:
     
     
     
    打开news.xml文件内容如下:
     
     
    打开img文件夹如下:
    我们开启Apache服务
     
     
     
    打开360浏览器,输入网址http://localhost/news.xml,结果如下:
     
     
    上面页面出现,说明我们通过电脑浏览器访问PC模拟的Apache服务器数据是可以的Apache服务器是配置成功的.
     
     
    4.接下来,我们开始真正开始编写手机Android 网易RSS新闻阅读器如下:
    (1)使用Eclipse新建一个Android工程,命名为" 网易新闻客户端 ",MainActivity.java在com.himi.news包下,如下:
     
     
    (2)我们不着急编写布局文件activity_main.xml文件,我们这里需要使用网络,在相应的网络路径下,获取新闻文件信息。
           • 我们有必要编写一个业务逻辑类去实现获取新闻信息;      (这里就是com.himi.news.net包下的:NewsUtils
           • 获取到新闻信息需要在本地存放起来(写入内存),方便读取新闻信息。所以这里我们也需要编写一个信息数据类存放数据用的;(这里就是com.himi.news.domain包下的:NewsItem
     
    ->1:首先我们编写一个业务工具类去获取新闻信息
     1 package com.himi.news.net;
     2 
     3 import java.io.InputStream;
     4 import java.net.HttpURLConnection;
     5 import java.net.URL;
     6 import java.util.List;
     7 
     8 import org.apache.http.HttpConnection;
     9 
    10 import android.content.Context;
    11 
    12 import com.himi.news.R;
    13 import com.himi.news.domain.NewsItem;
    14 import com.himi.news.service.NewsInfoParser;
    15 
    16 /**
    17  * 新闻的业务类:工具类
    18  * @author Administrator
    19  *
    20  */
    21 
    22 public class NewsUtils {
    23     //设置静态方法,方便外界调用
    24     //获取服务器端网页新闻的信息,这里需要写一个新闻信息数据类,新建包com.himi.news.info,新建类NewsItem存储信息
    25     
    26     /**
    27      * 得到服务器最新的所有的新闻信息,我们这里路径放在res/values/config.xml中
    28      * res资源我们必须使用上下文才能访问,所有getAllNews的参数是Context context
    29      * @param context上下文
    30      * @return
    31      */
    32     public static List<NewsItem> getAllNews(Context context) throws Exception {
    33         String path = context.getResources().getString(R.string.serverip);
    34         
    35         URL url = new URL(path);
    36         HttpURLConnection httpConnection = (HttpURLConnection) url.openConnection();
    37         httpConnection.setRequestMethod("GET");//请求方法为GET方法
    38         httpConnection.setConnectTimeout(5000);//设置连接超时时间
    39         httpConnection.setReadTimeout(5000);//前面是连接,这里读取超时时间
    40         
    41         int code = httpConnection.getResponseCode();
    42         if(code == 200) {
    43             InputStream is = httpConnection.getInputStream();//得到一个服务器端的输入流,代表RSS数据源,也就是获取了news.xml文件
    44             return NewsInfoParser.parseNews(is);
    45         }else  {
    46             return null;    
    47         }
    48         
    49         
    50     }
    51 
    52 }

           上面代码中String path = context.getResources().getString(R.string.serverip);

    其中R.string.serverip对应于res/values/config.xml(通常命名为config.xml,便于理解和维护),config.xml内容如下

    <?xml version="1.0" encoding="utf-8"?>
    <resources>
        <string name="serverip">http://49.123.72.40/news.xml</string>
        
    </resources>

    这里我们编写的是网易新闻客户端测试程序,访问的是PC模拟出来的Apache服务器,所以这里的网址是我个人电脑的IP地址: 49.123.72.40 ,以后我们开发真正的项目则是改为公网IP。这里这个config.xml则是方面我们配置网址的,这里修改网址很方便,而且不容易出错,不这样设置可能在程序中多处修改网址才行,很影响效率

    ->2:上面代码中,我们通过Http协议获得是一个服务器端new.xml文件的输入流:

    InputStream is = httpConnection.getInputStream();很明显这个输入流不是我们想要的,我们通过这个输入流解析这个news.xml文件,这里必然需要编写一个业务类去实现这个解析xml文件:

    这里我们新建一个包com.himi.news.service,包中新建一个类NewsInfoParser,如下:

     1 package com.itheima.news.service;
     2 
     3 import java.io.InputStream;
     4 import java.util.ArrayList;
     5 import java.util.List;
     6 
     7 import org.xmlpull.v1.XmlPullParser;
     8 
     9 import android.util.Xml;
    10 
    11 import com.itheima.news.domain.NewsItem;
    12 
    13 /**
    14  * 新闻信息的解析器 用来解析xml文件
    15  * 
    16  */
    17 public class NewsInfoParser {
    18 
    19     /**
    20      * 解析xml数据流返回 新闻数据
    21      * 
    22      * @param is
    23      * @return
    24      */
    25     public static List<NewsItem> parseNews(InputStream is) throws Exception {
    26         //定义一个新闻的集合,用来存放所有的新闻
    27         List<NewsItem> newsItems = new ArrayList<NewsItem>();    
    28         //代表一条新闻
    29         NewsItem newsItem = null;
    30         XmlPullParser parser = Xml.newPullParser();
    31         // 设置参数
    32         parser.setInput(is, "utf-8");
    33         // 开始解析
    34         int type = parser.getEventType();
    35         while (type != XmlPullParser.END_DOCUMENT) {
    36             switch (type) {
    37             case XmlPullParser.START_TAG://标签开始
    38                 if("item".equals(parser.getName())){
    39                     //一个新闻数据要开始了。
    40                     newsItem = new NewsItem();
    41                 }else if("title".equals(parser.getName())){
    42                     String title = parser.nextText();
    43                     newsItem.setTitle(title);
    44                 }else if("description".equals(parser.getName())){
    45                     String description = parser.nextText();
    46                     newsItem.setDesc(description);
    47                 }else if("image".equals(parser.getName())){
    48                     String image = parser.nextText();
    49                     newsItem.setImage(image);
    50                 }else if("type".equals(parser.getName())){
    51                     String newtype = parser.nextText();
    52                     newsItem.setType(Integer.parseInt(newtype));
    53                 }else if("comment".equals(parser.getName())){
    54                     String comment = parser.nextText();
    55                     newsItem.setComment(Integer.parseInt(comment));
    56                 }
    57                 break;
    58             case XmlPullParser.END_TAG://标签结束
    59                 if("item".equals(parser.getName())){
    60                     newsItems.add(newsItem);
    61                 }
    62                 break;
    63             }
    64             type = parser.next();// 解析下一个事件
    65         }
    66         return newsItems;
    67     }
    68 
    69 }

    下面定义一个方便外界调用的static方法 public static List<NewsItem> parseNews(InputStream is) ;接收参数为之前http协议获得的输入流,返回是,这里需要另外定义一个信息数据类NewsItem用来存放每条新闻。

     

    ->3:我们需要编写一个信息数据类NewsItem存放数据(存放每条新闻信息),如下:

    我们新建一个包com.himi.news.domain,这个包下我们新建一个类为NewsItem

     1 package com.himi.news.domain;
     2 
     3 public class NewsItem {
     4     private String title;
     5     private String desc;
     6     private String image;
     7     private int type;
     8     private int comment;
     9     public String getTitle() {
    10         return title;
    11     }
    12     public void setTitle(String title) {
    13         this.title = title;
    14     }
    15     /**
    16      * 它通常只是为了方便输出,比如System.out.println(xx),
    17      * 括号里面的“xx”如果不是String类型的话,就自动调用xx的toString()方法
    18      * 还有其他所有需要显示字符串的地方全部都需要调用tostring()
    19      */
    20     @Override
    21     public String toString() {
    22         return "NewsItem [title=" + title + ", desc=" + desc + ", image="
    23                 + image + ", type=" + type + ", comment=" + comment + "]";
    24     }
    25     public String getDesc() {
    26         return desc;
    27     }
    28     public void setDesc(String desc) {
    29         this.desc = desc;
    30     }
    31     public String getImage() {
    32         return image;
    33     }
    34     public void setImage(String image) {
    35         this.image = image;
    36     }
    37     public int getType() {
    38         return type;
    39     }
    40     public void setType(int type) {
    41         this.type = type;
    42     }
    43     public int getComment() {
    44         return comment;
    45     }
    46     public void setComment(int comment) {
    47         this.comment = comment;
    48     }
    49 
    50 }

     上面我们已经获得news.xml新闻文件的数据信息,并且单条新闻信息临时存放在NewsItem中,多条新闻信息组合成List集合存放的。返回List<NewsItem>。

    (3)上面我们写了一个复杂的xml解析的业务逻辑类,但是我们不能确定这个业务逻辑类一定是对的,所以我们要编程测试代码测试这个逻辑单元:

    新建一个com.himi.news.test,编写一个TestNewsInfoParser测试上面我们编写的NewsInfoParser,这里同时也需要配置AndroidManifest.xml文件

    这里我们把news.xml文件复制到Android工程目录下的assets文件夹下,在测试代码中调用这个news.xml文件进行解析:代码如下: 

     1 package com.himi.news.test;
     2 
     3 import java.util.List;
     4 
     5 import android.test.AndroidTestCase;
     6 
     7 import com.himi.news.domain.NewsItem;
     8 import com.himi.news.service.NewsInfoParser;
     9 
    10 public class TestNewsInfoParser extends AndroidTestCase {
    11     public void testparseNews() throws Exception {
    12         // 这里是测试parseNews方法解析xml文件的效果,复制news.xml文件到工程assets目录下测试(测试时期:不从服务器获取)
    13         List<NewsItem> items = NewsInfoParser.parseNews(getContext()
    14                 .getAssets().open("news.xml"));// 获取assets文件夹资源需要上下文,这里API提供一个getContext()获取一个虚拟上下文
    15         for (NewsItem item : items) {
    16             System.out.println(item);
    17         }
    18 
    19     }
    20 
    21 }

    测试结果说明业务逻辑是正确的;至于具体测试流程 详见  Android(java)学习笔记165:Android的Junit调试

     (4)接下来,我们去完成布局UI设计:

     由于我们希望同时显示多条新闻数据,所以这里我们主布局文件activity_main.xml中是使用ListView,如下:

    <RelativeLayout 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:paddingBottom="@dimen/activity_vertical_margin"
        android:paddingLeft="@dimen/activity_horizontal_margin"
        android:paddingRight="@dimen/activity_horizontal_margin"
        android:paddingTop="@dimen/activity_vertical_margin"
        tools:context="com.himi.news.MainActivity" >
    
        <ListView
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:id="@+id/lv" />
    
    </RelativeLayout>

    这里我们需要使用Android(java)学习笔记204自定义SmartImageView:(这里的SmartImageView是我们自己定义的,真实项目开发的时候我们可以利用开源项目

     1 package com.himi.news.ui;
     2 
     3 import java.io.InputStream;
     4 import java.net.HttpURLConnection;
     5 import java.net.URL;
     6 
     7 import com.himi.news.R;
     8 
     9 import android.content.Context;
    10 import android.graphics.Bitmap;
    11 import android.graphics.BitmapFactory;
    12 import android.os.Handler;
    13 import android.os.Message;
    14 import android.util.AttributeSet;
    15 import android.widget.ImageView;
    16 
    17 /**
    18  * 实现一个子类,扩展系统的ImageView
    19  * @author Administrator
    20  *
    21  */
    22 public class SmartImageView extends ImageView {
    23     
    24     private static final int SUCCESS = 1;
    25     private Handler handler = new Handler() {
    26         public void handleMessage(android.os.Message msg) {
    27             switch (msg.what) {
    28             case SUCCESS:
    29                 Bitmap bitmap = (Bitmap) msg.obj;
    30                 setImageBitmap(bitmap);
    31                 break;
    32 
    33             default:
    34                 //其他消息,都是获取图片失败,加载默认失败图片
    35                 setImageResource(R.drawable.error);
    36                 break;
    37             }
    38 
    39         };
    40     };
    41 
    42     public SmartImageView(Context context, AttributeSet attrs, int defStyle) {
    43         super(context, attrs, defStyle);
    44         // TODO 自动生成的构造函数存根
    45     }
    46 
    47     public SmartImageView(Context context, AttributeSet attrs) {
    48         super(context, attrs);
    49         // TODO 自动生成的构造函数存根
    50     }
    51 
    52     public SmartImageView(Context context) {
    53         super(context);
    54         // TODO 自动生成的构造函数存根
    55     }
    56     
    57     /**
    58      * 设置一个网络的路径给imageview,imageview会自动的把这个路径对应的图片下载下来
    59      * @param path 图片的路径
    60      */
    61 
    62     public void  setImageUrl(final String path) {
    63         new Thread() {
    64             public void run() {
    65                 try {
    66                     URL url = new URL(path);
    67                     HttpURLConnection conn = (HttpURLConnection) url.openConnection();
    68                     conn.setConnectTimeout(5000);
    69                     conn.setReadTimeout(5000);
    70                     conn.setRequestMethod("GET");
    71                     int code = conn.getResponseCode();
    72                     if(code ==200) {
    73                         InputStream is = conn.getInputStream();//获得服务器端的图片文件的输入流
    74                         Bitmap bitmap = BitmapFactory.decodeStream(is);//将服务器端的图片文件的输入流 转化为 Bitmap图片文件
    75                         //setImageBitmap(bitmap);子线程不能更新UI,这里要使用消息机制
    76                         Message msg = Message.obtain();
    77                         msg.obj = bitmap;
    78                         msg.what = SUCCESS;
    79                         handler.sendMessage(msg);
    80                     }else {
    81                         handler.sendEmptyMessage(0);
    82                     }
    83                 } catch (Exception e) {
    84                     e.printStackTrace();
    85                     handler.sendEmptyMessage(0);
    86                 }
    87             
    88                 
    89             };
    90         }.start();
    91     }
    92     
    93 }

    如果我们加载新闻数据信息,如果获取的图片路径不对的,现实生活中我们应该是默认显示一个" 加载失败 "的图片如下:

    这里我们自己自定义的SmartImageView往往不够成熟,考虑不够周全,幸运的时候我们在网上可以利用开源项目很方便获取非常成熟的SmartImageView开源代码

    每条新闻item的布局,自己定义item.xml,如下:

    新建一个包com.himi.news.ui,复制之前Android(java)学习笔记204中自定义的SmartImageView到这个包下:

    item.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="wrap_content" >
    
        <com.himi.news.ui.SmartImageView
            android:id="@+id/iv_item"
            android:layout_width="100dip"
            android:layout_height="80dip" />
    
        <TextView
            android:id="@+id/tv_item_title"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginLeft="5dip"
            android:layout_marginTop="5dip"
            android:layout_toRightOf="@id/iv_item"
            android:singleLine="true"
            android:text="我是标题"
            android:textColor="#000000"
            android:textSize="19sp" />
    
        <TextView
            android:id="@+id/tv_item_desc"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_below="@id/tv_item_title"
            android:layout_marginLeft="5dip"
            android:layout_marginTop="1dip"
            android:layout_toRightOf="@id/iv_item"
            android:lines="2"
            android:text="我是描述,我们都是好孩子"
            android:textColor="#AA000000"
            android:textSize="14sp" />
    
        <TextView
            android:id="@+id/tv_item_type"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginTop="2dip"
            android:layout_alignParentRight="true"
            android:layout_below="@id/tv_item_desc"
            android:background="#ff0000"
            android:text="直播" />
    
    </RelativeLayout>

    这里自定义的SmartImageView可以自动获取相应网络路径下的图片,更符合我们的需求,所以我们自定义扩展这个ImageView为SmartImageView。

     

     (5)回到MainActivity.java:

      1 package com.himi.news;
      2 
      3 import java.util.List;
      4 
      5 import android.app.Activity;
      6 import android.graphics.Color;
      7 import android.os.Bundle;
      8 import android.os.Handler;
      9 import android.os.Message;
     10 import android.view.View;
     11 import android.view.ViewGroup;
     12 import android.widget.BaseAdapter;
     13 import android.widget.ImageView;
     14 import android.widget.ListView;
     15 import android.widget.TextView;
     16 import android.widget.Toast;
     17 
     18 import com.himi.news.domain.NewsItem;
     19 import com.himi.news.net.NewsUtils;
     20 import com.himi.news.ui.SmartImageView;
     21 
     22 public class MainActivity extends Activity {
     23     protected static final int SUCCESS = 0;
     24     protected static final int ERROR = 1;
     25     private ListView lv;
     26     private Handler handler = new Handler() {
     27         public void handleMessage(android.os.Message msg) {
     28             switch (msg.what) {
     29             case SUCCESS:
     30                 lv.setAdapter(new NewsAdapter());
     31                 break;
     32 
     33             case ERROR:
     34                 Toast.makeText(MainActivity.this, "请求失败,获取失败", 0).show();
     35                 break;
     36             }
     37             
     38         };
     39     };
     40     
     41     /**
     42      * 所有的新闻信息
     43      */
     44     
     45     private List<NewsItem> newsItems;
     46     @Override
     47     protected void onCreate(Bundle savedInstanceState) {
     48         super.onCreate(savedInstanceState);
     49         setContentView(R.layout.activity_main);
     50         
     51         lv = (ListView)findViewById(R.id.lv);
     52         
     53         new Thread() {
     54             public void run() {
     55                 try {
     56                     newsItems = NewsUtils.getAllNews(MainActivity.this);
     57                     Message msg = Message.obtain();
     58                     msg.what = SUCCESS;
     59                     handler.sendMessage(msg);
     60                 } catch (Exception e) {
     61                     // TODO 自动生成的 catch 块
     62                     e.printStackTrace();
     63                     Message msg = Message.obtain();
     64                     msg.what = ERROR;
     65                     handler.sendMessage(msg);
     66                 }
     67             };
     68         }.start();
     69     }
     70 
     71     
     72     private class NewsAdapter extends BaseAdapter {
     73 
     74         public int getCount() {
     75             return newsItems.size();
     76         }
     77 
     78         public View getView(int position, View convertView, ViewGroup parent) {
     79             View view ;
     80             if(convertView == null) {
     81                 view = View.inflate(MainActivity.this, R.layout.item, null);
     82             }else {
     83                 view = convertView;
     84             }
     85             
     86             SmartImageView iv = (SmartImageView) view.findViewById(R.id.iv_item);
     87             TextView tv_title = (TextView) view.findViewById(R.id.tv_item_title);
     88             TextView tv_desc = (TextView) view.findViewById(R.id.tv_item_desc);
     89             TextView tv_type = (TextView) view.findViewById(R.id.tv_item_type);
     90             NewsItem item = newsItems.get(position);
     91             tv_title.setText(item.getTitle());
     92             tv_desc.setText(item.getDesc());
     93             int type = item.getType();
     94             if(type==1) {
     95                 tv_type.setText("评论:"+item.getComment());
     96             }else if(type==2) {
     97                 tv_type.setText("直播");
     98                 tv_type.setBackgroundColor(Color.RED);
     99             }else if(type==3) {
    100                 tv_type.setText("视频");
    101                 tv_type.setBackgroundColor(Color.BLUE);
    102             }
    103             
    104             return view;
    105         }
    106 
    107         public Object getItem(int position) {
    108             return null;
    109         }
    110 
    111         public long getItemId(int position) {
    112             return 0;
    113         }
    114 
    115         
    116     }
    117 }

     

    (6)布署程序到模拟器上,效果如下:

    (7)最后我们模拟出一个场景,当我们关闭Apache服务器的时候,我们的手机网易新闻客户端是不能再去获得服务器资源的,如下:

    这个时候我们再次进入程序,效果如下:

    这里访问请求失败的时候,我们是使用Toast土司提示信息,显示项目开发的时候,会现实开发项目中,我们事先设置好的访问失败的提示网页拿来显示

  • 相关阅读:
    移动端,多屏幕尺寸高清屏retina屏适配的解决方案
    angular 关于 factory、service、provider的相关用法
    2016最新手机号码正则、身份证JS正则表达式
    凸包总结
    BZOJ 3653: 谈笑风生(DFS序+可持久化线段树)
    BZOJ 3652: 大新闻(数位DP+概率论)
    BZOJ 1062: [NOI2008]糖果雨(二维树状数组)
    纪中集训 Day 8 & Last Day
    纪中集训 Day 7
    纪中集训 Day 6
  • 原文地址:https://www.cnblogs.com/hebao0514/p/4779505.html
Copyright © 2020-2023  润新知