學(xué)完Android基礎(chǔ)之后不知道該怎么辦?現(xiàn)在開始實(shí)戰(zhàn)吧!
現(xiàn)在來看看一款簡易的Android新聞客戶端是怎么做的,當(dāng)然,獲取網(wǎng)絡(luò)數(shù)據(jù)的這一部分我是使用別人做好的本地客戶端,然后通過組建本地?cái)?shù)據(jù)庫來使用的,這一部分我就不詳細(xì)介紹了。
本文Demo的地址
https://github.com/liaozhoubei/NetNewsDemo
本篇文章的技術(shù)要點(diǎn):
- ListView適配器的使用
- Handler的消息發(fā)送
- SQLite的使用
- JSon數(shù)據(jù)的解析
- 網(wǎng)絡(luò)與IO流的使用
做個(gè)簡易新聞客戶端的難度并不大,只要想通了其中的幾個(gè)要點(diǎn),做起來就很簡單。
制作流程
1、首先需要確定一個(gè)listView的布局
2、然后找到listView,設(shè)置條目的點(diǎn)擊事件
3、獲取網(wǎng)絡(luò)數(shù)據(jù)給listView做展示
4、創(chuàng)建一個(gè)BaseAdapter的子類,接收獲取的新聞數(shù)據(jù)
5、將adapter設(shè)置給ListView
現(xiàn)在讓我們開始做這個(gè)新聞客戶端吧!
客戶端布局
我們先看看說要獲取的JSon數(shù)據(jù)中擁有什么條目:
{
"newss": [
{
"id": 2,
"time": "2015-08-07",
"des": "7月29日,歷經(jīng)9個(gè)月數(shù)百萬人內(nèi)測完善之后,微軟終于發(fā)布Win10正式版系統(tǒng)。但是可能對于部分用戶而言,Win7仍然是絕對的經(jīng)典、游戲玩家的不二之選,為何非要升級到Win10系統(tǒng)呢?Windows10性能和功能相比Windows7,有提升嗎?下面IT之家就為大家?guī)鞼in7與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,我們發(fā)現(xiàn)JSon新聞數(shù)據(jù)中要包含這幾個(gè)類別:
- id:新聞的ID號
- time:新聞發(fā)布的時(shí)間
- des: 新聞的內(nèi)容
- time:新聞的標(biāo)題
- news_url:新聞的鏈接地址
- icon_url:新聞圖片的鏈接地址
- comment: 新聞的評論數(shù)
- type:新聞是屬于那種類別,0 是頭條新聞, 1為娛樂新聞,2為體育新聞
因此我們的布局界面應(yīng)該如下:

在開始寫ListView之前我們需要先寫出獲取新聞的bean對象,每個(gè)對象都有g(shù)et和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每個(gè)Item的布局,相信布局這方面的內(nèi)容難不倒大家,所以也縮寫了一部分如下
<?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>
其中有個(gè)<com.example.netnewsdemo.myview.MyImageView>的控件,這個(gè)是自定義的從網(wǎng)絡(luò)中獲取圖片的視圖控件。
然后我們要設(shè)置adapter適配器放置到ListView中去,所以我們先去完成適配器吧!
NewsAdapter
繼承BaseAdapter的NewsAdapter需要有這兩個(gè)成員變量
private LayoutInflater mLayoutInflater;
private List<NewsBean> mDatas; // 獲取到的新聞集合
還需要一個(gè)構(gòu)造器獲取傳遞過來的數(shù)據(jù)
public NewsAdapter(Context context, List<NewsBean> listNewsBean){
this.mLayoutInflater = LayoutInflater.from(context);
this.mDatas = listNewsBean;
}
然后在重寫繼承的4個(gè)方法
@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;
}
這樣整個(gè)ListView的adapter對象就弄好了,現(xiàn)在只需要在activity_main.xml中加入ListView視圖,然后在MainActivity.java綁定ListView就可以了。
自定義視圖
布局現(xiàn)在還沒寫好,因?yàn)槲覀冞€有一個(gè)獲取網(wǎng)絡(luò)圖片的自定義視圖沒搞好,現(xiàn)在來解決它吧。
自定義視圖繼承自ImageView,需要繼承兩個(gè)構(gòu)造方法:
public MyImageView(Context context) {
super(context);
}
public MyImageView(Context context, AttributeSet attrs) {
super(context, attrs);
}
我們從json數(shù)據(jù)中獲取的只是網(wǎng)絡(luò)圖片的url地址,因此我們還需要將網(wǎng)絡(luò)流轉(zhuǎn)換為地址,然后通過Message將圖片發(fā)送到主線程中,以避免在子線程更新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();;
}
詳細(xì)的MyImageView類還是看我的項(xiàng)目代碼吧,哈哈!
好了,現(xiàn)在整個(gè)視圖的布局就完成了,剩下的就是將獲取網(wǎng)絡(luò)數(shù)據(jù)放在適配器中就可以了
獲取網(wǎng)絡(luò)數(shù)據(jù)
在獲取網(wǎng)絡(luò)數(shù)據(jù)之前,我們先編寫一個(gè)工具類,將InputStream流直接轉(zhuǎn)換為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;
}
}
現(xiàn)在終于可以正式的獲取網(wǎng)絡(luò)中的json數(shù)據(jù)了,這個(gè)類中寫一個(gè)getNetNews方法,傳出Context和URL參數(shù),返回ArrayList<NewsBean>集合類型。
對于JSon數(shù)據(jù),我們研究可以得知,它是一個(gè)JSONObject,里面包含一個(gè)newss的JSONArray數(shù)組,數(shù)組里面在包含著一個(gè)個(gè)新聞對象。
整個(gè)獲取網(wǎng)絡(luò)數(shù)據(jù)并解析json數(shù)據(jù)的方法如下:
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工具類轉(zhuǎn)換流信息
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;
}
補(bǔ)全MainActivity
所有的工具都寫完了,我們可以返回補(bǔ)全MainAcitivty了,在MainActivity中有個(gè)Handler,它將網(wǎng)絡(luò)中返回的json數(shù)據(jù)反正NewsAdapter總,然后更新ListView中的數(shù)據(jù)。
private static String result = "傳入包含Json數(shù)據(jù)的網(wǎng)頁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); // 設(shè)置listView的點(diǎn)擊事件,略過
}
基本的新聞客戶端設(shè)置就到此結(jié)束了,但是這只是一個(gè)非常簡單的客戶端,如果是真實(shí)的新聞客戶端還有很多事情需要做,如設(shè)置新聞緩存,當(dāng)客戶端沒有聯(lián)網(wǎng)的時(shí)候從數(shù)據(jù)庫中獲取新聞等。
現(xiàn)在也順便寫一下如何用數(shù)據(jù)庫緩存新聞吧!
使用SQLite數(shù)據(jù)庫緩存新聞。
使用SQLite需要一個(gè)SQLiteOpenHelper類,然后自己封裝一個(gè)SQLite操作類。
SQLiteOpenHelper類用于建立數(shù)據(jù)庫,并且設(shè)置數(shù)據(jù)庫的列表形式,SQLite操作類這是負(fù)責(zé)數(shù)據(jù)庫的增刪查改等工作。
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);
}
// 保存新聞到數(shù)據(jù)庫中
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();
}
// 刪除數(shù)據(jù)庫數(shù)據(jù)
public void deleteNews (){
SQLiteDatabase db = dbHelper.getReadableDatabase();
db.delete("news", null, null);
db.close();
}
// 從數(shù)據(jù)庫中獲取存儲的行為
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;
}
}
返回修改獲取網(wǎng)絡(luò)數(shù)據(jù)類NetUtils和MainActivity類
修改NetUtils在getNetNews的InpuStream流關(guān)閉之前(is.close())添加兩行代碼:
// 如果獲取到網(wǎng)絡(luò)上的數(shù)據(jù),就刪除之前獲取的新聞數(shù)據(jù),保存新的新聞數(shù)據(jù)
new NewsDBUtils(context).deleteNews();
new NewsDBUtils(context).saveNews(arraylistNews);
然后給NetUtils添加一個(gè)獲取數(shù)據(jù)庫緩存新聞的方法:
// 返回?cái)?shù)據(jù)庫緩存到的數(shù)據(jù)
public static ArrayList<NewsBean> getDBNews(Context context){
return new NewsDBUtils(context).getNews();
}
OK,最后就是修改MainActivity中的代碼了,我們需要在onCreate()開啟新線程,獲取網(wǎng)絡(luò)數(shù)據(jù)之前,從數(shù)據(jù)庫中獲取到之前緩存的新聞,這樣才不會在網(wǎng)速緩慢的時(shí)候界面空白一片,增加一些代碼:
// 1.先去數(shù)據(jù)庫中獲取緩存的新聞數(shù)據(jù)展示到listview
ArrayList<NewsBean> allnews_database = NewsUtils.getDBNews(mContext);
if (allnews_database != null && allnews_database.size() > 0) {
// 創(chuàng)建一個(gè)adapter設(shè)置給listview
NewsAdapter newsAdapter = new NewsAdapter(mContext, allnews_database);
listview.setAdapter(newsAdapter);
}
總結(jié)
一個(gè)簡易的網(wǎng)絡(luò)新聞客戶端的制作流程就寫到這里,可能凌亂了一下,但是結(jié)合我在github的代碼還是能夠看明白的。
另外如果需要這個(gè)新聞客戶端獲取本地web服務(wù)方面的代碼,也可以聯(lián)系我。
最后就是如果哪位高手發(fā)現(xiàn)我的代碼還有改進(jìn)的地方,還請不吝賜教~~