2014年4月27日 星期日

[轉貼] android 多线程断点续传下载



今天跟大家一起分享下android开发中比较难的一个环节,可能很多人看到这个标题就会感觉头很大,的确如果没有良好的编码能力和逻辑思维,这块是很难搞明白的,前面2次总结中已经为大家分享过有关技术的一些基本要领,我们先一起简单回顾下它的基本原理。
http://blog.csdn.net/shimiso/article/details/6763664  android 多线程断点续传下载 一
http://blog.csdn.net/shimiso/article/details/6763986  android 多线程断点续传下载 二
什么是多线程下载?

多线程下载其实就是迅雷,BT一些下载原理,通过多个线程同时和服务器连接,那么你就可以榨取到较高的带宽了,大致做法是将文件切割成N块,每块交给单独一个线程去下载,各自下载完成后将文件块组合成一个文件,程序上要完成做切割和组装的小算法
什么是断点续传?
断点续传,就是当我们下载未结束时候,退出保存下载进度,当下次打开继续下载的时接着上次的进度继续下载,不用每次下载都重新开始,那么有关断点续传的原理和实现手段,可参考我以前的一篇总结http://blog.csdn.net/shimiso/article/details/5956314 里面详细讲解http协议断点续传的原理,务必要看懂,否则你无法真正理解本节代码
怎么完成多线程断点续传?
将两者合二为一需要程序记住每个文件块的下载进度,并保存入库,当下载程序启动时候你需要判断程序是否已经下载过该文件,并取出各个文件块的保存记录,换算出下载进度继续下载,在这里你需要掌握java多线程的基本知识,handler的使用,以及集合,算法,文件操作等基本技能,同时还要解决sqlite数据库的同步问题,因为它是不太怎么支持多线程操作的,控制不好经常会出现库被锁定的异常,同时在android2.3以后就不能activity中直接操作http,否则你将收到系统送上的NetworkOnMainThreadException异常,在UI体验上一定记住要使用异步完成,既然大致思路已经清楚,下面我们开始分析程序:
package cn.demo.DBHelper;
 
 import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
 
     /**
      * 建立一个数据库帮助类
      */
 public class DBHelper extends SQLiteOpenHelper {
     //download.db-->数据库名
     public DBHelper(Context context) {
         super(context, "download.db", null, 1);
     }
     
     /**
      * 在download.db数据库下创建一个download_info表存储下载信息
      */
     @Override
     public void onCreate(SQLiteDatabase db) {
         db.execSQL("create table download_info(_id integer PRIMARY KEY AUTOINCREMENT, thread_id integer, "
                 + "start_pos integer, end_pos integer, compelete_size integer,url char)");
     }
     @Override
     public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
 
     }
 
 }

 数据库操作要借助单例和同步,来保证线程的执行顺序,以免多个线程争相抢用sqlite资源导致异常出现
package cn.demo.Dao;

import java.util.ArrayList;
import java.util.List;

import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import cn.demo.DBHelper.DBHelper;
import cn.demo.entity.DownloadInfo;

/**
 * 
 * 一个业务类
 */
public class Dao {  
 private static Dao dao=null;
 private Context context; 
 private  Dao(Context context) { 
  this.context=context;
 }
 public static  Dao getInstance(Context context){
  if(dao==null){
   dao=new Dao(context); 
  }
  return dao;
 }
 public  SQLiteDatabase getConnection() {
  SQLiteDatabase sqliteDatabase = null;
  try { 
   sqliteDatabase= new DBHelper(context).getReadableDatabase();
  } catch (Exception e) {  
  }
  return sqliteDatabase;
 }

 /**
  * 查看数据库中是否有数据
  */
 public synchronized boolean isHasInfors(String urlstr) {
  SQLiteDatabase database = getConnection();
  int count = -1;
  Cursor cursor = null;
  try {
   String sql = "select count(*)  from download_info where url=?";
   cursor = database.rawQuery(sql, new String[] { urlstr });
   if (cursor.moveToFirst()) {
    count = cursor.getInt(0);
   } 
  } catch (Exception e) {
   e.printStackTrace();
  } finally {
   if (null != database) {
    database.close();
   }
   if (null != cursor) {
    cursor.close();
   }
  }
  return count == 0;
 }

 /**
  * 保存 下载的具体信息
  */
 public synchronized void saveInfos(List<DownloadInfo> infos) {
  SQLiteDatabase database = getConnection();
  try {
   for (DownloadInfo info : infos) {
    String sql = "insert into download_info(thread_id,start_pos, end_pos,compelete_size,url) values (?,?,?,?,?)";
    Object[] bindArgs = { info.getThreadId(), info.getStartPos(),
      info.getEndPos(), info.getCompeleteSize(),
      info.getUrl() };
    database.execSQL(sql, bindArgs);
   }
  } catch (Exception e) {
   e.printStackTrace();
  } finally {
   if (null != database) {
    database.close();
   }
  }
 }

 /**
  * 得到下载具体信息
  */
 public synchronized List<DownloadInfo> getInfos(String urlstr) {
  List<DownloadInfo> list = new ArrayList<DownloadInfo>();
  SQLiteDatabase database = getConnection();
  Cursor cursor = null;
  try {
   String sql = "select thread_id, start_pos, end_pos,compelete_size,url from download_info where url=?";
   cursor = database.rawQuery(sql, new String[] { urlstr });
   while (cursor.moveToNext()) {
    DownloadInfo info = new DownloadInfo(cursor.getInt(0),
      cursor.getInt(1), cursor.getInt(2), cursor.getInt(3),
      cursor.getString(4));
    list.add(info);
   }
  } catch (Exception e) {
   e.printStackTrace();
  } finally {
   if (null != database) {
    database.close();
   }
   if (null != cursor) {
    cursor.close();
   }
  }
  return list;
 }

 /**
  * 更新数据库中的下载信息
  */
 public synchronized void updataInfos(int threadId, int compeleteSize, String urlstr) {
  SQLiteDatabase database = getConnection();
  try {
   String sql = "update download_info set compelete_size=? where thread_id=? and url=?";
   Object[] bindArgs = { compeleteSize, threadId, urlstr };
   database.execSQL(sql, bindArgs);
  } catch (Exception e) {
   e.printStackTrace();
  } finally {
   if (null != database) {
    database.close();
   }
  }
 }

 /**
  * 下载完成后删除数据库中的数据
  */
 public synchronized void delete(String url) {
  SQLiteDatabase database = getConnection();
  try {
   database.delete("download_info", "url=?", new String[] { url });
  } catch (Exception e) {
   e.printStackTrace();
  } finally {
   if (null != database) {
    database.close();
   }
  }
 }
}

package cn.demo.download;

import java.util.List;
import java.util.Map;

import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.Button;
import android.widget.TextView;




public class DownLoadAdapter extends BaseAdapter{
  
 private LayoutInflater mInflater;
 private List<Map<String, String>> data;
 private Context context;
 private OnClickListener click;
 
 public DownLoadAdapter(Context context,List<Map<String, String>> data) {
  this.context=context;
  mInflater = LayoutInflater.from(context);
  this.data=data;
 }
 public void refresh(List<Map<String, String>> data) {
  this.data=data;
  this.notifyDataSetChanged();
 }
 public void setOnclick(OnClickListener click) {
   this.click=click;
 }
 
 
 @Override
 public int getCount() {
  return data.size();
 }

 @Override
 public Object getItem(int position) {
  return data.get(position);
 }

 @Override
 public long getItemId(int position) {
  return position;
 }

 @Override
 public View getView(final int position, View convertView, ViewGroup parent) {
  final Map<String, String> bean=data.get(position);
  ViewHolder holder = null;
  if (convertView == null) {
   convertView = mInflater.inflate(R.layout.list_item, null);
   holder = new ViewHolder(); 
   holder.resouceName=(TextView) convertView.findViewById(R.id.tv_resouce_name);
   holder.startDownload=(Button) convertView.findViewById(R.id.btn_start);
   holder.pauseDownload=(Button) convertView.findViewById(R.id.btn_pause);
   convertView.setTag(holder);
  } else {
   holder = (ViewHolder) convertView.getTag();
  } 
   holder.resouceName.setText(bean.get("name")); 
  return convertView;
 }
 public OnClickListener getClick() {
  return click;
 }
 public void setClick(OnClickListener click) {
  this.click = click;
 }
 private class ViewHolder { 
        public TextView resouceName;
        public Button startDownload;
        public Button pauseDownload;
      
        
     
    }
 
}


注意子线程不要影响主UI线程,灵活运用task和handler,各取所长,保证用户体验,handler通常在主线程中有利于专门负责处理UI的一些工作
package cn.demo.download;
 
 import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import android.app.ListActivity;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.view.View;
import android.widget.Button;
import android.widget.LinearLayout;
import android.widget.LinearLayout.LayoutParams;
import android.widget.ProgressBar; 
import android.widget.TextView;
import android.widget.Toast;
import cn.demo.entity.LoadInfo;
import cn.demo.service.Downloader;

 public class MainActivity extends ListActivity { 
     // 固定下载的资源路径,这里可以设置网络上的地址
     private static final String URL = "http://download.haozip.com/";
     // 固定存放下载的音乐的路径:SD卡目录下
     private static final String SD_PATH = "/mnt/sdcard/";
     // 存放各个下载器
     private Map<String, Downloader> downloaders = new HashMap<String, Downloader>();
     // 存放与下载器对应的进度条
     private Map<String, ProgressBar> ProgressBars = new HashMap<String, ProgressBar>();
     /**
      * 利用消息处理机制适时更新进度条
      */
     private Handler mHandler = new Handler() {
         public void handleMessage(Message msg) {
             if (msg.what == 1) {
                 String url = (String) msg.obj;
                 int length = msg.arg1;
                 ProgressBar bar = ProgressBars.get(url);
                 if (bar != null) {
                     // 设置进度条按读取的length长度更新
                     bar.incrementProgressBy(length);
                     if (bar.getProgress() == bar.getMax()) {
                      LinearLayout layout = (LinearLayout) bar.getParent();
                      TextView resouceName=(TextView)layout.findViewById(R.id.tv_resouce_name);
                         Toast.makeText(MainActivity.this, "["+resouceName.getText()+"]下载完成!", Toast.LENGTH_SHORT).show();
                         // 下载完成后清除进度条并将map中的数据清空
                         layout.removeView(bar);
                         ProgressBars.remove(url);
                         downloaders.get(url).delete(url);
                         downloaders.get(url).reset();
                         downloaders.remove(url);
                         
                         Button btn_start=(Button)layout.findViewById(R.id.btn_start);
                         Button btn_pause=(Button)layout.findViewById(R.id.btn_pause);
                         btn_pause.setVisibility(View.GONE);
                         btn_start.setVisibility(View.GONE);
                     }
                 }
             }
         }
     };
     @Override
     public void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
         setContentView(R.layout.main); 
         showListView();
     }
     // 显示listView,这里可以随便添加
     private void showListView() {
         List<Map<String, String>> data = new ArrayList<Map<String, String>>();
         Map<String, String> map = new HashMap<String, String>();
         map.put("name", "haozip_v3.1.exe");
         data.add(map);
         map = new HashMap<String, String>();
         map.put("name", "haozip_v3.1_hj.exe");
         data.add(map);
         map = new HashMap<String, String>();
         map.put("name", "haozip_v2.8_x64_tiny.exe");
         data.add(map);
         map = new HashMap<String, String>();
         map.put("name", "haozip_v2.8_tiny.exe");
         data.add(map);
         DownLoadAdapter adapter=new DownLoadAdapter(this,data);  
         setListAdapter(adapter);
         
     }
     /**
      * 响应开始下载按钮的点击事件
      */
     public void startDownload(View v) {
         // 得到textView的内容 
         LinearLayout layout = (LinearLayout) v.getParent();
         String resouceName = ((TextView) layout.findViewById(R.id.tv_resouce_name)).getText().toString();
         String urlstr = URL + resouceName;
         String localfile = SD_PATH + resouceName;
         //设置下载线程数为4,这里是我为了方便随便固定的
         String threadcount = "4";
         DownloadTask downloadTask=new DownloadTask(v);
         downloadTask.execute(urlstr,localfile,threadcount);
       
     };
    class DownloadTask extends AsyncTask<String, Integer, LoadInfo>{
     Downloader downloader=null; 
     View v=null;
     String urlstr=null;
     public DownloadTask(final View v){
      this.v=v;
     }  
     @Override
     protected void onPreExecute() { 
      Button btn_start=(Button)((View)v.getParent()).findViewById(R.id.btn_start);
      Button btn_pause=(Button)((View)v.getParent()).findViewById(R.id.btn_pause);
      btn_start.setVisibility(View.GONE);
      btn_pause.setVisibility(View.VISIBLE);
     }
  @Override
  protected LoadInfo doInBackground(String... params) {
   urlstr=params[0];
   String localfile=params[1];
   int threadcount=Integer.parseInt(params[2]);
    // 初始化一个downloader下载器
          downloader = downloaders.get(urlstr);
          if (downloader == null) {
              downloader = new Downloader(urlstr, localfile, threadcount, MainActivity.this, mHandler);
              downloaders.put(urlstr, downloader);
          }
          if (downloader.isdownloading())
           return null;
          // 得到下载信息类的个数组成集合
          return downloader.getDownloaderInfors(); 
  }
  @Override
  protected void onPostExecute(LoadInfo loadInfo) {
   if(loadInfo!=null){
     // 显示进度条
           showProgress(loadInfo, urlstr, v);
           // 调用方法开始下载
           downloader.download();
   } 
  }
   
  };
     /**
      * 显示进度条
      */
     private void showProgress(LoadInfo loadInfo, String url, View v) {
         ProgressBar bar = ProgressBars.get(url);
         if (bar == null) {
             bar = new ProgressBar(this, null, android.R.attr.progressBarStyleHorizontal);
             bar.setMax(loadInfo.getFileSize());
             bar.setProgress(loadInfo.getComplete());
             ProgressBars.put(url, bar);
             LinearLayout.LayoutParams params = new LayoutParams(LayoutParams.FILL_PARENT, 5);
             ((LinearLayout) ((LinearLayout) v.getParent()).getParent()).addView(bar, params);
         }
     }
     /**
      * 响应暂停下载按钮的点击事件
      */
     public void pauseDownload(View v) {
         LinearLayout layout = (LinearLayout) v.getParent();
         String resouceName = ((TextView) layout.findViewById(R.id.tv_resouce_name)).getText().toString();
         String urlstr = URL + resouceName;
         downloaders.get(urlstr).pause();
         Button btn_start=(Button)((View)v.getParent()).findViewById(R.id.btn_start);
    Button btn_pause=(Button)((View)v.getParent()).findViewById(R.id.btn_pause);
         btn_pause.setVisibility(View.GONE);
         btn_start.setVisibility(View.VISIBLE);
     }
 }

 这是一个信息的实体,记录了一些字典信息,可以认为是一个简单bean对象
package cn.demo.entity;
 /**
  *创建一个下载信息的实体类
  */
 public class DownloadInfo {
     private int threadId;//下载器id
     private int startPos;//开始点
     private int endPos;//结束点
     private int compeleteSize;//完成度
     private String url;//下载器网络标识
     public DownloadInfo(int threadId, int startPos, int endPos,
             int compeleteSize,String url) {
         this.threadId = threadId;
         this.startPos = startPos;
         this.endPos = endPos;
         this.compeleteSize = compeleteSize;
         this.url=url;
     }
     public DownloadInfo() {
     }
     public String getUrl() {
         return url;
     }
     public void setUrl(String url) {
         this.url = url;
     }
     public int getThreadId() {
         return threadId;
     }
     public void setThreadId(int threadId) {
         this.threadId = threadId;
     }
     public int getStartPos() {
         return startPos;
     }
     public void setStartPos(int startPos) {
         this.startPos = startPos;
     }
     public int getEndPos() {
         return endPos;
     }
     public void setEndPos(int endPos) {
         this.endPos = endPos;
     }
     public int getCompeleteSize() {
         return compeleteSize;
     }
     public void setCompeleteSize(int compeleteSize) {
         this.compeleteSize = compeleteSize;
     }
 
     @Override
     public String toString() {
         return "DownloadInfo [threadId=" + threadId
                 + ", startPos=" + startPos + ", endPos=" + endPos
                 + ", compeleteSize=" + compeleteSize +"]";
     }
 }

package cn.demo.entity;
 /**
  *自定义的一个记载下载器详细信息的类 
  */
 public class LoadInfo {
     public int fileSize;//文件大小
     private int complete;//完成度
     private String urlstring;//下载器标识
     public LoadInfo(int fileSize, int complete, String urlstring) {
         this.fileSize = fileSize;
         this.complete = complete;
         this.urlstring = urlstring;
     }
     public LoadInfo() {
     }
     public int getFileSize() {
         return fileSize;
     }
     public void setFileSize(int fileSize) {
         this.fileSize = fileSize;
     }
     public int getComplete() {
         return complete;
     }
     public void setComplete(int complete) {
         this.complete = complete;
     }
     public String getUrlstring() {
         return urlstring;
     }
     public void setUrlstring(String urlstring) {
         this.urlstring = urlstring;
     }
     @Override
     public String toString() {
         return "LoadInfo [fileSize=" + fileSize + ", complete=" + complete
                 + ", urlstring=" + urlstring + "]";
     }
 }


这是一个核心类,专门用来处理下载的
package cn.demo.service;
 
 import java.io.File;
import java.io.InputStream;
import java.io.RandomAccessFile;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;

import android.content.Context;
import android.os.AsyncTask;
import android.os.Handler;
import android.os.Message;
import android.util.Log;
import cn.demo.Dao.Dao;
import cn.demo.entity.DownloadInfo;
import cn.demo.entity.LoadInfo;
 
 public class Downloader {
     private String urlstr;// 下载的地址
     private String localfile;// 保存路径
     private int threadcount;// 线程数
     private Handler mHandler;// 消息处理器 
     private int fileSize;// 所要下载的文件的大小
     private Context context; 
     private List<DownloadInfo> infos;// 存放下载信息类的集合
     private static final int INIT = 1;//定义三种下载的状态:初始化状态,正在下载状态,暂停状态
     private static final int DOWNLOADING = 2;
     private static final int PAUSE = 3;
     private int state = INIT;
 
     public Downloader(String urlstr, String localfile, int threadcount,
             Context context, Handler mHandler) {
         this.urlstr = urlstr;
         this.localfile = localfile;
         this.threadcount = threadcount;
         this.mHandler = mHandler;
         this.context = context;
     }
     /**
      *判断是否正在下载 
      */
     public boolean isdownloading() {
         return state == DOWNLOADING;
     }
     /**
      * 得到downloader里的信息
      * 首先进行判断是否是第一次下载,如果是第一次就要进行初始化,并将下载器的信息保存到数据库中
      * 如果不是第一次下载,那就要从数据库中读出之前下载的信息(起始位置,结束为止,文件大小等),并将下载信息返回给下载器
      */
     public LoadInfo getDownloaderInfors() {
         if (isFirst(urlstr)) {
             Log.v("TAG", "isFirst");
             init();
             int range = fileSize / threadcount;
             infos = new ArrayList<DownloadInfo>();
             for (int i = 0; i < threadcount - 1; i++) {
                 DownloadInfo info = new DownloadInfo(i, i * range, (i + 1)* range - 1, 0, urlstr);
                 infos.add(info);
             }
             DownloadInfo info = new DownloadInfo(threadcount - 1,(threadcount - 1) * range, fileSize - 1, 0, urlstr);
             infos.add(info);
             //保存infos中的数据到数据库
             Dao.getInstance(context).saveInfos(infos);
             //创建一个LoadInfo对象记载下载器的具体信息
             LoadInfo loadInfo = new LoadInfo(fileSize, 0, urlstr);
             return loadInfo;
         } else {
             //得到数据库中已有的urlstr的下载器的具体信息
             infos = Dao.getInstance(context).getInfos(urlstr);
             Log.v("TAG", "not isFirst size=" + infos.size());
             int size = 0;
             int compeleteSize = 0;
             for (DownloadInfo info : infos) {
                 compeleteSize += info.getCompeleteSize();
                 size += info.getEndPos() - info.getStartPos() + 1;
             }
             return new LoadInfo(size, compeleteSize, urlstr);
         }
     }
 
     /**
      * 初始化
      */
     private void init() {
         try {
             URL url = new URL(urlstr);
             HttpURLConnection connection = (HttpURLConnection) url.openConnection();
             connection.setConnectTimeout(5000);
             connection.setRequestMethod("GET");
             fileSize = connection.getContentLength();
 
             File file = new File(localfile);
             if (!file.exists()) {
                 file.createNewFile();
             }
             // 本地访问文件
             RandomAccessFile accessFile = new RandomAccessFile(file, "rwd");
             accessFile.setLength(fileSize);
             accessFile.close();
             connection.disconnect();
         } catch (Exception e) {
             e.printStackTrace();
         }
     }  
     /**
      * 判断是否是第一次 下载
      */
     private boolean isFirst(String urlstr) {
         return Dao.getInstance(context).isHasInfors(urlstr);
     }
 
     /**
      * 利用线程开始下载数据
      */
     public void download() {
         if (infos != null) {
             if (state == DOWNLOADING)
                 return;
             state = DOWNLOADING;
             for (DownloadInfo info : infos) {
                 new MyThread(info.getThreadId(), info.getStartPos(),
                         info.getEndPos(), info.getCompeleteSize(),
                         info.getUrl()).start();
             }
         }
     }
 
     public class MyThread extends Thread {
         private int threadId;
         private int startPos;
         private int endPos;
         private int compeleteSize;
         private String urlstr;
 
         public MyThread(int threadId, int startPos, int endPos,
                 int compeleteSize, String urlstr) {
             this.threadId = threadId;
             this.startPos = startPos;
             this.endPos = endPos;
             this.compeleteSize = compeleteSize;
             this.urlstr = urlstr;
         }
         @Override
         public void run() {
             HttpURLConnection connection = null;
             RandomAccessFile randomAccessFile = null;
             InputStream is = null;
             try {
                 URL url = new URL(urlstr);
                 connection = (HttpURLConnection) url.openConnection();
                 connection.setConnectTimeout(5000);
                 connection.setRequestMethod("GET");
                 // 设置范围,格式为Range:bytes x-y;
                 connection.setRequestProperty("Range", "bytes="+(startPos + compeleteSize) + "-" + endPos);
 
                 randomAccessFile = new RandomAccessFile(localfile, "rwd");
                 randomAccessFile.seek(startPos + compeleteSize);
                 // 将要下载的文件写到保存在保存路径下的文件中
                 is = connection.getInputStream();
                 byte[] buffer = new byte[4096];
                 int length = -1;
                 while ((length = is.read(buffer)) != -1) {
                     randomAccessFile.write(buffer, 0, length);
                     compeleteSize += length;
                     // 更新数据库中的下载信息
                     Dao.getInstance(context).updataInfos(threadId, compeleteSize, urlstr);
                     // 用消息将下载信息传给进度条,对进度条进行更新
                     Message message = Message.obtain();
                     message.what = 1;
                     message.obj = urlstr;
                     message.arg1 = length;
                     mHandler.sendMessage(message);
                     if (state == PAUSE) {
                         return;
                     }
                 }
             } catch (Exception e) {
                 e.printStackTrace();
             }  
         }
     }
     //删除数据库中urlstr对应的下载器信息
     public void delete(String urlstr) {
      Dao.getInstance(context).delete(urlstr);
     }
     //设置暂停
     public void pause() {
         state = PAUSE;
     }
     //重置下载状态
     public void reset() {
         state = INIT;
     }
 }

以下是些xml文件
<?xml version="1.0" encoding="utf-8"?>
 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
            android:orientation="vertical"
            android:layout_width="fill_parent"
            android:layout_height="wrap_content">
     <LinearLayout
            android:orientation="horizontal"
            android:layout_width="fill_parent"
            android:layout_height="wrap_content"
            android:layout_marginBottom="5dip">
         <TextView 
             android:layout_width="fill_parent"
             android:layout_height="wrap_content"
             android:layout_weight="1"
             android:id="@+id/tv_resouce_name"/>
         <Button
             android:layout_width="fill_parent"
             android:layout_height="wrap_content"
             android:layout_weight="1"
             android:text="下载"
             android:id="@+id/btn_start"
             android:onClick="startDownload"/>
         <Button
             android:layout_width="fill_parent"
             android:layout_height="wrap_content"
             android:layout_weight="1"
             android:text="暂停"
             android:visibility="gone"
             android:id="@+id/btn_pause"
             android:onClick="pauseDownload"/>
       </LinearLayout>
 </LinearLayout>

<?xml version="1.0" encoding="utf-8"?>
 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
     android:orientation="vertical"
     android:layout_width="fill_parent"
     android:layout_height="fill_parent"
     android:id="@+id/llRoot">
     <ListView android:id="@android:id/list"
         android:layout_width="fill_parent"
         android:layout_height="fill_parent">
     </ListView>
 </LinearLayout>

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="cn.demo.download"
    android:versionCode="1"
    android:versionName="1.0">

    <uses-sdk android:minSdkVersion="8" android:targetSdkVersion="8" />
 <uses-permission android:name="android.permission.INTERNET"/> 
 <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
    <application android:label="@string/app_name"
        android:icon="@drawable/ic_launcher"
        android:theme="@style/AppTheme">
  <activity
             android:name=".MainActivity" 
             android:label="@string/app_name" >
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" /> 
                 <category android:name="android.intent.category.LAUNCHER" />
             </intent-filter>
         </activity>
    </application>

</manifest>

运行效果如下


1 則留言:

  1. Mang thai hộ là gì?Không phải bất kì người phụ nữ nào cũng có khả năng sinh con được, nhiều yếu tố khiến không ít người phụ nữ phải gặp phải tình trạng vô sinh
    Mang thai 3 tháng cuối cần chú ý những gì?Giữ cho mình một tinh thần thoải mái, thư giãn. Có thể đăng ký vài suất massage thư giãn cho mẹ bầu để giảm bớt mệt mỏi và áp lực trong những ngày cuối thai kỳ.
    Tư thế nằm ngủ có lợi cho sức khoẻ của bà bầuGiấc ngủ đối với chúng ta là hết sức quan trọng, nó ảnh hưởng nghiêm trọng đến tình trạng sức khỏe, cũng như tinh thần của con người.
    Lợi ích của nước đối với sức khoẻ bà bầuTáo bón là một vấn đề ở đường tiêu hóa xảy ra khá phổ biến, nhất là trong thời kỳ mang thai. Táo bón tuy không quá nguy hiểm, nhưng rõ ràng khi bị mắc táo bón sẽ chẳng dễ chịu chút nào
    Khi mang thai ăn trứng ngỗng có tác dụng gì?Các ông bà xưa thường có câu: Ăn 7 trứng ngỗng sẽ sinh con trai, ăn 9 trứng ngỗng sinh con gái, chính vì thế mà mọi người tin tưởng vào việc ăn trứng ngông sẽ có tác dụng.
    Trứng đà điểu có tác dụng gì?Trứng đà điểu còn được dùng để quấy cùng bột cho trẻ em, món ăn đầy dinh dưỡng và giúp trẻ thông minh hơn.
    Dấu hiệu bà bầu sắp sinhĐã đến đến tháng của thai kì, thì bất kì người phụ nữ nào cũng tò mò, bồn chồn trông ngóng thiên thần mình được chào đời.
    Bà bầu ăn mực có sao không?Nhưng cũng có nhiều người có ý kiến rằng ăn mực chứa nhiều vitamin, canxi, protein rất tốt cho bà bầu và sự phát triển của thai nhi.
    Bệnh gút là gì?Cuộc sống càng ngày càng phát triển, con người có cuộc sống ấm no, đầy đủ hơn trước, nhưng chính điều này lại khiến nhiều người mắc bệnh nhiều hơn.
    Bệnh gút và cách điều trịBệnh gút là bệnh thuộc dạng viêm khớp, bệnh thường gặp ở nam giới nhiều hơn với các nữ giới, đây là bệnh khiến nhiều người lo lắng vì các cơn đau khó chịu từ các khớp
    Cách chữa bệnh gút hiệu quả bằng thảo dượcBệnh gút còn được gọi là căn bệnh nhà giàu, thường gặp phải ở những người trung niên, đặc biệt là ở nam giới

    回覆刪除