简易的Android新闻客户端

作者: 宝塔山上的猫 | 来源:发表于2016-05-27 14:22 被阅读5152次

    学完Android基础之后不知道该怎么办?现在开始实战吧!
    现在来看看一款简易的Android新闻客户端是怎么做的,当然,获取网络数据的这一部分我是使用别人做好的本地客户端,然后通过组建本地数据库来使用的,这一部分我就不详细介绍了。
    本文Demo的地址
    https://github.com/liaozhoubei/NetNewsDemo

    本篇文章的技术要点:

    • ListView适配器的使用
    • Handler的消息发送
    • SQLite的使用
    • JSon数据的解析
    • 网络与IO流的使用

    做个简易新闻客户端的难度并不大,只要想通了其中的几个要点,做起来就很简单。

    制作流程

    1、首先需要确定一个listView的布局
    2、然后找到listView,设置条目的点击事件
    3、获取网络数据给listView做展示
    4、创建一个BaseAdapter的子类,接收获取的新闻数据
    5、将adapter设置给ListView
    现在让我们开始做这个新闻客户端吧!

    客户端布局

    我们先看看说要获取的JSon数据中拥有什么条目:

    {
        "newss": [
            {
                "id": 2,
                "time": "2015-08-07",
                "des": "7月29日,历经9个月数百万人内测完善之后,微软终于发布Win10正式版系统。但是可能对于部分用户而言,Win7仍然是绝对的经典、游戏玩家的不二之选,为何非要升级到Win10系统呢?Windows10性能和功能相比Windows7,有提升吗?下面IT之家就为大家带来Win7与Win10功能与性能的正面PK,相信还在犹豫不决的用户看完本文心里就会有了答案。",
                "title": "升还是不升:Win7、Win10全面对比评测",
                "news_url": "http://toutiao.com/a5229867988/",
                "icon_url": "http://p2.pstatp.com/large/6850/6105376239",
                "comment": 5000,
                "type": 1
            }
        ]
    }
    

    OK,我们发现JSon新闻数据中要包含这几个类别:

    • id:新闻的ID号
    • time:新闻发布的时间
    • des: 新闻的内容
    • time:新闻的标题
    • news_url:新闻的链接地址
    • icon_url:新闻图片的链接地址
    • comment: 新闻的评论数
    • type:新闻是属于那种类别,0 是头条新闻, 1为娱乐新闻,2为体育新闻

    因此我们的布局界面应该如下:


    news.png

    在开始写ListView之前我们需要先写出获取新闻的bean对象,每个对象都有get和set方法,由于篇幅有限就省略了一部分,代码如下:

    public class NewsBean {
    private int id;
    private int comment;
    private int type;
    private String time;
    private String title;
    private String news_url;
    private String icon_url;
    private String des;
    public int getId() {
        return id;
    }
    public void setId(int id) {
        this.id = id;
    }
    ·····
    }
    

    然后是ListView每个Item的布局,相信布局这方面的内容难不倒大家,所以也缩写了一部分如下

    <?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="horizontal" >
    
    <com.example.netnewsdemo.myview.MyImageView
        android:id="@+id/item_img_icon"
        ··· />
    
    <LinearLayout
       ···
        android:orientation="vertical" >
    
        <TextView
            android:id="@+id/item_tv_title"
            ··· />
    
        <TextView
            android:id="@+id/item_tv_des"
           ···/>
    
        <RelativeLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_gravity="center" >
    
            <TextView
                android:id="@+id/item_tv_comment"
              ···/>
    
            <TextView
                android:id="@+id/item_tv_type"
               ··· />
        </RelativeLayout>
    </LinearLayout>
    </LinearLayout>
    

    其中有个<com.example.netnewsdemo.myview.MyImageView>的控件,这个是自定义的从网络中获取图片的视图控件。
    然后我们要设置adapter适配器放置到ListView中去,所以我们先去完成适配器吧!

    NewsAdapter

    继承BaseAdapter的NewsAdapter需要有这两个成员变量

        private LayoutInflater mLayoutInflater;
        private List<NewsBean> mDatas; // 获取到的新闻集合
    

    还需要一个构造器获取传递过来的数据

        public NewsAdapter(Context context, List<NewsBean> listNewsBean){
        this.mLayoutInflater = LayoutInflater.from(context);
        this.mDatas = listNewsBean;
    }
    

    然后在重写继承的4个方法

        @Override
    public int getCount() {
        return mDatas.size();
    }
    
    @Override
    public Object getItem(int position) {
        return mDatas.get(position);
    }
    
    @Override
    public long getItemId(int position) {
        return position;
    }
    
    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        ViewHolder viewHolder;
        if (convertView == null) {
            convertView = mLayoutInflater.inflate(R.layout.layout_item, null);
            viewHolder = new ViewHolder();
            viewHolder.item_img_icon = (MyImageView) convertView.findViewById(R.id.item_img_icon);;
            viewHolder.item_tv_des = (TextView) convertView.findViewById(R.id.item_tv_des);
            viewHolder.item_tv_title = (TextView) convertView.findViewById(R.id.item_tv_title);
            viewHolder.item_tv_comment = (TextView) convertView.findViewById(R.id.item_tv_comment);
            viewHolder.item_tv_type = (TextView) convertView.findViewById(R.id.item_tv_type);
            convertView.setTag(viewHolder);
        } else{
            viewHolder = (ViewHolder) convertView.getTag();
        }
        NewsBean newsBean= mDatas.get(position);
        viewHolder.item_img_icon.setImageUrl(newsBean.getIcon_url());
        viewHolder.item_tv_des.setText(newsBean.getDes());
        viewHolder.item_tv_title.setText(newsBean.getTitle());
        viewHolder.item_tv_comment.setText(newsBean.getComment() + "");
        //0 :头条 1 :娱乐 2.体育
        switch (newsBean.getType()) {
        case 0:
            viewHolder.item_tv_type.setText("头条");
            break;
        case 1:
            viewHolder.item_tv_type.setText("娱乐 ");
            break;
        case 2:
            viewHolder.item_tv_type.setText("体育");
            break;
        default:
            break;
        }
        return convertView;
    }
    class ViewHolder{
        MyImageView item_img_icon;
        TextView item_tv_des;
        TextView item_tv_title;
        TextView item_tv_comment;
        TextView item_tv_type;
    }
    

    这样整个ListView的adapter对象就弄好了,现在只需要在activity_main.xml中加入ListView视图,然后在MainActivity.java绑定ListView就可以了。

    自定义视图

    布局现在还没写好,因为我们还有一个获取网络图片的自定义视图没搞好,现在来解决它吧。
    自定义视图继承自ImageView,需要继承两个构造方法:

    public MyImageView(Context context) {
        super(context);
    }
    
    public MyImageView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }
    

    我们从json数据中获取的只是网络图片的url地址,因此我们还需要将网络流转换为地址,然后通过Message将图片发送到主线程中,以避免在子线程更新UI线程

        private Handler mHandler = new Handler() {
          public void handleMessage(android.os.Message msg) {
            Bitmap bitmap = (Bitmap) msg.obj;
            MyImageView.this.setImageBitmap(bitmap);
        };
    };
    public void setImageUrl(final String urlString) {
        new Thread(new Runnable() {
    
            @Override
            public void run() {
                try {
                    URL url = new URL(urlString);
                    HttpURLConnection conn = (HttpURLConnection) url.openConnection();
                    conn.setRequestMethod("GET");
                    conn.setConnectTimeout(5 * 1000);
                    int code = conn.getResponseCode();
                    if (code == 200) {
                        InputStream is = conn.getInputStream();
                        Bitmap bitmap = BitmapFactory.decodeStream(is);
                        Message message = Message.obtain();
                        message.obj = bitmap;
                        mHandler.sendMessage(message);
                    }
                    
    
                } catch (Exception e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
            }
        }).start();;
    
    }    
    

    详细的MyImageView类还是看我的项目代码吧,哈哈!

    好了,现在整个视图的布局就完成了,剩下的就是将获取网络数据放在适配器中就可以了

    获取网络数据

    在获取网络数据之前,我们先编写一个工具类,将InputStream流直接转换为String字符串输出的工具类,代码如下:

    public class StreamUtils {
    public static String convertStream(InputStream is) {
        String result = "";
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        byte[] bt = new byte[1024];
        int len = 0;
        try {
            while ((len = is.read(bt)) != -1) {
                bos.write(bt, 0, len);
                bos.flush();
            }
            result = new String(bos.toByteArray(), "utf-8");
            result = bos.toString();
            bos.close();
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }       
        return result;      
    }
    }
    

    现在终于可以正式的获取网络中的json数据了,这个类中写一个getNetNews方法,传出Context和URL参数,返回ArrayList<NewsBean>集合类型。
    对于JSon数据,我们研究可以得知,它是一个JSONObject,里面包含一个newss的JSONArray数组,数组里面在包含着一个个新闻对象。
    整个获取网络数据并解析json数据的方法如下:

        public static ArrayList<NewsBean> getNetNews(Context context, String urlString) {
        ArrayList<NewsBean> arraylistNews = new ArrayList<NewsBean>();
        try {
            URL url = new URL(urlString);
            HttpURLConnection conn = (HttpURLConnection) url.openConnection();
            conn.setRequestMethod("GET");
            conn.setConnectTimeout(20 * 1000);
            int responseCode = conn.getResponseCode();          
            if (responseCode == 200) {
                // 获取请求到的流信息
                InputStream is = conn.getInputStream();
                // 通过之前的建立的StreamUtils工具类转换流信息
                String result = StreamUtils.convertStream(is);
                JSONObject root_json = new JSONObject(result);
                JSONArray jsonArray  = root_json.getJSONArray("newss");
                for (int i = 0; i < jsonArray .length(); i ++ ){
                    JSONObject news_json = jsonArray.getJSONObject(i);
                    NewsBean newsBean = new NewsBean();
                    newsBean.setId(news_json.getInt("id"));
                    newsBean.setTime(news_json.getString("time"));
                    newsBean.setDes(news_json.getString("des"));
                    newsBean.setTitle(news_json.getString("title"));
                    newsBean.setNews_url(news_json.getString("news_url"));
                    newsBean.setIcon_url(news_json.getString("icon_url"));
                    newsBean.setComment(news_json.getInt("comment"));
                    newsBean.setType(news_json.getInt("type"));
                    arraylistNews.add(newsBean);                    
                }           
                is.close();             
            }           
        } catch (Exception e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        return arraylistNews;
    }
    

    补全MainActivity

    所有的工具都写完了,我们可以返回补全MainAcitivty了,在MainActivity中有个Handler,它将网络中返回的json数据反正NewsAdapter总,然后更新ListView中的数据。

        private static String result = "傳入包含Json數據的網頁URL";
        private Handler mHandler = new Handler() {
        public void handleMessage(android.os.Message msg) {
            listNewsBean = (List<NewsBean>) msg.obj;
            NewsAdapter newsAdapter = new NewsAdapter(MainActivity.this, listNewsBean);
            listview.setAdapter(newsAdapter);
        };
    };
        @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mContext = MainActivity.this;
        listview = (ListView) findViewById(R.id.list_news);
        newsUtils = new NewsUtils();
        new Thread(new Runnable() {
            @Override
            public void run() {
                listNewsBean = newsUtils.getNetNews(mContext, result);
                Message message = Message.obtain();
                message.obj = listNewsBean;
                mHandler.sendMessage(message);
            }
        }).start();     
        listview.setOnItemClickListener(this); // 设置listView的点击事件,略过
    
    }
    

    基本的新闻客户端设置就到此结束了,但是这只是一个非常简单的客户端,如果是真实的新闻客户端还有很多事情需要做,如设置新闻缓存,当客户端没有联网的时候从数据库中获取新闻等。
    现在也顺便写一下如何用数据库缓存新闻吧!

    使用SQLite数据库缓存新闻。

    使用SQLite需要一个SQLiteOpenHelper类,然后自己封装一个SQLite操作类。
    SQLiteOpenHelper类用于建立数据库,并且设置数据库的列表形式,SQLite操作类这是负责数据库的增删查改等工作。
    SQLiteOpenHelper类代码如下:

    public class NewsDBHelper extends SQLiteOpenHelper{
    
    public NewsDBHelper(Context context) {
        super(context, "NetNews", null, 1);
    }
    
    @Override
    public void onCreate(SQLiteDatabase db) {
        
        db.execSQL("create table news (_id integer, title varchar(200), des varchar(300), "
                + "icon_url varchar(100), news_url varchar(200), type integer, time varchar(100), comment integer)");
    }
    
    @Override
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
        // TODO Auto-generated method stub      
    }
    }
    

    SQLite操作类代码如下:

    public class NewsDBUtils {
    private NewsDBHelper dbHelper;  
    public NewsDBUtils (Context context) {
        dbHelper = new NewsDBHelper(context);
    }   
    // 保存新闻到数据库中
    public void saveNews(ArrayList<NewsBean> arrayList) {
        SQLiteDatabase sqLite = dbHelper.getWritableDatabase();
        for(NewsBean newsBean : arrayList) {
            ContentValues value = new ContentValues();
            value.put("_id", newsBean.getId());
            value.put("time", newsBean.getTime());
            value.put("des", newsBean.getDes());
            value.put("title", newsBean.getTitle());
            value.put("news_url", newsBean.getNews_url());
            value.put("icon_url", newsBean.getIcon_url());
            value.put("comment", newsBean.getComment());
            value.put("type", newsBean.getType());
            sqLite.insert("news", null, value);
        }
        sqLite.close();
    }   
    
    // 删除数据库数据
    public void deleteNews (){
        SQLiteDatabase db = dbHelper.getReadableDatabase();
        db.delete("news", null, null);
        db.close();
    }
    
    // 从数据库中获取存储的行为
    public ArrayList<NewsBean> getNews() {
        ArrayList<NewsBean> arrayList = new ArrayList<NewsBean>();
        SQLiteDatabase db = dbHelper.getReadableDatabase();
        Cursor cursor = db.query("news", null, null, null, null, null, null, null);
        if (cursor != null && cursor.getCount() > 0) {
            while (cursor.moveToNext()) {
                NewsBean newsBean = new NewsBean();
                newsBean.setId(cursor.getInt(0));
                newsBean.setTime(cursor.getString(1));
                newsBean.setDes(cursor.getString(2));
                newsBean.setTitle(cursor.getString(3));
                newsBean.setNews_url(cursor.getString(4));
                newsBean.setIcon_url(cursor.getString(5));
                newsBean.setComment(cursor.getInt(6));
                newsBean.setType(cursor.getInt(7));
                arrayList.add(newsBean);
            }
        }
        
        return arrayList;
    }
    }
    

    返回修改获取网络数据类NetUtils和MainActivity类

    修改NetUtils在getNetNews的InpuStream流关闭之前(is.close())添加两行代码:

    // 如果获取到网络上的数据,就删除之前获取的新闻数据,保存新的新闻数据
                new NewsDBUtils(context).deleteNews();
                new NewsDBUtils(context).saveNews(arraylistNews);
    

    然后给NetUtils添加一个获取数据库缓存新闻的方法:

    // 返回数据库缓存到的数据
    public static ArrayList<NewsBean> getDBNews(Context context){
        
        return new NewsDBUtils(context).getNews();
    }
    

    OK,最后就是修改MainActivity中的代码了,我们需要在onCreate()开启新线程,获取网络数据之前,从数据库中获取到之前缓存的新闻,这样才不会在网速缓慢的时候界面空白一片,增加一些代码:

            // 1.先去数据库中获取缓存的新闻数据展示到listview
        ArrayList<NewsBean> allnews_database = NewsUtils.getDBNews(mContext);
    
        if (allnews_database != null && allnews_database.size() > 0) {
            // 创建一个adapter设置给listview
            NewsAdapter newsAdapter = new NewsAdapter(mContext, allnews_database);
            listview.setAdapter(newsAdapter);
        }
    

    总结

    一个简易的网络新闻客户端的制作流程就写到这里,可能凌乱了一下,但是结合我在github的代码还是能够看明白的。
    另外如果需要这个新闻客户端获取本地web服务方面的代码,也可以联系我。
    最后就是如果哪位高手发现我的代码还有改进的地方,还请不吝赐教~~

    相关文章

      网友评论

      • 山有扶苏_6bbb:运行的时候为什么没有界面呀,模拟器一片空白
        宝塔山上的猫:@山有扶苏_6bbb 你的服务端没有配置好吧~_~
      • b662bfd8c88e:老哥我想问下你又没关于android 调用Web服务的文章
        宝塔山上的猫:@PerfectLif_258f 通过webview调用js实习
      • fef222330142:题主方便留个联系方式吗,我毕设要做新闻客户端,
      • Neulana:题主你好,请问你新闻数据是从哪里请求的?有开源的API吗?今日头条还是网易新闻的?
        宝塔山上的猫:@加速度猫 这里的新闻数据我是组件本地网络提供的,安装一个tomcat,然后自己写一个json数据就可以了。
        我这里只是一个简化版的新闻客户端,如果你想要获取真实的新闻数据源,也是有免费的API使用的,你直接搜索 “百度api市场” 就可以了
      • 黑色扶手带:不错,基础扎实
      • AD小徐:非常不错的文章,作者能否提供一个可用的url用来测试数据?
        AD小徐:@宝塔山上的猫 thanks :blush:
        宝塔山上的猫:@小徐_ http://pan.baidu.com/s/1o8iZukU,使用方法也写好了

      本文标题:简易的Android新闻客户端

      本文链接:https://www.haomeiwen.com/subject/oaaxdttx.html