在Android开发中我们经常有这样的需求,从服务器上下载xml或者JSON类型的数据,其中包括一些图片资源,本demo模拟了这个需求,从网络上加载XML资源,其中包括图片,我们要做的解析XML里面的数据,并且把图片缓存到本地一个cache目录里面,并且用一个自定义的Adapter去填充到LIstView,demo运行效果见下图:
本demo中为了安全起见,还对下载下来的图片的文件名进行了MD5加密,下面是MD5加密的代码,
以上省略了Contact.java这个domain类,通过这个demo,可以看出Android中会经常需要进行异步任务的处理,所以我们会常常用到自己手动开启线程,handler机制,或者AsyncTask类等手段来保证应用的性能
通过这个demo,要学会有一下几点
1.怎么解析一个XML
2.demo中用到的缓存图片到本地一个临时目录的思想是怎样的?
3.AsyncTask类的使用,因为要去异步的加载数据,就必须开启线程,但是在开启线程的时有时候不能很好的控制线程的数量,线程数量太大的时候手机会很快被卡死 这里就采用AsynsTask类的去解决这个问题,这个类里面封装了线程池的技术,从而保证不会因开启过多的线程而消耗太多的资源
4.本demo中的Handler类的使用情况 5.自定义adapter的使用
下面是demo中的Activity。
01 | public class MainActivity extends Activity { |
02 | protected static final int SUCCESS_GET_CONTACT = 0 ; |
03 | private ListView mListView; |
04 | private MyContactAdapter mAdapter; |
05 | private File cache; |
06 | |
07 | private Handler mHandler = new Handler(){ |
08 | public void handleMessage(android.os.Message msg) { |
09 | if (msg.what == SUCCESS_GET_CONTACT){ |
10 | List<Contact> contacts = (List<Contact>) msg.obj; |
11 | mAdapter = new MyContactAdapter(getApplicationContext(),contacts,cache); |
12 | mListView.setAdapter(mAdapter); |
13 | } |
14 | }; |
15 | }; |
16 | |
17 | @Override |
18 | public void onCreate(Bundle savedInstanceState) { |
19 | super .onCreate(savedInstanceState); |
20 | setContentView(R.layout.main); |
21 | |
22 | mListView = (ListView) findViewById(R.id.listview); |
23 | |
24 | //创建缓存目录,系统一运行就得创建缓存目录的, |
25 | cache = new File(Environment.getExternalStorageDirectory(), "cache" ); |
26 | |
27 | if (!cache.exists()){ |
28 | cache.mkdirs(); |
29 | } |
30 | |
31 | //获取数据,主UI线程是不能做耗时操作的,所以启动子线程来做 |
32 | new Thread(){ |
33 | public void run() { |
34 | ContactService service = new ContactService(); |
35 | List<Contact> contacts = null ; |
36 | try { |
37 | contacts = service.getContactAll(); |
38 | } catch (Exception e) { |
39 | e.printStackTrace(); |
40 | } |
41 | //子线程通过Message对象封装信息,并且用初始化好的, |
42 | //Handler对象的sendMessage()方法把数据发送到主线程中,从而达到更新UI主线程的目的 |
43 | Message msg = new Message(); |
44 | msg.what = SUCCESS_GET_CONTACT; |
45 | msg.obj = contacts; |
46 | mHandler.sendMessage(msg); |
47 | }; |
48 | }.start(); |
49 | } |
50 | |
51 | @Override |
52 | protected void onDestroy() { |
53 | super .onDestroy(); |
54 | //清空缓存 |
55 | File[] files = cache.listFiles(); |
56 | for (File file :files){ |
57 | file.delete(); |
58 | } |
59 | cache.delete(); |
60 | } |
61 | } |
Activity中,注意以下几点,
1.初始化了一个缓存目录,这个目录最好是应用开启就去创建好,为手续缓存图片做准备,在这里把数据存放在SDCard上
2.要去服务器加载数据,这个耗时操作最好是去开启线程加载数据,加载完毕后去异步的更新UI线程,利用Handler机制能很好的解决这个问题,
3.最后退出应用的时候,要删掉缓存目录和目录里面的数据,避免给手机制造很多的垃圾文件
下面就是一个Service类了,
01 | public class ContactService { |
02 |
03 | /** |
04 | * 从服务器上获取数据 |
05 | */ |
06 | public List<Contact> getContactAll() throws Exception { |
07 | List<Contact> contacts = null ; |
08 | String Parth = "http://192.168.1.103:8080/myweb/list.xml" ; |
09 | URL url = new URL(Parth); |
10 | HttpURLConnection conn = (HttpURLConnection) url.openConnection(); |
11 | conn.setConnectTimeout( 3000 ); |
12 | conn.setRequestMethod( "GET" ); |
13 | if (conn.getResponseCode() == HttpURLConnection.HTTP_OK) { |
14 | InputStream is = conn.getInputStream(); |
15 | // 这里获取数据直接放在XmlPullParser里面解析 |
16 | contacts = xmlParser(is); |
17 | return contacts; |
18 | } else { |
19 | return null ; |
20 | } |
21 | } |
22 |
23 | // 这里并没有下载图片下来,而是把图片的地址保存下来了 |
24 | private List<Contact> xmlParser(InputStream is) throws Exception { |
25 | List<Contact> contacts = null ; |
26 | Contact contact = null ; |
27 | XmlPullParser parser = Xml.newPullParser(); |
28 | parser.setInput(is, "UTF-8" ); |
29 | int eventType = parser.getEventType(); |
30 | while ((eventType = parser.next()) != XmlPullParser.END_DOCUMENT) { |
31 | switch (eventType) { |
32 | case XmlPullParser.START_TAG: |
33 | if (parser.getName().equals( "contacts" )) { |
34 | contacts = new ArrayList<Contact>(); |
35 | } else if (parser.getName().equals( "contact" )) { |
36 | contact = new Contact(); |
37 | contact.setId(Integer.valueOf(parser.getAttributeValue( 0 ))); |
38 | } else if (parser.getName().equals( "name" )) { |
39 | contact.setName(parser.nextText()); |
40 | } else if (parser.getName().equals( "image" )) { |
41 | contact.setImage(parser.getAttributeValue( 0 )); |
42 | } |
43 | break ; |
44 |
45 | case XmlPullParser.END_TAG: |
46 | if (parser.getName().equals( "contact" )) { |
47 | contacts.add(contact); |
48 | } |
49 | break ; |
50 | } |
51 | } |
52 | return contacts; |
53 | } |
54 |
55 | /* |
56 | * 从网络上获取图片,如果图片在本地存在的话就直接拿,如果不存在再去服务器上下载图片 |
57 | * 这里的path是图片的地址 |
58 | */ |
59 | public Uri getImageURI(String path, File cache) throws Exception { |
60 | String name = MD5.getMD5(path) + path.substring(path.lastIndexOf( "." )); |
61 | File file = new File(cache, name); |
62 | // 如果图片存在本地缓存目录,则不去服务器下载 |
63 | if (file.exists()) { |
64 | return Uri.fromFile(file); //Uri.fromFile(path)这个方法能得到文件的URI |
65 | } else { |
66 | // 从网络上获取图片 |
67 | URL url = new URL(path); |
68 | HttpURLConnection conn = (HttpURLConnection) url.openConnection(); |
69 | conn.setConnectTimeout( 5000 ); |
70 | conn.setRequestMethod( "GET" ); |
71 | conn.setDoInput( true ); |
72 | if (conn.getResponseCode() == 200 ) { |
73 |
74 | InputStream is = conn.getInputStream(); |
75 | FileOutputStream fos = new FileOutputStream(file); |
76 | byte [] buffer = new byte [ 1024 ]; |
77 | int len = 0 ; |
78 | while ((len = is.read(buffer)) != - 1 ) { |
79 | fos.write(buffer, 0 , len); |
80 | } |
81 | is.close(); |
82 | fos.close(); |
83 | // 返回一个URI对象 |
84 | return Uri.fromFile(file); |
85 | } |
86 | } |
87 | return null ; |
88 | } |
89 | } |
Serivce类中,注意以下几点
1.HttpURLConnection conn = (HttpURLConnection) url.openConnection();获取一个链接,从而进行通讯
2.怎么利用XxmlPullPaser类去解析XML,从而把数据封装成对象
3.getImageURI(String path, File cache) 这个方法具体实现
4.Uri.fromFile(file);这个方法能够直接返回一个Uri来
下面是自定义的Adapter类,
001 | public class MyContactAdapter extends BaseAdapter { |
002 |
003 | protected static final int SUCCESS_GET_IMAGE = 0 ; |
004 | private Context context; |
005 | private List<Contact> contacts; |
006 | private File cache; |
007 | private LayoutInflater mInflater; |
008 |
009 | // 自己定义的构造函数 |
010 | public MyContactAdapter(Context context, List<Contact> contacts, File cache) { |
011 | this .context = context; |
012 | this .contacts = contacts; |
013 | this .cache = cache; |
014 |
015 | mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); |
016 | } |
017 |
018 | @Override |
019 | public int getCount() { |
020 | return contacts.size(); |
021 | } |
022 |
023 | @Override |
024 | public Object getItem( int position) { |
025 | return contacts.get(position); |
026 | } |
027 |
028 | @Override |
029 | public long getItemId( int position) { |
030 | return position; |
031 | } |
032 |
033 | @Override |
034 | public View getView( int position, View convertView, ViewGroup parent) { |
035 | // 1获取item,再得到控件 |
036 | // 2 获取数据 |
037 | // 3绑定数据到item |
038 | View view = null ; |
039 | if (convertView != null ) { |
040 | view = convertView; |
041 | } else { |
042 | view = mInflater.inflate(R.layout.item, null ); |
043 | } |
044 |
045 | ImageView iv_header = (ImageView) view.findViewById(R.id.iv_header); |
046 | TextView tv_name = (TextView) view.findViewById(R.id.tv_name); |
047 |
048 | Contact contact = contacts.get(position); |
049 |
050 | // 异步的加载图片 (线程池 + Handler ) ---> AsyncTask |
051 | asyncloadImage(iv_header, contact.image); |
052 | tv_name.setText(contact.name); |
053 |
054 | return view; |
055 | } |
056 |
057 | private void asyncloadImage(ImageView iv_header, String path) { |
058 | ContactService service = new ContactService(); |
059 | AsyncImageTask task = new AsyncImageTask(service, iv_header); |
060 | task.execute(path); |
061 | } |
062 |
063 | private final class AsyncImageTask extends AsyncTask<String, Integer, Uri> { |
064 |
065 | private ContactService service; |
066 | private ImageView iv_header; |
067 |
068 | public AsyncImageTask(ContactService service, ImageView iv_header) { |
069 | this .service = service; |
070 | this .iv_header = iv_header; |
071 | } |
072 |
073 | // 后台运行的子线程子线程 |
074 | @Override |
075 | protected Uri doInBackground(String... params) { |
076 | try { |
077 | return service.getImageURI(params[ 0 ], cache); |
078 | } catch (Exception e) { |
079 | e.printStackTrace(); |
080 | } |
081 | return null ; |
082 | } |
083 |
084 | // 这个放在在ui线程中执行 |
085 | @Override |
086 | protected void onPostExecute(Uri result) { |
087 | super .onPostExecute(result); |
088 | // 完成图片的绑定 |
089 | if (iv_header != null && result != null ) { |
090 | iv_header.setImageURI(result); |
091 | } |
092 | } |
093 | } |
094 |
095 | /** |
096 | * 采用普通方式异步的加载图片 |
097 | */ |
098 | /*private void asyncloadImage(final ImageView iv_header, final String path) { |
099 | final Handler mHandler = new Handler() { |
100 | @Override |
101 | public void handleMessage(Message msg) { |
102 | super.handleMessage(msg); |
103 | if (msg.what == SUCCESS_GET_IMAGE) { |
104 | Uri uri = (Uri) msg.obj; |
105 | if (iv_header != null && uri != null) { |
106 | iv_header.setImageURI(uri); |
107 | } |
108 |
109 | } |
110 | } |
111 | }; |
112 | // 子线程,开启子线程去下载或者去缓存目录找图片,并且返回图片在缓存目录的地址 |
113 | Runnable runnable = new Runnable() { |
114 | @Override |
115 | public void run() { |
116 | ContactService service = new ContactService(); |
117 | try { |
118 | //这个URI是图片下载到本地后的缓存目录中的URI |
119 | Uri uri = service.getImageURI(path, cache); |
120 | Message msg = new Message(); |
121 | msg.what = SUCCESS_GET_IMAGE; |
122 | msg.obj = uri; |
123 | mHandler.sendMessage(msg); |
124 | } catch (Exception e) { |
125 | e.printStackTrace(); |
126 | } |
127 | } |
128 | }; |
129 | new Thread(runnable).start(); |
130 | }*/ |
131 | } |
自定义Adapter中,我们要注意 AsyncImageTask这个类继承了AsyncTask类,AsyncTask是Android中常用来做异步任务的类,对线程池进行了封装,详细分析稍后再贴出一篇Blog。
下面是我们从服务器上获取并且解析的Xml文件
01 | <? xml version = "1.0" encoding = "UTF-8" ?> |
02 | < contacts > |
03 | < contact id = "1" > |
04 | < name >张飞</ name > |
05 | < image src = "http://192.168.1.103:8080/mymyweb/images/1.gif" /> |
06 | </ contact > |
07 | < contact id = "2" > |
08 | < name >博文</ name > |
09 | < image src = "http://192.168.1.103:8080/myweb/images/2.gif" /> |
10 | </ contact > |
11 | < contact id = "3" > |
12 | < name >张天佑</ name > |
13 | < image src = "http://192.168.1.103:8080/myweb/images/3.gif" /> |
14 | </ contact > |
15 | < contact id = "4" > |
16 | < name >松德</ name > |
17 | < image src = "http://192.168.1.103:8080/myweb/images/4.gif" /> |
18 | </ contact > |
19 | < contact id = "5" > |
20 | < name >赵薇</ name > |
21 | < image src = "http://192.168.1.103:8080/myweb/images/5.gif" /> |
22 | </ contact > |
23 | < contact id = "6" > |
24 | < name >李静</ name > |
25 | < image src = "http://192.168.1.103:8080/myweb/images/6.gif" /> |
26 | </ contact > |
27 | < contact id = "7" > |
28 | < name >李明</ name > |
29 | < image src = "http://192.168.1.103:8080/myweb/images/7.gif" /> |
30 | </ contact > |
31 | < contact id = "8" > |
32 | < name >黎明</ name > |
33 | < image src = "http://192.168.1.103:8080/myweb/images/8.gif" /> |
34 | </ contact > |
35 | |
36 | < contact id = "9" > |
37 | < name >秦桧</ name > |
38 | < image src = "http://192.168.1.103:8080/myweb/images/9.gif" /> |
39 | </ contact > |
40 | < contact id = "10" > |
41 | < name >朱德</ name > |
42 | < image src = "http://192.168.1.103:8080/myweb/images/10.gif" /> |
43 | </ contact > |
44 | < contact id = "11" > |
45 | < name >冯巩</ name > |
46 | < image src = "http://192.168.1.103:8080/myweb/images/11.gif" /> |
47 | </ contact > |
48 | < contact id = "12" > |
49 | < name >dylan</ name > |
50 | < image src = "http://192.168.1.103:8080/myweb/images/12.gif" /> |
51 | </ contact > |
52 | < contact id = "13" > |
53 | < name >黄单</ name > |
54 | < image src = "http://192.168.1.103:8080/myweb/images/13.gif" /> |
55 | </ contact > |
56 | < contact id = "14" > |
57 | < name >含蕊</ name > |
58 | < image src = "http://192.168.1.103:8080/myweb/images/14.gif" /> |
59 | </ contact > |
60 | < contact id = "15" > |
61 | < name >欣琪</ name > |
62 | < image src = "http://192.168.1.103:8080/myweb/images/15.jpg" /> |
63 | </ contact > |
64 | < contact id = "16" > |
65 | < name >李忠华</ name > |
66 | < image src = "http://192.168.1.103:8080/myweb/images/16.jpg" /> |
67 | </ contact > |
68 | < contact id = "17" > |
69 | < name >方产员</ name > |
70 | < image src = "http://192.168.1.103:8080/myweb/images/17.jpg" /> |
71 | </ contact > |
72 | < contact id = "18" > |
73 | < name >张光</ name > |
74 | < image src = "http://192.168.1.103:8080/myweb/images/18.jpg" /> |
75 | </ contact > |
76 | </ contacts > |
01 | public class MD5 { |
02 |
03 | public static String getMD5(String content) { |
04 | try { |
05 | MessageDigest digest = MessageDigest.getInstance( "MD5" ); |
06 | digest.update(content.getBytes()); |
07 | return getHashString(digest); |
08 | |
09 | } catch (NoSuchAlgorithmException e) { |
10 | e.printStackTrace(); |
11 | } |
12 | return null ; |
13 | } |
14 | |
15 | private static String getHashString(MessageDigest digest) { |
16 | StringBuilder builder = new StringBuilder(); |
17 | for ( byte b : digest.digest()) { |
18 | builder.append(Integer.toHexString((b >> 4 ) & 0xf )); |
19 | builder.append(Integer.toHexString(b & 0xf )); |
20 | } |
21 | return builder.toString(); |
22 | } |
23 | } |