5.安卓基础之网络编程&开源框架&多线程下载

1. 简单的xml联网读取

  • 需要在AndroidManifest.xml开启权限
    <uses-permission android:name="android.permission.INTERNET"/>

  • 定义一个工具流

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class StreamTool {
public static String decodeStream(InputStream in) throws Exception{
ByteArrayOutputStream baos = new ByteArrayOutputStream();
byte[]buf = new byte[1024];
int len = 0;
while((len=in.read(buf))>0){
baos.write(buf,0,len);
}
return baos.toString();
}
}
  • 定义一个NewsItem
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
public class NewsItem {
private String title;
private String description;
private String image;
private String type;
private String comment;
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public String getImage() {
return image;
}
public void setImage(String image) {
this.image = image;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public String getComment() {
return comment;
}
public void setComment(String comment) {
this.comment = comment;
}
}
  • 定义一个NewsService,通过处理数据返回List
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
public class NewsService {
public static List<NewsItem> getAllNewsItem(final String path){
final List<NewsItem> items = new ArrayList<NewsItem>();
new Thread(){
public void run() {
try {
URL url = new URL(path);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
//设置超时时间
conn.setConnectTimeout(5000);
conn.setRequestMethod("GET");
int code = conn.getResponseCode();
if(code==200){
InputStream in = conn.getInputStream();
XmlPullParser parser = Xml.newPullParser();
parser.setInput(in, "utf-8");
int type = parser.getEventType();
NewsItem item=null;
while(type!=XmlPullParser.END_DOCUMENT){
if(type==XmlPullParser.START_TAG){
if("item".equals(parser.getName())){
item = new NewsItem();
}else if("title".equals(parser.getName())){
item.setTitle(parser.nextText());
}else if("description".equals(parser.getName())){
item.setDescription(parser.nextText());
}else if("image".equals(parser.getName())){
item.setImage(parser.nextText());
}else if("type".equals(parser.getName())){
item.setType(parser.nextText());
}else if("comment".equals(parser.getName())){
item.setComment(parser.nextText());
}
}else if(type==XmlPullParser.END_TAG){
//将 item 添加到一个 list集合中
if(item!=null){
items.add(item);
}
}
type = parser.next();
}
}
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}.start();
return items;
}
}
  • 定义一个MainActivity
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
public class MainActivity extends AppCompatActivity {
private String path = "http://www.itceo.net/test/news.xml";
private List<NewsItem> items =null;
private ListView lv;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
lv = (ListView) findViewById(R.id.lv);
items = NewsService.getAllNewsItem(path);
// 将item中的数据绑定到屏幕显示
loadData();
}
//声明adapter
private MyAdapter myadapter;
private void loadData() {
if(myadapter==null){
myadapter = new MyAdapter();
lv.setAdapter(myadapter);
}else{
//通知数据改变
myadapter.notifyDataSetChanged();
}
}
private class MyAdapter extends BaseAdapter{
//指定到底有多少个item要显示在 lv 中
@Override
public int getCount() {
return items.size();
}
//这个方法是在每次显示一个item时会被调用到的
@Override
public View getView(int position, View convertView, ViewGroup parent) {
View v ;
if(convertView==null){
v=View.inflate(MainActivity.this, R.layout.item, null);
}else{
v = convertView;
}
//拿到当前位置newsItem对象
NewsItem newsItem = items.get(position);
//找到item中每个 控件
SmartImageView siv = (SmartImageView) v.findViewById(R.id.item_iv);
TextView title= (TextView) v.findViewById(R.id.item_title);
TextView desc = (TextView) v.findViewById(R.id.item_desc);
TextView type = (TextView) v.findViewById(R.id.item_type);
title.setText(newsItem.getTitle());
desc.setText(newsItem.getDescription());
String tp = newsItem.getType();
if("1".equals(tp)){
//就是评论
type.setText("评论: "+ newsItem.getComment());
type.setTextColor(Color.YELLOW);
}else if("2".equals(tp)){
//就是视频
type.setText("视频");
type.setTextColor(Color.RED);
}else if("3".equals(tp)){
// 就是直播
type.setText("Live直播 ");
type.setTextColor(Color.BLUE);
}
//利用开源图片显示库
siv.setImageUrl(newsItem.getImage());
return v;
}
@Override
public Object getItem(int position) {
// TODO Auto-generated method stub
return null;
}
@Override
public long getItemId(int position) {
// TODO Auto-generated method stub
return 0;
}
}
}
  • activity_main.xml与item.xml的编写
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
activity_main.xml:
<LinearLayout 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"
tools:context=".MainActivity" >
<ListView
android:id="@+id/lv"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
></ListView>
</LinearLayout>
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="match_parent">
<com.loopj.android.image.SmartImageView
android:id="@+id/item_iv"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true" />
<TextView
android:id="@+id/item_title"
android:text="标题"
android:layout_toRightOf="@id/item_iv"
android:layout_width="fill_parent"
android:textSize="20sp"
android:layout_height="wrap_content"
/>
<TextView
android:id="@+id/item_desc"
android:textSize="15sp"
android:layout_below="@id/item_title"
android:lines="2"
android:text="描述xxxxxxxxxxxxxxxx"
android:textColor="#55000000"
android:layout_toRightOf="@id/item_iv"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
/>
<TextView
android:id="@+id/item_type"
android:layout_below="@id/item_desc"
android:textSize="15sp"
android:text="类型:"
android:textColor="#55000000"
android:layout_alignParentRight="true"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
/>
<View
android:background="#44ffffff"
android:layout_width="fill_parent"
android:layout_height="1dip"
/>
</RelativeLayout>

2. get与post的关系

get与post的关系

2.1 get的关系

如果在发送数据的过程中,传输了中文数据,那么是需要进行url编码的,否则带不过去.

1
2
3
4
http://192.168.1.100:8080/web_login/login?number=%E5%93%88%E5%93%88&pwd=520
http://192.168.1.100:8080/web_login/login?number=5201314&pwd=520
path = path+"number="+URLEncoder.encode(number, "UTF-8")+"&pwd="+URLEncoder.encode(pwd, "UTF-8");

2.2 post的关系

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
URL url = new URL(path);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("POST");
conn.setConnectTimeout(5000);
// Content-Type: application/x-www-form-urlencoded
conn.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
//准备好数据 ---- number=erwan&pwd=456
String data = "number="+URLEncoder.encode(number, "utf-8")+"&pwd="+URLEncoder.encode(pwd, "utf-8");
//Content-Length: 20
conn.setRequestProperty("Content-Length", data.length()+"");
//这个地方表示告诉加了一个标志, 要给服务器写数据了
conn.setDoOutput(true);
conn.getOutputStream().write(data.getBytes());

2.3 apache-httpclient-get

1
2
3
4
5
6
7
8
9
10
11
12
13
//客户端浏览器
HttpClient client = new DefaultHttpClient();
//get 方式请求的必要的参数
HttpGet get = new HttpGet(path);
//http://192.168.1.100:8080/web_login/login
//收到的来自于服务器端的响应的 数据
HttpResponse response = client.execute(get);
// HTTP/1.1 200 OK
int code = response.getStatusLine().getStatusCode();

2.4 apache-httpclient-post

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
//客户端浏览器
HttpClient client = new DefaultHttpClient();
//指定了发送的请求的方式
HttpPost post = new HttpPost(path);
//传递给服务器要带过去的参数的信息
List<NameValuePair> list = new ArrayList<NameValuePair>();
//将带过去的参数放到一个nameValuePair 中, 然后再放到 一个list 中,然后再将这个list给要带过去的数据实体
list.add(new BasicNameValuePair("number", number));
list.add(new BasicNameValuePair("pwd", pwd));
//设置带给服务器的参数的信息
//number=5201314&pwd=123
//设置要带给服务器的数据实体
post.setEntity(new UrlEncodedFormEntity(list,"UTF-8"));
//http://192.168.1.100:8080/web_login/login
//收到的来自于服务器端的响应的数据
HttpResponse response = client.execute(post);
//http 的相应分为响应行,响应头,响应体
//HTTP/1.1 200 OK
int code = response.getStatusLine().getStatusCode();

2.5 开源android-async-http-master-get

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
String path = "http://192.168.1.100:8080/web_login/login?number="+number+"&pwd="+pwd;
AsyncHttpClient client = new AsyncHttpClient();
client.get(path, new AsyncHttpResponseHandler() {
//请求成功的时候 会被调用的
@Override
public void onSuccess(int statusCode, Header[] headers,
byte[] responseBody) {
tv_status.setText(new String(responseBody));
}
//请求失败的时候会被调用的
@Override
public void onFailure(int statusCode, Header[] headers,
byte[] responseBody, Throwable error) {
error.printStackTrace(System.out);
Toast.makeText(MainActivity.this, "对不起, 俺错误了...", 0).show();
}
});

2.6 开源android-async-http-master-post

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
String path = "http://192.168.1.100:8080/web_login/login";
AsyncHttpClient client = new AsyncHttpClient();
//封装了api , 使用到了handler 去 处理了 这些事儿 ...
RequestParams params = new RequestParams();
// number=5201314&pwd=123
params.add("number", number);
params.add("pwd", pwd);
client.post(path, params, new AsyncHttpResponseHandler(){
@Override
public void onSuccess(int statusCode, Header[] headers,
byte[] responseBody) {
tv_status.setText(new String(responseBody));
}
@Override
public void onFailure(int statusCode, Header[] headers,
byte[] responseBody, Throwable error) {
error.printStackTrace(System.out);
Toast.makeText(MainActivity.this, "出错误了 ", 0).show();
}
});

3. 多线程与断点续传实现

多线程推导

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
public class ThreadDownload {
//规定下载服务端的资源使用3条线程去下载
private static int threadCount = 3;
private static int currentRunningThread = 3;
private static String path = "http://down.qq.com/cf/dltools/CrossFire_OBV4.0.5.0_Full_QQVIPDL_speeded_signed.exe";
public static void main(String[] args) {
try{
URL url = new URL(path);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setConnectTimeout(5000);
conn.setRequestMethod("GET");
int code = conn.getResponseCode();
if(code==200){
//拿到文件的长度大小
int length = conn.getContentLength();
File file = new File(getFileName(path));
RandomAccessFile raf = new RandomAccessFile(file, "rw");
raf.setLength(length);
raf.close();
//启动线程去下载文件
//获得每块线程的平均大小
int blockSize = length / threadCount; //总文件长度大小/3
//threadId:线程的id threadcount:几条线程下载
for(int threadId=0;threadId<threadCount;threadId++){
int startIndex = threadId * blockSize; //0~2
int endIndex = (threadId+1)*blockSize-1;//3~5
//如果线程是最后的一个线程
if(threadId==(threadCount-1)){
endIndex = length-1;
}
new DownloadFilePartThread(threadId,startIndex,endIndex).start();
}
}
}catch (Exception e) {
e.printStackTrace();
}
}
private static class DownloadFilePartThread extends Thread{
//线程的id号
private int threadId;
//线程的下载开始位置
private int startIndex;
//线程的下载的结束位置
private int endIndex;
//当前线程下载到的位置
private int currentPostion;
public DownloadFilePartThread(int threadId,int startIndex,int endIndex){
this.threadId = threadId;
this.startIndex = startIndex;
this.endIndex = endIndex;
currentPostion = startIndex;
}
public void run(){
System.out.println("第 " + threadId + "线程开始 下载了 : 下载 从 "
+ startIndex + "~ " + endIndex);
try{
URL url = new URL(path);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setConnectTimeout(5000);
conn.setRequestMethod("GET");
// 在多线程下载的时候,每条线程只需要目标文件的一部分的数据
// 需要告诉服务器,只需要那一段的数据
// 通过设置 http的请求头可以去实现 ,告诉 服务器,只需要目标 段的数据
// startIndex ---- endIndex
conn.setRequestProperty("range", "bytes="+startIndex+"-"+endIndex);
//获得服务器返回的目标段的数据
File file = new File(getFileName(path));
RandomAccessFile raf = new RandomAccessFile(file, "rw");
File ilf = new File(threadId+".position");
if(ilf.exists()&&ilf.length()>0){
BufferedReader br = new BufferedReader(new FileReader(ilf));
String vl = br.readLine();
int alreadyWritePosition = Integer.parseInt(vl);
//告诉服务器要数据的时候 ,从这个位置开始要
conn.setRequestProperty("range", "bytes="+alreadyWritePosition+"-"+endIndex);
raf.seek(alreadyWritePosition);
System.out.println("下载过了!");
}else{
System.out.println("没下载过!");
conn.setRequestProperty("range", "bytes="+startIndex+"-"+endIndex);
//表示从哪里开始
raf.seek(startIndex);
}
int code = conn.getResponseCode();
if(code == 206){
//拿到数据
InputStream in = conn.getInputStream();
int len = 0;
byte[]buf = new byte[1024*1024*1024*1024*1024*1024*1024*1024*1024];
while((len=in.read(buf))>0){
raf.write(buf,0,len);
//将实时的位置记录了之后,方便下面紧接着去往文件中去写
currentPostion = currentPostion + len;
File info = new File(threadId+".position");
RandomAccessFile rf = new RandomAccessFile(info, "rwd");
rf.write(String.valueOf(currentPostion).getBytes());
rf.close();
}
in.close();
raf.close();
}
System.out.println("第 " + threadId + "线程下载 结束了");
// 等着所有的线程都下载完成再删文件
// 弄一个 计数器,记住总共有多少条线程正在下载,每当一条线程下载完成,走到这里的时候,就将计数器-1 一下
// 当发现计数器小于或者等于0 的时候,说明没有线程正在下载,所以这个时候,再去删记录了下载位置的文件
synchronized (ThreadDownload.class) {
currentRunningThread--;
if(currentRunningThread<=0){
//将记录下载位置的文件删除
for(threadId=0;threadId<threadCount;threadId++){
File fff = new File(threadId+".position");
fff.renameTo(new File(threadId+".position.finish"));
File fll = new File(threadId+".position.finish");
fll.delete();
}
}
}
}catch (Exception e) {
e.printStackTrace();
}
}
}
private static String getFileName(String path) {
int index = path.lastIndexOf("/");
return path.substring(index+1);
}
}

4. 总结

多线程下载还需要深入了解.