Android 文件上传与下载

news2024/11/15 20:13:40

在实际开发涉及文件上传不会自己写上传代码,一般 会集成第三网络库来做图片上传,比如android-async-http,okhttp等,另外还有七牛也提供 了下载和上传的API。

1.项目用到的图片上传的关键方法:

这里用到一个第三方的库: android-async-http.jar,自己到github下下这个库~然后调用一下下面的方法即可,自己改下url!
上传图片的核心方法如下:

private void sendImage(Bitmap bm)
{
    ByteArrayOutputStream stream = new ByteArrayOutputStream();
    bm.compress(Bitmap.CompressFormat.PNG, 60, stream);
    byte[] bytes = stream.toByteArray();
    String img = new String(Base64.encodeToString(bytes, Base64.DEFAULT));
    AsyncHttpClient client = new AsyncHttpClient();
    RequestParams params = new RequestParams();
    params.add("img", img);
    client.post("http:xxx/postIcon", params, new AsyncHttpResponseHandler() {
        @Override
        public void onSuccess(int i, Header[] headers, byte[] bytes) {
            Toast.makeText(MainActivity.this, "Upload Success!", Toast.LENGTH_LONG).show();

        }
        @Override
        public void onFailure(int i, Header[] headers, byte[] bytes, Throwable throwable) {
            Toast.makeText(MainActivity.this, "Upload Fail!", Toast.LENGTH_LONG).show();
        }
    });
}

2.使用HttpConnection上传文件:

public class SocketHttpRequester   
{  
    /** 
     * 发送xml数据 
     * @param path 请求地址 
     * @param xml xml数据 
     * @param encoding 编码 
     * @return 
     * @throws Exception 
     */  
    public static byte[] postXml(String path, String xml, String encoding) throws Exception{  
        byte[] data = xml.getBytes(encoding);  
        URL url = new URL(path);  
        HttpURLConnection conn = (HttpURLConnection)url.openConnection();  
        conn.setRequestMethod("POST");  
        conn.setDoOutput(true);  
        conn.setRequestProperty("Content-Type", "text/xml; charset="+ encoding);  
        conn.setRequestProperty("Content-Length", String.valueOf(data.length));  
        conn.setConnectTimeout(5 * 1000);  
        OutputStream outStream = conn.getOutputStream();  
        outStream.write(data);  
        outStream.flush();  
        outStream.close();  
        if(conn.getResponseCode()==200){  
            return readStream(conn.getInputStream());  
        }  
        return null;  
    }  
      
    /** 
     * 直接通过HTTP协议提交数据到服务器,实现如下面表单提交功能: 
     *   <FORM METHOD=POST ACTION="http://192.168.0.200:8080/ssi/fileload/test.do" enctype="multipart/form-data"> 
            <INPUT TYPE="text" NAME="name"> 
            <INPUT TYPE="text" NAME="id"> 
            <input type="file" name="imagefile"/> 
            <input type="file" name="zip"/> 
         </FORM> 
     * @param path 上传路径(注:避免使用localhost或127.0.0.1这样的路径测试, 
     *                  因为它会指向手机模拟器,你可以使用http://www.baidu.com或http://192.168.1.10:8080这样的路径测试) 
     * @param params 请求参数 key为参数名,value为参数值 
     * @param file 上传文件 
     */  
    public static boolean post(String path, Map<String, String> params, FormFile[] files) throws Exception  
    {     
        //数据分隔线  
        final String BOUNDARY = "---------------------------7da2137580612";   
        //数据结束标志"---------------------------7da2137580612--"  
        final String endline = "--" + BOUNDARY + "--/r/n";  
          
        //下面两个for循环都是为了得到数据长度参数,依据表单的类型而定  
        //首先得到文件类型数据的总长度(包括文件分割线)  
        int fileDataLength = 0;  
        for(FormFile uploadFile : files)  
        {  
            StringBuilder fileExplain = new StringBuilder();  
            fileExplain.append("--");  
            fileExplain.append(BOUNDARY);  
            fileExplain.append("/r/n");  
            fileExplain.append("Content-Disposition: form-data;name=/""+ uploadFile.getParameterName()+"/";filename=/""+ uploadFile.getFilname() + "/"/r/n");  
            fileExplain.append("Content-Type: "+ uploadFile.getContentType()+"/r/n/r/n");  
            fileExplain.append("/r/n");  
            fileDataLength += fileExplain.length();  
            if(uploadFile.getInStream()!=null){  
                fileDataLength += uploadFile.getFile().length();  
            }else{  
                fileDataLength += uploadFile.getData().length;  
            }  
        }  
        //再构造文本类型参数的实体数据  
        StringBuilder textEntity = new StringBuilder();          
        for (Map.Entry<String, String> entry : params.entrySet())   
        {    
            textEntity.append("--");  
            textEntity.append(BOUNDARY);  
            textEntity.append("/r/n");  
            textEntity.append("Content-Disposition: form-data; name=/""+ entry.getKey() + "/"/r/n/r/n");  
            textEntity.append(entry.getValue());  
            textEntity.append("/r/n");  
        }  
          
        //计算传输给服务器的实体数据总长度(文本总长度+数据总长度+分隔符)  
        int dataLength = textEntity.toString().getBytes().length + fileDataLength +  endline.getBytes().length;  
          
        URL url = new URL(path);  
        //默认端口号其实可以不写  
        int port = url.getPort()==-1 ? 80 : url.getPort();  
        //建立一个Socket链接  
        Socket socket = new Socket(InetAddress.getByName(url.getHost()), port);  
        //获得一个输出流(从Android流到web)  
        OutputStream outStream = socket.getOutputStream();  
        //下面完成HTTP请求头的发送  
        String requestmethod = "POST "+ url.getPath()+" HTTP/1.1/r/n";  
        outStream.write(requestmethod.getBytes());  
        //构建accept  
        String accept = "Accept: image/gif, image/jpeg, image/pjpeg, image/pjpeg, application/x-shockwave-flash, application/xaml+xml, application/vnd.ms-xpsdocument, application/x-ms-xbap, application/x-ms-application, application/vnd.ms-excel, application/vnd.ms-powerpoint, application/msword, */*/r/n";  
        outStream.write(accept.getBytes());  
        //构建language  
        String language = "Accept-Language: zh-CN/r/n";  
        outStream.write(language.getBytes());  
        //构建contenttype  
        String contenttype = "Content-Type: multipart/form-data; boundary="+ BOUNDARY+ "/r/n";  
        outStream.write(contenttype.getBytes());  
        //构建contentlength  
        String contentlength = "Content-Length: "+ dataLength + "/r/n";  
        outStream.write(contentlength.getBytes());  
        //构建alive  
        String alive = "Connection: Keep-Alive/r/n";          
        outStream.write(alive.getBytes());  
        //构建host  
        String host = "Host: "+ url.getHost() +":"+ port +"/r/n";  
        outStream.write(host.getBytes());  
        //写完HTTP请求头后根据HTTP协议再写一个回车换行  
        outStream.write("/r/n".getBytes());  
        //把所有文本类型的实体数据发送出来  
        outStream.write(textEntity.toString().getBytes());           
          
        //把所有文件类型的实体数据发送出来  
        for(FormFile uploadFile : files)  
        {  
            StringBuilder fileEntity = new StringBuilder();  
            fileEntity.append("--");  
            fileEntity.append(BOUNDARY);  
            fileEntity.append("/r/n");  
            fileEntity.append("Content-Disposition: form-data;name=/""+ uploadFile.getParameterName()+"/";filename=/""+ uploadFile.getFilname() + "/"/r/n");  
            fileEntity.append("Content-Type: "+ uploadFile.getContentType()+"/r/n/r/n");  
            outStream.write(fileEntity.toString().getBytes());  
            //边读边写  
            if(uploadFile.getInStream()!=null)  
            {  
                byte[] buffer = new byte[1024];  
                int len = 0;  
                while((len = uploadFile.getInStream().read(buffer, 0, 1024))!=-1)  
                {  
                    outStream.write(buffer, 0, len);  
                }  
                uploadFile.getInStream().close();  
            }  
            else  
            {  
                outStream.write(uploadFile.getData(), 0, uploadFile.getData().length);  
            }  
            outStream.write("/r/n".getBytes());  
        }  
        //下面发送数据结束标志,表示数据已经结束  
        outStream.write(endline.getBytes());          
        BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));  
        //读取web服务器返回的数据,判断请求码是否为200,如果不是200,代表请求失败  
        if(reader.readLine().indexOf("200")==-1)  
        {  
            return false;  
        }  
        outStream.flush();  
        outStream.close();  
        reader.close();  
        socket.close();  
        return true;  
    }  
      
    /**  
     * 提交数据到服务器  
     * @param path 上传路径(注:避免使用localhost或127.0.0.1这样的路径测试,因为它会指向手机模拟器,你可以使用http://www.baidu.com或http://192.168.1.10:8080这样的路径测试)  
     * @param params 请求参数 key为参数名,value为参数值  
     * @param file 上传文件  
     */  
    public static boolean post(String path, Map<String, String> params, FormFile file) throws Exception  
    {  
       return post(path, params, new FormFile[]{file});  
    }  
    /** 
     * 提交数据到服务器 
     * @param path 上传路径(注:避免使用localhost或127.0.0.1这样的路径测试,因为它会指向手机模拟器,你可以使用http://www.baidu.com或http://192.168.1.10:8080这样的路径测试) 
     * @param params 请求参数 key为参数名,value为参数值 
     * @param encode 编码 
     */  
    public static byte[] postFromHttpClient(String path, Map<String, String> params, String encode) throws Exception  
    {  
        //用于存放请求参数  
        List<NameValuePair> formparams = new ArrayList<NameValuePair>();  
        for(Map.Entry<String, String> entry : params.entrySet())  
        {  
            formparams.add(new BasicNameValuePair(entry.getKey(), entry.getValue()));  
        }  
        UrlEncodedFormEntity entity = new UrlEncodedFormEntity(formparams, encode);  
        HttpPost httppost = new HttpPost(path);  
        httppost.setEntity(entity);  
        //看作是浏览器  
        HttpClient httpclient = new DefaultHttpClient();  
        //发送post请求    
        HttpResponse response = httpclient.execute(httppost);     
        return readStream(response.getEntity().getContent());  
    }  
    /** 
     * 发送请求 
     * @param path 请求路径 
     * @param params 请求参数 key为参数名称 value为参数值 
     * @param encode 请求参数的编码 
     */  
    public static byte[] post(String path, Map<String, String> params, String encode) throws Exception  
    {  
        //String params = "method=save&name="+ URLEncoder.encode("老毕", "UTF-8")+ "&age=28&";//需要发送的参数  
        StringBuilder parambuilder = new StringBuilder("");  
        if(params!=null && !params.isEmpty())  
        {  
            for(Map.Entry<String, String> entry : params.entrySet())  
            {  
                parambuilder.append(entry.getKey()).append("=")  
                    .append(URLEncoder.encode(entry.getValue(), encode)).append("&");  
            }  
            parambuilder.deleteCharAt(parambuilder.length()-1);  
        }  
        byte[] data = parambuilder.toString().getBytes();  
        URL url = new URL(path);  
        HttpURLConnection conn = (HttpURLConnection)url.openConnection();  
        //设置允许对外发送请求参数  
        conn.setDoOutput(true);  
        //设置不进行缓存  
        conn.setUseCaches(false);  
        conn.setConnectTimeout(5 * 1000);  
        conn.setRequestMethod("POST");  
        //下面设置http请求头  
        conn.setRequestProperty("Accept", "image/gif, image/jpeg, image/pjpeg, image/pjpeg, application/x-shockwave-flash, application/xaml+xml, application/vnd.ms-xpsdocument, application/x-ms-xbap, application/x-ms-application, application/vnd.ms-excel, application/vnd.ms-powerpoint, application/msword, */*");  
        conn.setRequestProperty("Accept-Language", "zh-CN");  
        conn.setRequestProperty("User-Agent", "Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 5.2; Trident/4.0; .NET CLR 1.1.4322; .NET CLR 2.0.50727; .NET CLR 3.0.04506.30; .NET CLR 3.0.4506.2152; .NET CLR 3.5.30729)");  
        conn.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");  
        conn.setRequestProperty("Content-Length", String.valueOf(data.length));  
        conn.setRequestProperty("Connection", "Keep-Alive");  
          
        //发送参数  
        DataOutputStream outStream = new DataOutputStream(conn.getOutputStream());  
        outStream.write(data);//把参数发送出去  
        outStream.flush();  
        outStream.close();  
        if(conn.getResponseCode()==200)  
        {  
            return readStream(conn.getInputStream());  
        }  
        return null;  
    }  
      
    /**  
     * 读取流  
     * @param inStream  
     * @return 字节数组  
     * @throws Exception  
     */  
    public static byte[] readStream(InputStream inStream) throws Exception  
    {  
        ByteArrayOutputStream outSteam = new ByteArrayOutputStream();  
        byte[] buffer = new byte[1024];  
        int len = -1;  
        while( (len=inStream.read(buffer)) != -1)  
        {  
            outSteam.write(buffer, 0, len);  
        }  
        outSteam.close();  
        inStream.close();  
        return outSteam.toByteArray();  
    }  
}  

3.普通单线程下载文件:

直接使用URLConnection.openStream()打开网络输入流,然后将流写入到文件中!
核心方法:

public static void downLoad(String path,Context context)throws Exception
{
  URL url = new URL(path);
  InputStream is = url.openStream();
  //截取最后的文件名
  String end = path.substring(path.lastIndexOf("."));
  //打开手机对应的输出流,输出到文件中
  OutputStream os = context.openFileOutput("Cache_"+System.currentTimeMillis()+end, Context.MODE_PRIVATE);
  byte[] buffer = new byte[1024];
  int len = 0;
  //从输入六中读取数据,读到缓冲区中
  while((len = is.read(buffer)) > 0)
  {
    os.write(buffer,0,len);
  }
  //关闭输入输出流
  is.close();
  os.close();
}

在这里插入图片描述
在这里插入图片描述

4.普通多线程下载:

我们都知道使用多线程下载文件可以更快地完成文件的下载,但是为什么呢?
答:因为抢占的服务器资源多,假设服务器最多服务100个用户,服务器中的一个线程 对应一个用户100条线程在计算机中并发执行,由CPU划分时间片轮流执行,加入a有99条线程 下载文件,那么相当于占用了99个用户资源,自然就有用较快的下载速度

PS:当然不是线程越多就越好,开启过多线程的话,app需要维护和同步每条线程的开销, 这些开销反而会导致下载速度的降低,另外还和你的网速有关!

多线程下载的流程:
获取网络连接
本地磁盘创建相同大小的空文件
计算每条线程需从文件哪个部分开始下载,结束
依次创建,启动多条线程来下载网络资源的指定部分

在这里插入图片描述
PS:这里直接创建一个Java项目,然后在JUnit里运行指定方法即可,
核心代码如下:

public class Downloader {
  //添加@Test标记是表示该方法是Junit测试的方法,就可以直接运行该方法了
    @Test
    public void download() throws Exception
    {
      //设置URL的地址和下载后的文件名
      String filename = "meitu.exe";
      String path = "http://10.13.20.32:8080/Test/XiuXiu_Green.exe";
      URL url = new URL(path);
      HttpURLConnection conn = (HttpURLConnection) url.openConnection();
      conn.setConnectTimeout(5000);
      conn.setRequestMethod("GET");
      //获得需要下载的文件的长度(大小)
      int filelength = conn.getContentLength();
      System.out.println("要下载的文件长度"+filelength);
      //生成一个大小相同的本地文件
      RandomAccessFile file = new RandomAccessFile(filename, "rwd");
      file.setLength(filelength);
      file.close();
      conn.disconnect();
      //设置有多少条线程下载
      int threadsize = 3;
      //计算每个线程下载的量
      int threadlength = filelength % 3 == 0 ? filelength/3:filelength+1;
      for(int i = 0;i < threadsize;i++)
      {
        //设置每条线程从哪个位置开始下载
        int startposition = i * threadlength;
        //从文件的什么位置开始写入数据
        RandomAccessFile threadfile = new RandomAccessFile(filename, "rwd");
        threadfile.seek(startposition);
        //启动三条线程分别从startposition位置开始下载文件
        new DownLoadThread(i,startposition,threadfile,threadlength,path).start();
      }
      int quit = System.in.read();
      while('q' != quit)
      {
        Thread.sleep(2000);
      }
    }
  
  
  private class DownLoadThread extends Thread {
    private int threadid;
    private int startposition;
    private RandomAccessFile threadfile;
    private int threadlength;
    private String path;
    public DownLoadThread(int threadid, int startposition,
        RandomAccessFile threadfile, int threadlength, String path) {
      this.threadid = threadid;
      this.startposition = startposition;
      this.threadfile = threadfile;
      this.threadlength = threadlength;
      this.path = path;
    }
    public DownLoadThread() {}
    @Override
    public void run() {
      try
      {
        URL url = new URL(path);
        HttpURLConnection conn = (HttpURLConnection) url.openConnection();
        conn.setConnectTimeout(5000);
        conn.setRequestMethod("GET");
        //指定从什么位置开始下载
        conn.setRequestProperty("Range", "bytes="+startposition+"-");
        //System.out.println(conn.getResponseCode());
        if(conn.getResponseCode() == 206)
        {
          InputStream is = conn.getInputStream();
          byte[] buffer = new byte[1024];
          int len = -1;
          int length = 0;
          while(length < threadlength && (len = is.read(buffer)) != -1)
          {
            threadfile.write(buffer,0,len);
            //计算累计下载的长度
            length += len;
          }
          threadfile.close();
          is.close();
          System.out.println("线程"+(threadid+1) + "已下载完成");
        }
      }catch(Exception ex){System.out.println("线程"+(threadid+1) + "下载出错"+ ex);}
    }
    
  }
}

在这里插入图片描述
在这里插入图片描述
注意事项:
int filelength = conn.getContentLength(); //获得下载文件的长度(大小)
RandomAccessFile file = new RandomAccessFile(filename, “rwd”); //该类运行对文件进行读写,是多线程下载的核心
nt threadlength = filelength % 3 == 0 ? filelength/3:filelength+1; //计算每个线程要下载的量
conn.setRequestProperty(“Range”, “bytes=”+startposition+“-”); //指定从哪个位置开始读写,这个是URLConnection提供的方法
//System.out.println(conn.getResponseCode()); //这个注释了的代码是用来查看conn的返回码的,我们前面用的都是200, 而针对多线程的话,通常是206,必要时我们可以通过调用该方法查看返回码!
int quit = System.in.read();while(‘q’ != quit){Thread.sleep(2000);} //这段代码是做延时操作的,因为我们用的是本地下载,可能该方法运行完了,而我们的 线程还没有开启,这样会引发异常,这里的话,让用户输入一个字符,如果是’q’的话就退出

5.使用DownloadManager更新应用并覆盖安装:

下面的代码可以直接用,加入到项目后,记得为这个内部广播注册一个过滤器:
AndroidManifest.xml

import android.app.DownloadManager;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
import android.os.Environment;
import android.support.v7.app.AppCompatActivity;

/**
 * Created by Jay on 2015/9/9 0009.
 */
public class UpdateAct extends AppCompatActivity {
    //这个更新的APK的版本部分,我们是这样命名的:xxx_v1.0.0_xxxxxxxxx.apk
    //这里我们用的是git提交版本的前九位作为表示
    private static final String FILE_NAME = "ABCDEFGHI";

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        String endpoint = "";
        try {
            //这部分是获取AndroidManifest.xml里的配置信息的,包名,以及Meta_data里保存的东西
            ApplicationInfo info = getPackageManager().getApplicationInfo(
                    getPackageName(), PackageManager.GET_META_DATA);
            //我们在meta_data保存了xxx.xxx这样一个数据,是https开头的一个链接,这里替换成http
            endpoint = info.metaData.getString("xxxx.xxxx").replace("https",
                    "http");
        } catch (PackageManager.NameNotFoundException e) {
            e.printStackTrace();
        }
        //下面的都是拼接apk更新下载url的,path是保存的文件夹路径
        final String _Path = this.getIntent().getStringExtra("path");
        final String _Url = endpoint + _Path;
        final DownloadManager _DownloadManager = (DownloadManager) getSystemService(DOWNLOAD_SERVICE);
        DownloadManager.Request _Request = new DownloadManager.Request(
                Uri.parse(_Url));
        _Request.setDestinationInExternalPublicDir(
                Environment.DIRECTORY_DOWNLOADS, FILE_NAME + ".apk");
        _Request.setTitle(this.getString(R.string.app_name));
        //是否显示下载对话框
        _Request.setShowRunningNotification(true);
        _Request.setMimeType("application/com.trinea.download.file");
        //将下载请求放入队列
        _DownloadManager.enqueue(_Request);
        this.finish();
    }



    //注册一个广播接收器,当下载完毕后会收到一个android.intent.action.DOWNLOAD_COMPLETE
    //的广播,在这里取出队列里下载任务,进行安装
    public static class Receiver extends BroadcastReceiver {
        public void onReceive(Context context, Intent intent) {
            final DownloadManager _DownloadManager = (DownloadManager) context
                    .getSystemService(Context.DOWNLOAD_SERVICE);
            final long _DownloadId = intent.getLongExtra(
                    DownloadManager.EXTRA_DOWNLOAD_ID, 0);
            final DownloadManager.Query _Query = new DownloadManager.Query();
            _Query.setFilterById(_DownloadId);
            final Cursor _Cursor = _DownloadManager.query(_Query);
            if (_Cursor.moveToFirst()) {
                final int _Status = _Cursor.getInt(_Cursor
                        .getColumnIndexOrThrow(DownloadManager.COLUMN_STATUS));
                final String _Name = _Cursor.getString(_Cursor
                        .getColumnIndexOrThrow("local_filename"));
                if (_Status == DownloadManager.STATUS_SUCCESSFUL
                        && _Name.indexOf(FILE_NAME) != 0) {

                    Intent _Intent = new Intent(Intent.ACTION_VIEW);
                    _Intent.setDataAndType(
                            Uri.parse(_Cursor.getString(_Cursor
                                    .getColumnIndexOrThrow(DownloadManager.COLUMN_LOCAL_URI))),
                            "application/vnd.android.package-archive");
                    _Intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
                    context.startActivity(_Intent);
                }
            }
            _Cursor.close();
        }
    }
}

6.Android多线程断点下载的代码流程解析:

在这里插入图片描述
实现流程全解析:

Step 1:创建一个用来记录线程下载信息的表

创建数据库表,于是乎我们创建一个数据库的管理器类,继承SQLiteOpenHelper类 重写onCreate()与onUpgrade()方法,我们创建的表字段如下:
在这里插入图片描述
DBOpenHelper.java:

package com.jay.example.db;
import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteDatabase.CursorFactory;
import android.database.sqlite.SQLiteOpenHelper;

public class DBOpenHelper extends SQLiteOpenHelper {
  public DBOpenHelper(Context context) {
    super(context, "downs.db", null, 1);
  }
  @Override
  public void onCreate(SQLiteDatabase db) {
    //数据库的结构为:表名:filedownlog 字段:id,downpath:当前下载的资源,
    //threadid:下载的线程id,downlength:线程下载的最后位置
    db.execSQL("CREATE TABLE IF NOT EXISTS filedownlog " +
        "(id integer primary key autoincrement," +
        " downpath varchar(100)," +
        " threadid INTEGER, downlength INTEGER)");
  }
  @Override
  public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
    //当版本号发生改变时调用该方法,这里删除数据表,在实际业务中一般是要进行数据备份的
    db.execSQL("DROP TABLE IF EXISTS filedownlog");
    onCreate(db);
  }

}

Step 2:创建一个数据库操作类

我们需要创建什么样的方法呢?
①我们需要一个根据URL获得每条线程当前下载长度的方法
②接着,当我们的线程新开辟后,我们需要往数据库中插入与该线程相关参数的方法
③还要定义一个可以实时更新下载文件长度的方法
④我们线程下载完,还需要根据线程id,删除对应记录的方法

FileService.java

package com.jay.example.db;

import java.util.HashMap;
import java.util.Map;
import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;

/*
 * 该类是一个业务bean类,完成数据库的相关操作
 * */

public class FileService {
  //声明数据库管理器
  private DBOpenHelper openHelper;
  
  //在构造方法中根据上下文对象实例化数据库管理器
  public FileService(Context context) {
    openHelper = new DBOpenHelper(context);
  }
  
  /**
   * 获得指定URI的每条线程已经下载的文件长度
   * @param path
   * @return 
   * */
  public Map<Integer, Integer> getData(String path)
  {
    //获得可读数据库句柄,通常内部实现返回的其实都是可写的数据库句柄
    SQLiteDatabase db = openHelper.getReadableDatabase();
    //根据下载的路径查询所有现场的下载数据,返回的Cursor指向第一条记录之前
    Cursor cursor = db.rawQuery("select threadid, downlength from filedownlog where downpath=?",
        new String[]{path});
    //建立一个哈希表用于存放每条线程已下载的文件长度
    Map<Integer,Integer> data = new HashMap<Integer, Integer>();
    //从第一条记录开始遍历Cursor对象
    cursor.moveToFirst();
    while(cursor.moveToNext())
    {
      //把线程id与该线程已下载的长度存放到data哈希表中
      data.put(cursor.getInt(0), cursor.getInt(1));
      data.put(cursor.getInt(cursor.getColumnIndexOrThrow("threadid")),
          cursor.getInt(cursor.getColumnIndexOrThrow("downlength")));
    }
    cursor.close();//关闭cursor,释放资源;
    db.close();
    return data;
  }
  
  /**
   * 保存每条线程已经下载的文件长度
   * @param path 下载的路径
   * @param map 现在的di和已经下载的长度的集合
  */
  public void save(String path,Map<Integer,Integer> map)
  {
    SQLiteDatabase db = openHelper.getWritableDatabase();
    //开启事务,因为此处需要插入多条数据
    db.beginTransaction();
    try{
      //使用增强for循环遍历数据集合
      for(Map.Entry<Integer, Integer> entry : map.entrySet())
      {
        //插入特定下载路径特定线程ID已经下载的数据
        db.execSQL("insert into filedownlog(downpath, threadid, downlength) values(?,?,?)",
            new Object[]{path, entry.getKey(), entry.getValue()});
      }
      //设置一个事务成功的标志,如果成功就提交事务,如果没调用该方法的话那么事务回滚
      //就是上面的数据库操作撤销
      db.setTransactionSuccessful();
    }finally{
      //结束一个事务
      db.endTransaction();
    }
    db.close();
  }
  
  /**
   * 实时更新每条线程已经下载的文件长度
   * @param path
   * @param map
   */
  public void update(String path,int threadId,int pos)
  {
    SQLiteDatabase db = openHelper.getWritableDatabase();
    //更新特定下载路径下特定线程已下载的文件长度
    db.execSQL("update filedownlog set downlength=? where downpath=? and threadid=?",
        new Object[]{pos, path, threadId});
    db.close();
  }
  
  
  /**
   *当文件下载完成后,删除对应的下载记录
   *@param path 
   */
  public void delete(String path)
  {
    SQLiteDatabase db = openHelper.getWritableDatabase();
    db.execSQL("delete from filedownlog where downpath=?", new Object[]{path});
    db.close();
  }
  
}

Step 3:创建一个文件下载器类

好了,数据库管理器与操作类都完成了接着就该弄一个文件下载器类了,在该类中又要完成 什么操作呢?要做的事就多了:
①定义一堆变量,核心是线程池threads和同步集合ConcurrentHashMap,用于缓存线程下载长度的
②定义一个获取线程池中线程数的方法;
③定义一个退出下载的方法,
④获取当前文件大小的方法
⑤累计当前已下载长度的方法,这里需要添加一个synchronized关键字,用来解决并发访问的问题
⑥更新指定线程最后的下载位置,同样也需要用同步
⑦在构造方法中完成文件下载,线程开辟等操作
⑧获取文件名的方法:先截取提供的url最后的’/'后面的字符串,如果获取不到,再从头字段查找,还是 找不到的话,就使用网卡标识数字+cpu的唯一数字生成一个16个字节的二进制作为文件名
⑨开始下载文件的方法
⑩获取http响应头字段的方法
⑪打印http头字段的方法
12.打印日志信息的方法
FileDownloadered.java:

package com.jay.example.service;

import java.io.File;
import java.io.RandomAccessFile;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import android.content.Context;
import android.util.Log;





import com.jay.example.db.FileService;

public class FileDownloadered {
  
  private static final String TAG = "文件下载类";  //设置一个查log时的一个标志
  private static final int RESPONSEOK = 200;    //设置响应码为200,代表访问成功
  private FileService fileService;        //获取本地数据库的业务Bean
  private boolean exited;             //停止下载的标志
  private Context context;            //程序的上下文对象
  private int downloadedSize = 0;               //已下载的文件长度
  private int fileSize = 0;           //开始的文件长度
  private DownloadThread[] threads;        //根据线程数设置下载的线程池
  private File saveFile;              //数据保存到本地的文件中
  private Map<Integer, Integer> data = new ConcurrentHashMap<Integer, Integer>();  //缓存个条线程的下载的长度
  private int block;                            //每条线程下载的长度
  private String downloadUrl;                   //下载的路径
  
  
  /**
   * 获取线程数
   */
  public int getThreadSize()
  {
    //return threads.length;
    return 0;
  }
  
  /**
   * 退出下载
   * */
  public void exit()
  {
    this.exited = true;    //将退出的标志设置为true;
  }
  public boolean getExited()
  {
    return this.exited;
  }
  
  /**
   * 获取文件的大小
   * */
  public int getFileSize()
  {
    return fileSize;
  }
  
  /**
   * 累计已下载的大小
   * 使用同步锁来解决并发的访问问题
   * */
  protected synchronized void append(int size)
  {
    //把实时下载的长度加入到总的下载长度中
    downloadedSize += size;
  }
  
  /**
   * 更新指定线程最后下载的位置
   * @param threadId 线程id
   * @param pos 最后下载的位置
   * */
  protected synchronized void update(int threadId,int pos)
  {
    //把指定线程id的线程赋予最新的下载长度,以前的值会被覆盖掉
    this.data.put(threadId, pos);
    //更新数据库中制定线程的下载长度
    this.fileService.update(this.downloadUrl, threadId, pos);
  }
  
  
  /**
   * 构建文件下载器
   * @param downloadUrl 下载路径
   * @param fileSaveDir 文件的保存目录
   * @param threadNum  下载线程数
   * @return 
   */
  public FileDownloadered(Context context,String downloadUrl,File fileSaveDir,int threadNum)
  {
    try {
      this.context = context;     //获取上下文对象,赋值
      this.downloadUrl = downloadUrl;  //为下载路径赋值
      fileService = new FileService(this.context);   //实例化数据库操作的业务Bean类,需要传一个context值
      URL url = new URL(this.downloadUrl);     //根据下载路径实例化URL
      if(!fileSaveDir.exists()) fileSaveDir.mkdir();  //如果文件不存在的话指定目录,这里可创建多层目录
      this.threads = new DownloadThread[threadNum];   //根据下载的线程数量创建下载的线程池
      
      
      HttpURLConnection conn = (HttpURLConnection) url.openConnection();   //创建远程连接句柄,这里并未真正连接
      conn.setConnectTimeout(5000);      //设置连接超时事件为5秒
      conn.setRequestMethod("GET");      //设置请求方式为GET
      //设置用户端可以接收的媒体类型
      conn.setRequestProperty("Accept", "image/gif, image/jpeg, image/pjpeg, " +
          "image/pjpeg, application/x-shockwave-flash, application/xaml+xml, " +
          "application/vnd.ms-xpsdocument, application/x-ms-xbap," +
          " application/x-ms-application, application/vnd.ms-excel," +
          " application/vnd.ms-powerpoint, application/msword, */*");
      
      conn.setRequestProperty("Accept-Language", "zh-CN");  //设置用户语言
      conn.setRequestProperty("Referer", downloadUrl);    //设置请求的来源页面,便于服务端进行来源统计
      conn.setRequestProperty("Charset", "UTF-8");    //设置客户端编码
      //设置用户代理
      conn.setRequestProperty("User-Agent", "Mozilla/4.0 (compatible; MSIE 8.0; " +
          "Windows NT 5.2; Trident/4.0; .NET CLR 1.1.4322; .NET CLR 2.0.50727;" +
          " .NET CLR 3.0.04506.30; .NET CLR 3.0.4506.2152; .NET CLR 3.5.30729)");
      
      conn.setRequestProperty("Connection", "Keep-Alive");  //设置connection的方式
      conn.connect();      //和远程资源建立正在的链接,但尚无返回的数据流
      printResponseHeader(conn);   //打印返回的Http的头字段集合
      //对返回的状态码进行判断,用于检查是否请求成功,返回200时执行下面的代码
      if(conn.getResponseCode() == RESPONSEOK)
      {
        this.fileSize = conn.getContentLength();  //根据响应获得文件大小
        if(this.fileSize <= 0)throw new RuntimeException("不知道文件大小");  //文件长度小于等于0时抛出运行时异常
        String filename = getFileName(conn);      //获取文件名称
        this.saveFile = new File(fileSaveDir,filename);  //根据文件保存目录和文件名保存文件
        Map<Integer,Integer> logdata = fileService.getData(downloadUrl);    //获取下载记录
        //如果存在下载记录
        if(logdata.size() > 0)
        {
          //遍历集合中的数据,把每条线程已下载的数据长度放入data中
          for(Map.Entry<Integer, Integer> entry : logdata.entrySet())
          {
            data.put(entry.getKey(), entry.getValue());
          }
        }
        //如果已下载的数据的线程数和现在设置的线程数相同时则计算所有现场已经下载的数据总长度
        if(this.data.size() == this.threads.length)
        {
          //遍历每条线程已下载的数据
          for(int i = 0;i < this.threads.length;i++)
          {
            this.downloadedSize += this.data.get(i+1);
          }
          print("已下载的长度" + this.downloadedSize + "个字节");
        }
        //使用条件运算符求出每个线程需要下载的数据长度
        this.block = (this.fileSize % this.threads.length) == 0?
            this.fileSize / this.threads.length:
              this.fileSize / this.threads.length + 1;
      }else{
        //打印错误信息
        print("服务器响应错误:" + conn.getResponseCode() + conn.getResponseMessage());
        throw new RuntimeException("服务器反馈出错");
      }
      
      
    }catch (Exception e) 
    {
      print(e.toString());   //打印错误
      throw new RuntimeException("无法连接URL");
    }
  }

  
  /**
   * 获取文件名
   * */
  private String getFileName(HttpURLConnection conn)
  {
    //从下载的路径的字符串中获取文件的名称
    String filename = this.downloadUrl.substring(this.downloadUrl.lastIndexOf('/') + 1);
    if(filename == null || "".equals(filename.trim())){     //如果获取不到文件名称
    for(int i = 0;;i++)  //使用无限循环遍历
    {
      String mine = conn.getHeaderField(i);     //从返回的流中获取特定索引的头字段的值
      if (mine == null) break;          //如果遍历到了返回头末尾则退出循环
      //获取content-disposition返回字段,里面可能包含文件名
      if("content-disposition".equals(conn.getHeaderFieldKey(i).toLowerCase())){
        //使用正则表达式查询文件名
        Matcher m = Pattern.compile(".*filename=(.*)").matcher(mine.toLowerCase());
        if(m.find()) return m.group(1);    //如果有符合正则表达式规则的字符串,返回
      }
    }
    filename = UUID.randomUUID()+ ".tmp";//如果都没找到的话,默认取一个文件名
    //由网卡标识数字(每个网卡都有唯一的标识号)以及CPU时间的唯一数字生成的一个16字节的二进制作为文件名
    }
      return filename;
  }
  
  /**
   *  开始下载文件
   * @param listener 监听下载数量的变化,如果不需要了解实时下载的数量,可以设置为null
   * @return 已下载文件大小
   * @throws Exception
   */
  //进行下载,如果有异常的话,抛出异常给调用者
  public int download(DownloadProgressListener listener) throws Exception{
    try {
      RandomAccessFile randOut = new RandomAccessFile(this.saveFile, "rwd");
      //设置文件大小
      if(this.fileSize>0) randOut.setLength(this.fileSize);
      randOut.close();    //关闭该文件,使设置生效
      URL url = new URL(this.downloadUrl);
      if(this.data.size() != this.threads.length){
      //如果原先未曾下载或者原先的下载线程数与现在的线程数不一致
        this.data.clear();
        //遍历线程池
        for (int i = 0; i < this.threads.length; i++) {
          this.data.put(i+1, 0);//初始化每条线程已经下载的数据长度为0
        }
        this.downloadedSize = 0;   //设置已经下载的长度为0
      }
      
      for (int i = 0; i < this.threads.length; i++) {//开启线程进行下载
        int downLength = this.data.get(i+1);   
        //通过特定的线程id获取该线程已经下载的数据长度
        //判断线程是否已经完成下载,否则继续下载 
        if(downLength < this.block && this.downloadedSize<this.fileSize){
          //初始化特定id的线程
          this.threads[i] = new DownloadThread(this, url, this.saveFile, this.block, this.data.get(i+1), i+1);
          //设置线程优先级,Thread.NORM_PRIORITY = 5;
          //Thread.MIN_PRIORITY = 1;Thread.MAX_PRIORITY = 10,数值越大优先级越高
          this.threads[i].setPriority(7);   
          this.threads[i].start();    //启动线程
        }else{
          this.threads[i] = null;   //表明线程已完成下载任务
        }
      }
      
      fileService.delete(this.downloadUrl);           
      //如果存在下载记录,删除它们,然后重新添加
      fileService.save(this.downloadUrl, this.data);  
      //把下载的实时数据写入数据库中
      boolean notFinish = true;             
      //下载未完成
      while (notFinish) {               
      // 循环判断所有线程是否完成下载
        Thread.sleep(900);
        notFinish = false;                
        //假定全部线程下载完成
        for (int i = 0; i < this.threads.length; i++){
          if (this.threads[i] != null && !this.threads[i].isFinish()) {
          //如果发现线程未完成下载
            notFinish = true;                   
            //设置标志为下载没有完成
            if(this.threads[i].getDownLength() == -1){        
            //如果下载失败,再重新在已下载的数据长度的基础上下载
            //重新开辟下载线程,设置线程的优先级
              this.threads[i] = new DownloadThread(this, url, this.saveFile, this.block, this.data.get(i+1), i+1);
              this.threads[i].setPriority(7);
              this.threads[i].start();
            }
          }
        }       
        if(listener!=null) listener.onDownloadSize(this.downloadedSize);
        //通知目前已经下载完成的数据长度
      }
      if(downloadedSize == this.fileSize) fileService.delete(this.downloadUrl);
      //下载完成删除记录
    } catch (Exception e) {
      print(e.toString());
      throw new Exception("文件下载异常");
    }
    return this.downloadedSize;
  }
  
  
  /**
   * 获取Http响应头字段
   * @param http
   * @return
   */
  public static Map<String, String> getHttpResponseHeader(HttpURLConnection http) {
    //使用LinkedHashMap保证写入和便利的时候的顺序相同,而且允许空值
    Map<String, String> header = new LinkedHashMap<String, String>();
    //此处使用无线循环,因为不知道头字段的数量
    for (int i = 0;; i++) {
      String mine = http.getHeaderField(i);  //获取第i个头字段的值
      if (mine == null) break;      //没值说明头字段已经循环完毕了,使用break跳出循环
      header.put(http.getHeaderFieldKey(i), mine); //获得第i个头字段的键
    }
    return header;
  }
  /**
   * 打印Http头字段
   * @param http
   */
  public static void printResponseHeader(HttpURLConnection http){
    //获取http响应的头字段
    Map<String, String> header = getHttpResponseHeader(http);
    //使用增强for循环遍历取得头字段的值,此时遍历的循环顺序与输入树勋相同
    for(Map.Entry<String, String> entry : header.entrySet()){
      //当有键的时候则获取值,如果没有则为空字符串
      String key = entry.getKey()!=null ? entry.getKey()+ ":" : "";
      print(key+ entry.getValue());      //打印键和值得组合
    }
  }
  
  /**
   * 打印信息
   * @param msg 信息字符串
   * */
  private static void print(String msg) {
    Log.i(TAG, msg);
  }
}

Step 4:自定义一个下载线程类

这个自定义的线程类要做的事情如下:
① 首先肯定是要继承Thread类啦,然后重写Run()方法
② Run()方法:先判断是否下载完成,没有得话:打开URLConnection链接,接着RandomAccessFile 进行数据读写,完成时设置完成标记为true,发生异常的话设置长度为-1,打印异常信息
③打印log信息的方法
④判断下载是否完成的方法(根据完成标记)
⑤获得已下载的内容大小
DownLoadThread.java:

package com.jay.example.service;

import java.io.File;
import java.io.InputStream;
import java.io.RandomAccessFile;
import java.net.HttpURLConnection;
import java.net.URL;

import android.util.Log;

public class DownloadThread extends Thread {
  private static final String TAG = "下载线程类";    //定义TAG,在打印log时进行标记
  private File saveFile;              //下载的数据保存到的文件
  private URL downUrl;              //下载的URL
  private int block;                //每条线程下载的大小
  private int threadId = -1;            //初始化线程id设置
  private int downLength;             //该线程已下载的数据长度
  private boolean finish = false;         //该线程是否完成下载的标志
  private FileDownloadered downloader;      //文件下载器

  public DownloadThread(FileDownloadered downloader, URL downUrl, File saveFile, int block, int downLength, int threadId) {
    this.downUrl = downUrl;
    this.saveFile = saveFile;
    this.block = block;
    this.downloader = downloader;
    this.threadId = threadId;
    this.downLength = downLength;
  }
  
  @Override
  public void run() {
    if(downLength < block){//未下载完成
      try {
        HttpURLConnection http = (HttpURLConnection) downUrl.openConnection();
        http.setConnectTimeout(5 * 1000);
        http.setRequestMethod("GET");
        http.setRequestProperty("Accept", "image/gif, image/jpeg, image/pjpeg, image/pjpeg, application/x-shockwave-flash, application/xaml+xml, application/vnd.ms-xpsdocument, application/x-ms-xbap, application/x-ms-application, application/vnd.ms-excel, application/vnd.ms-powerpoint, application/msword, */*");
        http.setRequestProperty("Accept-Language", "zh-CN");
        http.setRequestProperty("Referer", downUrl.toString()); 
        http.setRequestProperty("Charset", "UTF-8");
        int startPos = block * (threadId - 1) + downLength;//开始位置
        int endPos = block * threadId -1;//结束位置
        http.setRequestProperty("Range", "bytes=" + startPos + "-"+ endPos);//设置获取实体数据的范围
        http.setRequestProperty("User-Agent", "Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 5.2; Trident/4.0; .NET CLR 1.1.4322; .NET CLR 2.0.50727; .NET CLR 3.0.04506.30; .NET CLR 3.0.4506.2152; .NET CLR 3.5.30729)");
        http.setRequestProperty("Connection", "Keep-Alive");
        
        InputStream inStream = http.getInputStream();     //获得远程连接的输入流
        byte[] buffer = new byte[1024];           //设置本地数据的缓存大小为1MB
        int offset = 0;                   //每次读取的数据量
        print("Thread " + this.threadId + " start download from position "+ startPos);  //打印该线程开始下载的位置
        
        RandomAccessFile threadfile = new RandomAccessFile(this.saveFile, "rwd");
        threadfile.seek(startPos);
        //用户没有要求停止下载,同时没有达到请求数据的末尾时会一直循环读取数据
        while (!downloader.getExited() && (offset = inStream.read(buffer, 0, 1024)) != -1) {
          threadfile.write(buffer, 0, offset);          //直接把数据写入到文件中
          downLength += offset;             //把新线程已经写到文件中的数据加入到下载长度中
          downloader.update(this.threadId, downLength); //把该线程已经下载的数据长度更新到数据库和内存哈希表中
          downloader.append(offset);            //把新下载的数据长度加入到已经下载的数据总长度中
        }
        threadfile.close();
        inStream.close();
        print("Thread " + this.threadId + " download finish");
        this.finish = true;                               //设置完成标记为true,无论下载完成还是用户主动中断下载
      } catch (Exception e) {
        this.downLength = -1;               //设置该线程已经下载的长度为-1
        print("Thread "+ this.threadId+ ":"+ e);
      }
    }
  }
  private static void print(String msg){
    Log.i(TAG, msg);
  }
  /**
   * 下载是否完成
   * @return
   */
  public boolean isFinish() {
    return finish;
  }
  /**
   * 已经下载的内容大小
   * @return 如果返回值为-1,代表下载失败
   */
  public long getDownLength() {
    return downLength;
  }
}

Step 5:创建一个DownloadProgressListener接口监听下载进度

FileDownloader中使用了DownloadProgressListener进行进度监听, 所以这里需要创建一个接口,同时定义一个方法的空实现:
DownloadProgressListener.java:

package com.jay.example.service;
public interface DownloadProgressListener {
  public void onDownloadSize(int downloadedSize);
}

Step 6:编写我们的布局代码

另外调用android:enabled="false"设置组件是否可点击, 代码如下
activity_main.xml:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/LinearLayout1"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context="com.jay.example.multhreadcontinuabledemo.MainActivity" >

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="请输入要下载的文件地址" />
  <EditText 
      android:id="@+id/editpath"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:text="http://10.13.20.32:8080/Test/twelve.mp3"    
  />
  <Button 
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:id="@+id/btndown"
    android:text="下载"    
  />
  <Button 
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:id="@+id/btnstop"
    android:text="停止"
    android:enabled="false"    
  />
  
  <ProgressBar
     android:layout_width="fill_parent" 
     android:layout_height="18dp" 
     style="?android:attr/progressBarStyleHorizontal"
     android:id="@+id/progressBar"
    />
    
   <TextView  
    android:layout_width="fill_parent" 
    android:layout_height="wrap_content" 
    android:gravity="center"
    android:id="@+id/textresult"
    android:text="显示实时下载的百分比"
    />
  
</LinearLayout>

Step 7:MainActivity的编写

最后就是我们的MainActivity了,完成组件以及相关变量的初始化; 使用handler来完成界面的更新操作,另外耗时操作不能够在主线程中进行, 所以这里需要开辟新的线程,这里用Runnable实现,详情见代码 吧
MainActivity.java:

package com.jay.example.multhreadcontinuabledemo;


import java.io.File;

import com.jay.example.service.FileDownloadered;


import android.app.Activity;
import android.os.Bundle;
import android.os.Environment;
import android.os.Handler;
import android.os.Message;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ProgressBar;
import android.widget.TextView;
import android.widget.Toast;


public class MainActivity extends Activity {

  private EditText editpath;
  private Button btndown;
  private Button btnstop;
  private TextView textresult;
  private ProgressBar progressbar;
  private static final int PROCESSING = 1;   //正在下载实时数据传输Message标志
  private static final int FAILURE = -1;     //下载失败时的Message标志
  
  
  private Handler handler = new UIHander();
    
    private final class UIHander extends Handler{
    public void handleMessage(Message msg) {
      switch (msg.what) {
      //下载时
      case PROCESSING:
        int size = msg.getData().getInt("size");     //从消息中获取已经下载的数据长度
        progressbar.setProgress(size);         //设置进度条的进度
        //计算已经下载的百分比,此处需要转换为浮点数计算
        float num = (float)progressbar.getProgress() / (float)progressbar.getMax();
        int result = (int)(num * 100);     //把获取的浮点数计算结果转换为整数
        textresult.setText(result+ "%");   //把下载的百分比显示到界面控件上
        if(progressbar.getProgress() == progressbar.getMax()){ //下载完成时提示
          Toast.makeText(getApplicationContext(), "文件下载成功", 1).show();
        }
        break;

      case FAILURE:    //下载失败时提示
        Toast.makeText(getApplicationContext(), "文件下载失败", 1).show();
        break;
      }
    }
    }
  
  
  
  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    
    editpath = (EditText) findViewById(R.id.editpath);
    btndown = (Button) findViewById(R.id.btndown);
    btnstop = (Button) findViewById(R.id.btnstop);
    textresult = (TextView) findViewById(R.id.textresult);
    progressbar = (ProgressBar) findViewById(R.id.progressBar);
    ButtonClickListener listener = new ButtonClickListener();
    btndown.setOnClickListener(listener);
    btnstop.setOnClickListener(listener);
    
    
  }
  
  
  private final class ButtonClickListener implements View.OnClickListener{
    public void onClick(View v) {
      switch (v.getId()) {
      case R.id.btndown:
        String path = editpath.getText().toString();
        if(Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)){
          File saveDir = Environment.getExternalStorageDirectory();
          download(path, saveDir);
        }else{
          Toast.makeText(getApplicationContext(), "sd卡读取失败", 1).show();
        }
        btndown.setEnabled(false);
        btnstop.setEnabled(true);
        break;

      case R.id.btnstop:
        exit();
        btndown.setEnabled(true);
        btnstop.setEnabled(false);
        break;
      }
    }
    /*
    由于用户的输入事件(点击button, 触摸屏幕....)是由主线程负责处理的,如果主线程处于工作状态,
    此时用户产生的输入事件如果没能在5秒内得到处理,系统就会报“应用无响应”错误。
    所以在主线程里不能执行一件比较耗时的工作,否则会因主线程阻塞而无法处理用户的输入事件,
    导致“应用无响应”错误的出现。耗时的工作应该在子线程里执行。
     */
    private DownloadTask task;
    /**
     * 退出下载
     */
    public void exit(){
      if(task!=null) task.exit();
    }
    private void download(String path, File saveDir) {//运行在主线程
      task = new DownloadTask(path, saveDir);
      new Thread(task).start();
    }
    
    
    /*
     * UI控件画面的重绘(更新)是由主线程负责处理的,如果在子线程中更新UI控件的值,更新后的值不会重绘到屏幕上
     * 一定要在主线程里更新UI控件的值,这样才能在屏幕上显示出来,不能在子线程中更新UI控件的值
     */
    private final class DownloadTask implements Runnable{
      private String path;
      private File saveDir;
      private FileDownloadered loader;
      public DownloadTask(String path, File saveDir) {
        this.path = path;
        this.saveDir = saveDir;
      }
      /**
       * 退出下载
       */
      public void exit(){
        if(loader!=null) loader.exit();
      }
      
      public void run() {
        try {
          loader = new FileDownloadered(getApplicationContext(), path, saveDir, 3);
          progressbar.setMax(loader.getFileSize());//设置进度条的最大刻度
          loader.download(new com.jay.example.service.DownloadProgressListener() {
            public void onDownloadSize(int size) {
              Message msg = new Message();
              msg.what = 1;
              msg.getData().putInt("size", size);
              handler.sendMessage(msg);
            }
          });
        } catch (Exception e) {
          e.printStackTrace();
          handler.sendMessage(handler.obtainMessage(-1));
        }
      }     
    }
    }
}

Step 8:AndroidManifest.xml文件中添加相关权限

<!-- 访问internet权限 -->
<uses-permission android:name="android.permission.INTERNET"/>
<!--SDCard中创建与删除文件权限 -->
<uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS"/>
<!--SDCard写入数据权限 -->
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1988732.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

ChatGPT辅助论文写作各阶段提示词分享

学境思源&#xff0c;一键生成论文初稿&#xff1a; AcademicIdeas - 学境思源AI论文写作 随着人工智能技术的迅速发展&#xff0c;ChatGPT作为一种强大的语言模型&#xff0c;已经在各个领域展现出其独特的应用价值。在学术论文写作过程中&#xff0c;ChatGPT不仅可以帮助研究…

细节持续跟新

1.input的自带光标如何去除 2.阻止事件冒泡 3.获取父亲兄弟的innertext 4.画表格 参考博主链接 前端-HTML表格制作_哔哩哔哩_bilibili 美化

[CSCCTF 2019 Qual]FlaskLight (jinja2模版注入)

两种方法&#xff1a; 1.工具法 进来看见flask到处飘&#xff0c;估计就是ssti ctrlU打开发现两行注释提示GET方式传递参数search 这种有参数的我先直接丢fengjing扫了一下&#xff0c;结果还真搞出来&#xff0c;这工具还是挺牛的&#xff0c;就是没参数的时候搞不了 fengj…

在 Ubuntu 24.04 LTS 上安装 MySQL 8

1. 更新系统软件包 在开始安装 MySQL 之前&#xff0c;确保你的系统软件包是最新的。 sudo apt update sudo apt upgrade -y 2. 添加 MySQL APT 存储库 首先&#xff0c;下载 MySQL APT 存储库的包&#xff1a; wget https://dev.mysql.com/get/mysql-apt-config_0.8.17-1…

软件工程_软件过程模型__20240806

1:软件过程模型 1.11:敏捷方法 敏捷方法-XP 四大价值观 沟通[加强面对面沟通] 简单[不过度设计] 反馈[及时反馈] 勇气[接受变更的勇气]12条过程实践规则 简单设计 测试驱动 代码重构 结对编程 持续集成 现场客户 发行版本小型化 系统隐喻 代码集体所有制 规划策略 规范代码…

【数据结构】五、树:8.并查集

4.并查集Disjoint Set 文章目录 4.并查集Disjoint Set4.1查4.2并❗4.3代码实现4.4对union优化4.5对Find的优化&#xff08;压缩路径&#xff09;❗4.6并查集C代码&#xff08;优化后&#xff09;按秩合并 集合。在集合中将各个元素划分为若干个 互不相交的子集。 如何表示&quo…

uniapp video播放视频 悬浮在屏幕无法滑动

背景&#xff1a; 在uniapp中&#xff0c;需要使用<video></video>标签进行播放动态src的视频。 1.在开发的时候&#xff0c;运行到浏览器&#xff0c;vedio标签正常&#xff1b;(使用HbuildX运行&#xff0c;运行 -->运行到浏览器)。 2.但是在打包成原生App&am…

【数据结构】栈篇

文章目录 1. 栈2. 栈的实现2.1 准备工作2.2 栈的初始化2.3 入栈2.4 出栈2.5 判断栈是否为空2.6 取出栈顶元素2.7 获取栈中有效元素个数2.8 销毁栈效果图 3.代码整合 1. 栈 栈是一种特殊的线性表&#xff0c;其只允许固定一端进行插入和删除元素操作。进行数据的插入和删除操作的…

qt项目之在线考试系统----------MVC使用模型-视图-控制器

1、什么是MVC的设计模式 在Qt中,MVC是一种设计模式,全称为Model-View-Controller(模型-视图-控制器)。这是一个经典的设计模式,用于将数据表示(Model)、用户界面(View)和业务逻辑(Controller)分离。具体来说,MVC设计模式在Qt中的应用如下: Model(模型):表示应用…

C++之从C过渡(下)

C之从C过渡&#xff08;下&#xff09; 接着上一篇&#xff0c;从引用开始往下讲解。 引用的特性 引⽤在定义时必须初始化⼀个变量可以有多个引⽤引⽤⼀旦引⽤⼀个实体&#xff0c;再不能引⽤其他实体 C的引用不能完全替代指针。比如&#xff0c;在链表结点中我们会存储指向下…

2024下半年EI收录的老牌会议,检索超快!

在科研领域&#xff0c;EI作为全球公认的工程技术领域重要检索工具&#xff0c;其收录的会议论文往往代表着某一领域内的最新研究成果与前沿技术。对于广大科研工作者而言&#xff0c;能够在EI收录的老牌会议上发表论文&#xff0c;不仅是对自身研究能力的一种肯定&#xff0c;…

pinctrl子系统做功能的切换.

SD卡和debug口中sdmmc和uart共用同一组pin脚,需实现在sd使用的时候切换到sdmmc不插入sd卡的时候使用debug口功能。 sd卡有检测脚可以作为切换的标志所以我们的切换要在sd卡的驱动中去做。 第一步&#xff1a; 使能俩个功能的dts并去除不能切换的pinctrl&#xff0c;只有一个节点…

自动回复的AI小助手,人工智能还是人工智障

最近在运营公司的百家号账号。因为老杨和同事们在一些大会上有干货满满的演讲&#xff0c;我们将它剪辑成比较短的视频&#xff0c;放在一些平台上供大家观看。百家号因百度的关系&#xff0c;搜索的引流会好一些。 一开始每次发好视频&#xff0c;就会有播放量。几次之后&…

Java每日一题———删除有序数组中的重复项

这个问题可以通过使用双指针技术来解决。我们可以使用两个指针&#xff0c;一个慢指针 slowRunner 用于跟踪新数组的末尾&#xff0c;另一个快指针 fastRunner 用于遍历数组。每当 fastRunner 遇到一个新的唯一元素时&#xff0c;就将其复制到 slowRunner 指向的位置&#xff0…

创建谷歌外链的常见错误及避免方法!

创建谷歌外链是个技术活&#xff0c;很多人在这个过程中容易犯错。了解这些常见错误和如何避免它们可以帮助你更有效地提升你的SEO表现。 其一&#xff0c;忽视锚文本多样性。有些人在建立外链时&#xff0c;总是使用相同的锚文本&#xff0c;这看起来很不自然&#xff0c;可能…

基于python爬虫技术的bilibili网用户数据采集系统的设计与实现-计算机毕业设计源码55962

摘要 在当今信息爆炸的时代&#xff0c;互联网已经成为人们获取信息、交流思想的重要平台。作为国内领先的弹幕视频网站&#xff0c;Bilibili凭借其独特的弹幕文化和丰富的内容生态&#xff0c;吸引了亿万用户的关注。这些用户生成的海量数据蕴含着丰富的信息&#xff0c;对于理…

异常(Java)

目录 1. 异常的概念 2. 异常的分类 3. 异常的处理 4. 异常的抛出 5. 异常的捕获 5.1 异常声明throws 5.2 try-catch捕获并处理 5.3 finally 6. 异常的处理流程 7. 自定义异常类 1. 异常的概念 异常就是在程序执行过程中发生的不正常的行为.异常中断了正在执行程序的…

Cross-Modality Person Re-identification with Memory-Based Contrastive Embedding

文章目录 题目&#xff1a;Cross-Modality Person Re-identification with Memory-Based Contrastive Embedding&#xff08;基于记忆对比嵌入的跨模态人物再识别&#xff09;摘要论文分析网络框架1、Problem Definition&#xff08;模态预处理&#xff09;2、Learning Modalit…

RUM技术探索:前端监控数据采集与实践

​​随着互联网技术的不断演进&#xff0c;Web应用程序正日益呈现出复杂多变与高度动态性的特征。用户渴望获得快速的页面加载、流畅的交互体验以及高度的可靠性。为了满足这些&#xff0c;实时监控 Web 应用的性能和行为变得至关重要。前端监控让开发者能够深入了解应用的表现…

Hack The Box-Resource

总体思路 phar反序列化->SSH CA私钥泄露->SSH CA私钥滥用->SSH脚本滥用 信息收集&端口利用 nmap -sSVC itrc.ssg.htb目标开放了两个ssh端口和一个80端口&#xff0c;先查看80端口 网站是一个SSG IT资源中心&#xff0c;主要用于解决网站问题、管理 SSH 访问、清…