断点续传android_断点续传 android - CSDN
  • Android断点续传

    2019-09-15 12:34:22
     最近看了一些大佬去面试的时候都提到了断点续传,所以自己也写一个记录下来,断点续传的原理就是通过数据库实时的去保存当前下载的长度,然后下次再次下载的时候通过setRequestProperty告诉服务端我需要这个文件从...

     最近看了一些大佬去面试的时候都提到了断点续传,所以自己也写一个记录下来,断点续传的原理就是通过数据库实时的去保存当前下载的长度,然后下次再次下载的时候通过setRequestProperty告诉服务端我需要这个文件从什么地方开始下载,我们再通过RandomAccessFile去设置开始写入的位置

    效果

    首先上效果图与日志,GIF只截取了5秒
    在这里插入图片描述

    在这里插入图片描述

    步骤

    1. 首先我们建立一个实体类,用于保存文件的下载进度信息
    public class FileInfo implements Serializable{
        private String url;
        private long length;//文件的总长度,去服务端获取
        private int start;//开始的下载位置
        private int now;//当前的位置
    
     .....get set方法省略........
    }
    
    1. 建立一个数据库,用于保存FileInfo,需要增删查改四个方法
    2. 创建一个服务,在onStartCommand中去根据URL去查找是否有匹配的数据信息,如果没有,俺就是第一次下载,如果有,那就是断点续传
    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        if (intent.getAction().equals(DownConstans.DOWNLOAD_START)) {
            isPause = false;
            //开始下载,启动线程
            String url=intent.getStringExtra("url");
            FileInfo fileInfo = new FileInfo();
            fileInfo.setUrl(url);
    
            downLoadDao = new DownLoadDAO(this);
            //从数据库中根据URL去查找是否有和该URL匹配的信息
            List<FileInfo> infos = downLoadDao.get(url);
            if (infos.size() == 0) {
                //第一次下载
                Log.e(TAG, "onHandleIntent: 第一次下载" );
                downLoadDao.insert(fileInfo);
                down(fileInfo);
                new DownLoadThread(fileInfo).start();
            } else {
                //断点续传
                Log.e(TAG, "onHandleIntent: 断点续传" );
                new DownLoadThread(infos.get(0)).start();
            }
    
        } else if (intent.getAction().equals(DownConstans.DOWNLOAD_STOP)) {
            //下载标示设置为暂停
            isPause=true;
        }
    
        return super.onStartCommand(intent, flags, startId);
    }
    
    1. 在线程中执行下载操作,核心的几个操作:请求、写入

      1. 请求,通过connection.setRequestProperty(“Range”,“bytes=” + info.getNow() + “-”);对我们要读去的字节进行控制,这里有一步需要注意,获取要下载文件的长度要放在setRequestProperty之后进行

      1.Range=0-100代表只读取前100个字节。
      2.Range=100-500代表读取从第100个字节开始,读到第500个字节为止。
      3.Range=100-则代表从第100个字节开始读取,一直读取到文件末尾结束。

      1. 写入,由于普通的File无法去设置定点写入,所以这里需要用到 RandomAccessFile的seek()方法,去设置我当前要写入的位置,如果当前选择了暂停,则我们将下载的进度保存在数据库中,然后退出下载方法,如果全部下载完了,那么就在数据库中删除掉该条url的数据

      这里在下载之前,我们还需要判断之前下载的文件是否已经被删除了,如果删除了,那我们就不能是断点续传了,需要从头下起了

    class  DownLoadThread extends Thread{
    
        private FileInfo info;
    
        public DownLoadThread(FileInfo info) {
            this.info = info;
        }
    
        @Override
        public void run() {
            HttpURLConnection connection = null;
            RandomAccessFile randomFile = null;
            InputStream inputStream = null;
            int now=0;
            try {
                URL url = new URL(info.getUrl());
                connection = (HttpURLConnection) url.openConnection();
                connection.setConnectTimeout(5000);
                connection.setRequestMethod("GET");
    
                connection.setRequestProperty("Range","bytes=" + info.getNow() + "-");//设置当前位置,第一次下载时当前位置为0,断点续传时当前位置为上次下载暂停的位置
    
                int contentLength = connection.getContentLength();
                info.setLength(contentLength);
                if (contentLength <= 0) {
                    return;
                }
                File file = new File(DownConstans.DOWNLOAD_PATH,"12345.jpeg");
                randomFile = new RandomAccessFile(file, "rwd");
                randomFile.seek(info.getNow());
    
                //向Activity发送广播
                Intent intent = new Intent(DownConstans.DOWNLOAD_UPDATE);
                Log.e(TAG, "now: "+info.getNow());
                now = +info.getNow();
    
                if (connection.getResponseCode() == 206) {
                    //获得文件流
                    inputStream = connection.getInputStream();
                    byte[] buffer = new byte[512];
                    int len = -1;
                    while ((len = inputStream.read(buffer))!= -1){
                        //写入文件
                        randomFile.write(buffer,0,len);
    
                        //把进度发送给Activity
                        now += len;
                        
                        int progress = (int) (now * 100 / info.getLength());
                        intent.putExtra("now",progress);
                        sendBroadcast(intent);
    
                        //判断是否是暂停状态
                        if(isPause){
                            Log.e(TAG, "down: 当前暂停了" );
                            downLoadDao.update(info.getUrl(),now);
                            return;
                        }
                    }
                    Log.e(TAG, "down: 下载结束" );
                    downLoadDao.delete(info.getUrl());
                }
    
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                connection.disconnect();
                try {
                    randomFile.close();
                    inputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
    

     上面是单线程的下载,多线程的下载也是一样的,指定好每个线程要下载的字节部分,即可

    展开全文
  • Android 断点续传下载

    2019-01-11 17:41:25
    什么是断点续传: 可以知道当前下载进度,并且可以下载一部分的时候进行停止下载,下载一部分的时候进行继续下载   这里是郭霖的《Android第一行代码》中的示例代码 我看的时候是基于Android7.0的,我又加了新特性...

    什么是断点续传:

    可以知道当前下载进度,并且可以下载一部分的时候进行停止下载,下载一部分的时候进行继续下载

     

    这里是郭霖的《Android第一行代码》中的示例代码

    我看的时候是基于Android7.0的,我又加了新特性NotificationChannel通知渠道,还有下载完成扫描文件

     

    分4部分:MainActivity,DownloadTask,DownloadService,DownloadListener

    用到了回调接口,绑定服务,前台服务,子线程异步消息处理机制,IO操作,网络请求,通知

     

    先从最简单的开始

    申请权限和导入依赖

    这里就一个网络权限,和一个写入权限(写入权限需要动态获取)

        <uses-permission android:name="android.permission.INTERNET"/>
        <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>

    依赖就一个Okhttp

        implementation 'com.squareup.okhttp3:okhttp:3.4.1'

     

    DownloadListener接口

    这是下载用的一个回调接口,定义了5个方法,在Service中实例化并实现这5个回调方法,作用是更新进度,和下载结果的显示

    public interface DownloadListener {
        //下载进度显示
        void onProgress(int progress);
        //下载状态显示,成功,失败,暂停,取消
        void onSuccess();
        void onFailed();
        void onPaused();
        void onCanceled();
    }

    MainActivity

    这个是活动,启动服务的方式为绑定服务,所以初始化了ServiceConnection类和服务中自定义的DownloadBinder类,在连接成功时获取DownloadBinder对象

    设置了三个按钮分别是开始下载,暂停下载,取消下载,具体实现是DownloadBinder中对应的这三个方法

    在Activity创建的时候onCreate()中绑定服务,并且动态申请写入权限,在Activity销毁的Destroy()中解绑服务

    public class MainActivity extends AppCompatActivity {
    
        private static final String TAG = "MainActivity";
    
        //初始化对象
        private DownloadService.DownloadBinder downloadBinder;
        private ServiceConnection connection =new ServiceConnection() {
            @Override
            public void onServiceConnected(ComponentName name, IBinder service) {
                downloadBinder= (DownloadService.DownloadBinder) service;
            }
    
            @Override
            public void onServiceDisconnected(ComponentName name) {
    
            }
        };
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            Button startDownload= (Button) findViewById(R.id.start_download);
            Button pauseDownload= (Button) findViewById(R.id.pause_download);
            Button cancelDownload= (Button) findViewById(R.id.cancel_download);
            //下载按钮
            startDownload.setOnClickListener(new View.OnClickListener() {
                @RequiresApi(api = 26)
                @Override
                public void onClick(View v) {
                    String url="http://f.hiphotos.baidu.com/image/pic/item/b7fd5266d016092446517fdadd0735fae7cd34ff.jpg";
                    downloadBinder.startDownload(url);
                }
            });
            //暂停按钮
            pauseDownload.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    downloadBinder.pauseDownload();
                }
            });
            //取消按钮
            cancelDownload.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    downloadBinder.cancelDownload();
                }
            });
    
            //绑定启动服务
            Intent intent=new Intent(this,DownloadService.class);
            bindService(intent,connection,BIND_AUTO_CREATE);
    
            //申请写入权限
            if (ContextCompat.checkSelfPermission(MainActivity.this, Manifest.permission.WRITE_EXTERNAL_STORAGE)!= PackageManager.PERMISSION_GRANTED){
                ActivityCompat.requestPermissions(MainActivity.this,new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE},1);
            }
    
    
        }
    
        @Override
        public void onRequestPermissionsResult(int requestCode,String [] permissions,int[] grantResults){
            switch (requestCode){
                case 1:
                    if (grantResults.length>0&&grantResults[0]!=PackageManager.PERMISSION_GRANTED){
                        Toast.makeText(this,"拒绝权限将无法正常使用",Toast.LENGTH_SHORT).show();
                        finish();
                    }
                    break;
                default:
            }
        }
    
        @Override
        protected void onDestroy(){
            super.onDestroy();
            //断开服务的绑定
            unbindService(connection);
        }
    }

    DownloadTask类

    这个是开启子线程实现具体网络请求和下载操作的类

    这里直接继承了AsyncTask<>异步消息处理类(本质也是Android异步消息处理机制),对于异步消息处理机制不懂的请看

    https://blog.csdn.net/yh18668197127/article/details/86224318

    构造方法获取Context对象和DownloadListener对象

    在doInBackground进行具体下载操作

    先判断本地文件大小和远程文件的大小是否一致判断是否下载成功

    下载的时候网络请求当前已下载的byte到文件末尾,写入文件到本地的时候也是从当前已下载的末尾开始写入

    在写入文件的时候调用publishProgress(progress);传递下载进度

    在onProgressUpdate中接收下载进度参数与当前下载进度对比,进行下载进度的更新

    在onPostExecute中接收返回值,根据返回值来更新下载状态

    public class DownloadTask extends AsyncTask<String,Integer,Integer>{
        private static final String TAG = "DownloadTask";
        public static final int TYPE_SUCCESS=0;
        public static final int TYPE_FAILED=1;
        public static final int TYPE_PAUSED=2;
        public static final int TYPE_CANCELED=3;
    
        //初始化对象
        private DownloadListener listener;
        private Context context;
    
        private boolean isCanceled=false;
        private boolean isPaused=false;
        private int lastProgress=0;
        public DownloadTask(DownloadListener listener,Context context){
            this.listener=listener;
            this.context=context;
        }
    
        @Override
        protected Integer doInBackground(String ... params) {
            InputStream inputStream = null;
            RandomAccessFile savedFile = null;
            File file = null;
            try {
                long downloadedLength = 0;//已下载长度
                String downloadUrl = params[0];
                String fileName = downloadUrl.substring(downloadUrl.lastIndexOf("/"));
    
                String directory= Environment.getExternalStorageDirectory().getPath();
    
                Log.i(TAG, "doInBackground: "+directory+" "+fileName);
    
                file = new File(directory + fileName);
                if (file.exists()) {
                    downloadedLength = file.length();
                }
                long contentLength = getContentLength(downloadUrl);
                if (contentLength == 0) {
                    return TYPE_FAILED;
                } else if (contentLength == downloadedLength) {
                    return TYPE_SUCCESS;
                }
                OkHttpClient client = new OkHttpClient();
                Request request = new Request.Builder()
                        .addHeader("RANGE", "bytes=" + downloadedLength + "-")
                        .url(downloadUrl)
                        .build();
                Response response = client.newCall(request).execute();
                if (response != null) {
                    inputStream = response.body().byteStream();
                    savedFile = new RandomAccessFile(file, "rw");
                    savedFile.seek(downloadedLength);
                    byte[] b = new byte[1024];
                    int total = 0;
                    int len;
                    while ((len = inputStream.read(b)) != -1) {
                        if (isCanceled) {
                            return TYPE_CANCELED;
                        } else if (isPaused) {
                            return TYPE_PAUSED;
                        } else {
                            total += len;
                            savedFile.write(b, 0, len);
                            int progress = (int) ((total + downloadedLength) * 100 / contentLength);
                            publishProgress(progress);
                        }
                    }
                    //下载完成调用系统文件扫描机制,否则电脑连接显示不了文件
                    MediaScannerConnection.scanFile(context, new String[] { file.getAbsolutePath() }, null, null);
                    return TYPE_SUCCESS;
                }
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                try {
                    if (inputStream != null) {
                        inputStream.close();
                    }
                    if (savedFile != null) {
                        savedFile.close();
                    }
                    if (isCanceled && file != null) {
                        file.delete();
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
            return TYPE_FAILED;
        }
    
    
        @Override
        protected void onProgressUpdate(Integer...values){
            int progress=values[0];
            if (progress>lastProgress){
                listener.onProgress(progress);
                lastProgress=progress;
            }
        }
    
        @Override
        protected void onPostExecute(Integer status){
            switch (status){
                case TYPE_SUCCESS:
                    listener.onSuccess();
                    break;
                case TYPE_PAUSED:
                    listener.onPaused();
                    break;
                case TYPE_FAILED:
                    listener.onFailed();
                    break;
                case TYPE_CANCELED:
                    listener.onCanceled();
                    break;
                default:
                    break;
            }
        }
    
        public void pauseDownload(){
            isPaused=true;
        }
    
        public void cancelDownload(){
            isCanceled=true;
        }
    
    
        private long getContentLength(String downloadUrl) throws IOException {
            OkHttpClient client=new OkHttpClient();
            Request request=new Request.Builder()
                    .url(downloadUrl)
                    .build();
            Response response=client.newCall(request).execute();
            if (response!=null&&response.isSuccessful()){
                long contentLength=response.body().contentLength();
                response.body().close();
                return  contentLength;
            }
            return 0;
        }
    }

    DownloadService服务

    自定义了DownloadBinder继承Binder

    创建了DownloadListener对象并实现了它的五个回调方法

    通过通知的方式来显示下载进度,和下载结果

    //这是一个服务
    public class DownloadService extends Service {
    
        final String CHANNEL_ID = "channel_id_1";
        final String CHANNEL_NAME = "channel_name_1";
    
        private DownloadTask downloadTask;
        private String downloadUrl;
    
        private DownloadListener listener = new DownloadListener() {
            @RequiresApi(api = 26)
            @Override
            public void onProgress(int progress) {
                getNotificationManager().notify(1, getNotification("Downloading...", progress));
            }
    
            @RequiresApi(api = 26)
            @Override
            public void onSuccess() {
                downloadTask = null;
                stopForeground(true);
                getNotificationManager().notify(1, getNotification("Download Success", -1));
                Toast.makeText(DownloadService.this, "Download Success", Toast.LENGTH_SHORT).show();
            }
    
            @RequiresApi(api = 26)
            @Override
            public void onFailed() {
                downloadTask = null;
                stopForeground(true);
                getNotificationManager().notify(1, getNotification("Download Failed", -1));
                Toast.makeText(DownloadService.this, "Download Success", Toast.LENGTH_SHORT).show();
            }
    
            @Override
            public void onPaused() {
                downloadTask = null;
                Toast.makeText(DownloadService.this, "Paused", Toast.LENGTH_SHORT).show();
            }
    
            @Override
            public void onCanceled() {
                downloadTask = null;
                stopForeground(true);
                Toast.makeText(DownloadService.this, "Canceled", Toast.LENGTH_SHORT).show();
            }
        };
    
        private DownloadBinder mBinder = new DownloadBinder();
    
        class DownloadBinder extends Binder {
            @RequiresApi(api = 26)
            public void startDownload(String url) {
                if (downloadTask == null) {
                    downloadUrl = url;
                    downloadTask = new DownloadTask(listener,getApplication());
                    downloadTask.execute(downloadUrl);
                    startForeground(1, getNotification("Downloading...", 0));
                    Toast.makeText(DownloadService.this, "Downloading...", Toast.LENGTH_SHORT).show();
                }
            }
    
            public void pauseDownload() {
                if (downloadTask != null) {
                    downloadTask.pauseDownload();
                }
            }
    
            public void cancelDownload() {
                if (downloadTask != null) {
                    downloadTask.cancelDownload();
                }
                if (downloadUrl != null) {
                    String fileName = downloadUrl.substring(downloadUrl.lastIndexOf("/"));
    //                String directory= Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).getPath();
                    String directory = Environment.getExternalStorageDirectory().getPath();
                    File file = new File(directory + fileName);
                    if (file.exists()) {
                        file.delete();
                    }
                    getNotificationManager().cancel(1);
                    stopForeground(true);
                    Toast.makeText(DownloadService.this, "Canceled", Toast.LENGTH_SHORT).show();
                }
            }
        }
    
        @Override
        public IBinder onBind(Intent intent) {
            return mBinder;
        }
    
    
        private NotificationManager getNotificationManager() {
            return (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
        }
    
        @RequiresApi(api = 26)
        private Notification getNotification(String s, int progress) {
            Intent intent = new Intent(this, MainActivity.class);
            PendingIntent pi = PendingIntent.getActivities(this, 0, new Intent[]{intent}, 0);
    
            if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
                //只在Android O之上需要渠道
                NotificationChannel notificationChannel = new NotificationChannel(CHANNEL_ID,
                        CHANNEL_NAME, NotificationManager.IMPORTANCE_HIGH);
                //如果这里用IMPORTANCE_NOENE就需要在系统的设置里面开启渠道,
                //通知才能正常弹出
                getNotificationManager().createNotificationChannel(notificationChannel);
            }
    
            Notification.Builder builder = new Notification.Builder(this, CHANNEL_ID);
            builder.setSmallIcon(R.mipmap.ic_launcher);
            builder.setLargeIcon(BitmapFactory.decodeResource(getResources(), R.mipmap.ic_launcher));
            builder.setContentIntent(pi);
            builder.setContentTitle(s);
            if (progress > 0) {
                builder.setContentText(progress + "%");
                builder.setProgress(100, progress, false);
            }
            return builder.build();
        }
    }

     

    展开全文
  • Android-断点续传下载

    2018-11-24 21:39:36
    工作找完了,玩也玩完了,该好好学习了,最近我把《Java并发编程的艺术》这本书给读完了,对于并发编程以及线程池的使用还是不娴熟,我就在imooc上找到一个项目“Android-Service系列之断点续传下载“,这是我对这个...

    工作找完了,玩也玩完了,该好好学习了,最近我把《Java并发编程的艺术》这本书给读完了,对于并发编程以及线程池的使用还是不娴熟,我就在imooc上找到一个项目“Android-Service系列之断点续传下载“,这是我对这个项目在编写的时候记录。

    涉及知识点

    • UI界面编写
    • 数据库
    • Service
    • 广播传递数据
    • 多线程以及Handler
    • 网络

    这些应该是Android的基础,我就不累述了,到时候在代码中遇到了再进行解释。
    这个项目主要的流程是:
    流程图
    一切的操作的开始是基于Activity的,但是我们的下载任务肯定是不能在Activity中进行的,因为假如我们的Activity切换成后台进程就有可能会被销毁(进程的优先级:前台,可见,服务,后台,空),所以我们将下载放在Service中是比较好的,但是Service和Activity一样是主线程,是不能进行数据的操作的,所以我们要利用到Thread或者是线程池,如果我们要可见下载进度的话,我们就需要通过广播的消息传递来更新UI上的进度,对于断点我们就需要实时将下载到的文件位置存储下来,所以我们利用数据库(稳定)存储进度。下载完成以后再将下载信息删除。

    基础布局

    这部分就不讲了,特别简单的一个布局

    <?xml version="1.0" encoding="utf-8"?>
    <RelativeLayout 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">
    
        <TextView
            android:id="@+id/tvFileName"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Hello World!" />
    
        <ProgressBar
            android:id="@+id/pbProgress"
            style="?android:attr/progressBarStyleHorizontal"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_alignLeft="@+id/tvFileName"
            android:layout_below="@+id/tvFileName"/>
    
        <Button
            android:id="@+id/btStop"
            style="?android:buttonStyleSmall"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_alignParentRight="true"
            android:layout_below="@id/pbProgress"
            android:text="停止"/>
    
        <Button
            android:id="@+id/btStart"
            style="?android:buttonStyleSmall"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_toLeftOf="@id/btStop"
            android:layout_below="@id/pbProgress"
            android:text="下载"/>
    
    
    </RelativeLayout>
    

    实体类

    在这个项目里面我们需要定义两个实体类来进操作,一个是文件信息的实体类,一个是线程信息的实体类

    文件信息

    主要的文件相关信息

    private int id;//文件id
    private String url;//文件的下载url
    private String fileName;//文件名
    private int length;//文件长度
    private int finished;//文件下载完成度
    
    线程相关信息
    private int id;//文件id
    private String url;//文件下载url
    private int start;//线程从哪里开始下载
    private int end;//线程到哪里结束下载
    private int finished;//完成多少
    

    因为我们需要将文件信息进行储存以及传递,所以我们需要实现序列化接口

    public class FileInfo implements Serializable
    

    Activity

    我们在Activity中需要对控件进行监听,如何通过Intent将信息进行传递。
    当然这是最基础的,在之后我们有多个下载任务或者是下载完成我们就需要使用ListView去完成这个效果。先暂时这样写。

    package com.gin.xjh.download_demo;
    
    import android.content.Intent;
    import android.os.Bundle;
    import android.support.v7.app.AppCompatActivity;
    import android.view.View;
    import android.widget.Button;
    import android.widget.ProgressBar;
    import android.widget.TextView;
    
    import com.gin.xjh.download_demo.entities.FileInfo;
    import com.gin.xjh.download_demo.services.DownloadService;
    
    public class MainActivity extends AppCompatActivity {
    
        private TextView mTvFileName;
        private ProgressBar mPbProgress;
        private Button mBtStop, mBtStart;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            initView();
            initEvent();
        }
    
        private void initEvent() {
            final FileInfo fileInfo = new FileInfo(0, "http://music.163.com/" +
                    "song/media/outer/url?id=557581647.mp3", "一眼一生", 0, 0);
            mBtStart.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View view) {
                    Intent intent = new Intent(MainActivity.this, DownloadService.class);
                    intent.setAction(DownloadService.ACTION_START);
                    intent.putExtra("fileInfo", fileInfo);
                    startService(intent);
                }
            });
            mBtStop.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View view) {
                    Intent intent = new Intent(MainActivity.this, DownloadService.class);
                    intent.setAction(DownloadService.ACTION_STOP);
                    intent.putExtra("fileInfo", fileInfo);
                    startService(intent);
                }
            });
        }
    
        private void initView() {
            mTvFileName = findViewById(R.id.tvFileName);
            mPbProgress = findViewById(R.id.pbProgress);
            mBtStop = findViewById(R.id.btStop);
            mBtStart = findViewById(R.id.btStart);
        }
    }
    
    
    

    Service

    当然我们将消息传递到了Service中,并且我们是通过Start方法启动的Service,所以我们需要在onStartCommand方法中对Intent传递的消息进行判断,我们就需要重写onStartCommand方法,但是我们需要进行网络下载,所以我们需要新建一个Thread去完成这个耗时操作。

    package com.gin.xjh.download_demo.services;
    
    import android.app.Service;
    import android.content.Intent;
    import android.os.IBinder;
    import android.support.annotation.Nullable;
    import android.util.Log;
    
    import com.gin.xjh.download_demo.entities.FileInfo;
    
    import java.net.HttpURLConnection;
    
    public class DownloadService extends Service {
    
        public static final String DOWNLOAD_PATH =
                Environment.getExternalStorageDirectory().getAbsolutePath() +
                        "/downloads/";
        public static final String ACTION_START = "ACTION_START";
        public static final String ACTION_STOP = "ACTION_STOP";
    
        @Override
        public int onStartCommand(Intent intent, int flags, int startId) {
            //从Activity中传来的数据
            FileInfo fileInfo = (FileInfo) intent.getSerializableExtra("fileInfo");
            if (ACTION_START.equals(intent.getAction())) {
                Log.i("test", "Start:" + fileInfo.toString());
            } else if (ACTION_STOP.equals(intent.getAction())) {
                Log.i("test", "Stop:" + fileInfo.toString());
            }
            return super.onStartCommand(intent, flags, startId);
        }
    
        @Nullable
        @Override
        public IBinder onBind(Intent intent) {
            return null;
        }
    
        class InitThread extends Thread {
            private FileInfo mFileInfo = null;
    
            public InitThread(FileInfo mFileInfo) {
                this.mFileInfo = mFileInfo;
            }
    
            @Override
            public void run() {
                //网络下载操作
            }
        }
    }
    
    

    网络下载初始化

    因为我们是最基础的下载,所以我们使用的还是HttpURLConnection进行网络的相关操作,并且因为我们是从服务器中下载文件,所以我们选择的是GET方法来获取数据。
    我们根据url获取到文件的相关数据,对长度进行初始化,以及创建下载文件相关(检查路径是否存在,如果没有则进行创建)
    PS:网络链接,以及流文件使用完后进行关闭,防止内存泄漏。

    @Override
    @Override
    public void run() {
        HttpURLConnection conn = null;
        RandomAccessFile raf = null;
        try {
            //连接网络文件
            URL url = new URL(mFileInfo.getUrl());
            conn = (HttpURLConnection) url.openConnection();
            conn.setConnectTimeout(3000);
            conn.setRequestMethod("GET");
            int length = -1;
            if (conn.getResponseCode() == HttpURLConnection.HTTP_OK) {
                //获得文件长度
                length = conn.getContentLength();
            }
            if (length <= 0) {
                return;
            }
            //在本地创建文件
            File dir = new File(DOWNLOAD_PATH);//验证下载地址
            if (!dir.exists()) {
                dir.mkdir();
            }
            File file = new File(dir, mFileInfo.getFileName());
            raf = new RandomAccessFile(file, "rwd");//r:读权限,w:写权限,d:删除权限
            //设置文件长度
            raf.setLength(length);
            mFileInfo.setLength(length);
            mHandler.obtainMessage(MSG_INIT, mFileInfo).sendToTarget();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            conn.disconnect();
            try {
                raf.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
    

    数据库初始化

    我们要将线程的相关信息存入数据库中,这样我们才能做到断点续传,因为,没有记录的话,下一次的下载就不知道从哪里开始了,所以我们需要使用数据库(关于数据库的基本操作可以看我的另一篇博客:传送门)。在这里我们需要几个操作:

    定义数据库帮助类

    这里我们继承SQLiteOpenHelper,并且将数据库的创建以及更新重写好。

    package com.gin.xjh.download_demo.db;
    
    import android.content.Context;
    import android.database.sqlite.SQLiteDatabase;
    import android.database.sqlite.SQLiteOpenHelper;
    
    public class DBHelper extends SQLiteOpenHelper {
    
        private static final String DB_NAME = "download.db";
        private static final int VERSION = 1;
        private static final String SQL_CREATE = "create table thread_info(_id integer primary key autoincrement," +
                "thread_id integer,url text,start integer,ends integer,finished integer)";
        private static final String SQL_DROP = "drop table if exists thread_info";
    
        public DBHelper(Context context) {
            super(context, DB_NAME, null, VERSION);
        }
    
        @Override
        public void onCreate(SQLiteDatabase db) {
            db.execSQL(SQL_CREATE);
        }
    
        @Override
        public void onUpgrade(SQLiteDatabase db, int i, int i1) {
            db.execSQL(SQL_DROP);
            db.execSQL(SQL_CREATE);
        }
    }
    
    
    定义数据访问接口

    虽然我们直接在帮助类中写增删改查的操作也是可以的,但是使用接口的话就可以定义一个规范,并且在之后我们进行接口的实现也可以使得代码的耦合度降到最低。

    package com.gin.xjh.download_demo.db;
    
    import com.gin.xjh.download_demo.entities.ThreadInfo;
    
    import java.util.List;
    
    /**
     * 数据访问接口
     */
    
    public interface ThreadDAO {
    
        /**
         * 插入线程信息
         */
        void insertThread(ThreadInfo threadInfo);
    
        /**
         * 删除线程信息
         */
        void deleteThread(String url, int thread_id);
    
        /**
         * 更新线程信息
         */
        void updateThread(String url, int thread_id, int finished);
    
        /**
         * 查询文件的线程信息
         */
        List<ThreadInfo> getThreads(String url);
    
        /**
         * 线程信息是否存在
         */
        boolean isExists(String url, int thread_id);
    }
    
    
    实现数据访问接口

    就是简单的增删改查的操作

    package com.gin.xjh.download_demo.db;
    
    import android.content.Context;
    import android.database.Cursor;
    import android.database.sqlite.SQLiteDatabase;
    
    import com.gin.xjh.download_demo.entities.ThreadInfo;
    
    import java.util.ArrayList;
    import java.util.List;
    
    /**
     * 数据访问接口的实现
     */
    
    public class ThreadDAOImpl implements ThreadDAO {
    
        private DBHelper mHelper = null;
    
        public ThreadDAOImpl(Context context) {
            mHelper = new DBHelper(context);
        }
    
    
        @Override
        public void insertThread(ThreadInfo threadInfo) {
            SQLiteDatabase db = mHelper.getWritableDatabase();
            db.execSQL(
                    "insert into thread_info(thread_id,url,start,ends,finished) values(?,?,?,?,?)",
                    new Object[]{threadInfo.getId(), threadInfo.getUrl(), threadInfo.getStart(),
                            threadInfo.getEnds(), threadInfo.getFinished()});
            db.close();
        }
    
        @Override
        public void deleteThread(String url, int thread_id) {
            SQLiteDatabase db = mHelper.getWritableDatabase();
            db.execSQL(
                    "delete from thread_info where url = ? and thread_id = ?",
                    new Object[]{url, thread_id});
            db.close();
        }
    
        @Override
        public void updateThread(String url, int thread_id, int finished) {
            SQLiteDatabase db = mHelper.getWritableDatabase();
            db.execSQL(
                    "update thread_info set finished = ? where url = ? and thread_id = ?",
                    new Object[]{finished, url, thread_id});
            db.close();
        }
    
        @Override
        public List<ThreadInfo> getThreads(String url) {
            List<ThreadInfo> list = new ArrayList<>();
            SQLiteDatabase db = mHelper.getReadableDatabase();
            Cursor cursor = db.rawQuery("select * from thread_info where url = ?",
                    new String[]{url});
            while (cursor.moveToNext()) {
                ThreadInfo thread = new ThreadInfo();
                thread.setId(cursor.getInt(cursor.getColumnIndex("thread_id")));
                thread.setUrl(cursor.getString(cursor.getColumnIndex("url")));
                thread.setStart(cursor.getInt(cursor.getColumnIndex("start")));
                thread.setEnds(cursor.getInt(cursor.getColumnIndex("ends")));
                thread.setFinished(cursor.getInt(cursor.getColumnIndex("finished")));
                list.add(thread);
            }
            cursor.close();
            db.close();
            return list;
        }
    
        @Override
        public boolean isExists(String url, int thread_id) {
            SQLiteDatabase db = mHelper.getWritableDatabase();
            Cursor cursor = db.rawQuery("select * from thread_info where url = ? and thread_id = ?",
                    new String[]{url, thread_id + ""});
            boolean exists = cursor.moveToNext();
            cursor.close();
            db.close();
            return exists;
        }
    }
    
    
    

    任务下载类

    我们需要定义一个任务下载类,在里面启动线程下载我们需要的文件。

    在文件的下载中我们使用的是RandomAccessFile,这个类的seek方法可以在读写的时候根据我们的需要在我们设定的位置开始读写。
    然后我们对于暂停按钮的监听,定义一个标值位来判断是否点击暂停,并且在每500毫秒刷新一次进度,并且保存线程下载信息,以防程序奔溃,数据没有记录。

    package com.gin.xjh.download_demo.services;
    
    import android.content.Context;
    import android.content.Intent;
    import android.util.Log;
    
    import com.gin.xjh.download_demo.db.ThreadDAO;
    import com.gin.xjh.download_demo.db.ThreadDAOImpl;
    import com.gin.xjh.download_demo.entities.FileInfo;
    import com.gin.xjh.download_demo.entities.ThreadInfo;
    
    import java.io.File;
    import java.io.IOException;
    import java.io.InputStream;
    import java.io.RandomAccessFile;
    import java.net.HttpURLConnection;
    import java.net.MalformedURLException;
    import java.net.URL;
    import java.util.List;
    
    /**
     * 下载任务类
     */
    
    public class DownloadTask {
    
        private Context mContext = null;
        private FileInfo mFileInfo = null;
        private ThreadDAO mDao = null;
        private int mFinished;
        public boolean isPause = false;
    
        public DownloadTask(Context mContext, FileInfo mFileInfo) {
            this.mContext = mContext;
            this.mFileInfo = mFileInfo;
            mDao = new ThreadDAOImpl(mContext);
        }
    
        public void download() {
            //读取数据库中的线程信息
            List<ThreadInfo> threadInfos = mDao.getThreads(mFileInfo.getUrl());
            ThreadInfo threadInfo = null;
            if (threadInfos.size() == 0) {
                //初始化线程信息对象
                threadInfo = new ThreadInfo(0, mFileInfo.getUrl(), 0, mFileInfo.getLength(), 0);
            } else {
                threadInfo = threadInfos.get(0);
            }
            //创建子线程进行下载
            new DownloadThread(threadInfo).start();
        }
    
        /**
         * 下载线程
         */
        class DownloadThread extends Thread {
            private ThreadInfo mThreadInfo = null;
    
            public DownloadThread(ThreadInfo mThreadInfo) {
                this.mThreadInfo = mThreadInfo;
            }
    
            @Override
            public void run() {
                //向数据库插入线程信息
                if (!mDao.isExists(mThreadInfo.getUrl(), mThreadInfo.getId())) {
                    mDao.insertThread(mThreadInfo);
                }
                //设置下载位置
                HttpURLConnection conn = null;
                RandomAccessFile raf = null;
                InputStream input = null;
                try {
                    URL url = new URL(mThreadInfo.getUrl());
                    conn = (HttpURLConnection) url.openConnection();
                    conn.setConnectTimeout(3000);
                    conn.setRequestMethod("GET");
                    //设置下载位置
    				int start = mThreadInfo.getStart() + mThreadInfo.getFinished();
    				conn.setRequestProperty("Range", "bytes=" + start + "-" + mThreadInfo.getEnds());
    				//设置文件写入位置
    				File file = new File(DownloadService.DOWNLOAD_PATH, mFileInfo.getFileName());
    				raf = new RandomAccessFile(file, "rwd");
    				raf.seek(start);
    				//开始下载
    				mFinished += mThreadInfo.getFinished();
                    Intent intent = new Intent(DownloadService.ACTION_UPDATE);
                    if (conn.getResponseCode() == HttpURLConnection.HTTP_PARTIAL) {
                        //读取数据
                        input = conn.getInputStream();
                        byte[] buffer = new byte[1024 * 4];
                        int len = -1;
                        long time = System.currentTimeMillis();
                        while ((len = input.read(buffer)) != -1) {
                            //写入文件
                            raf.write(buffer, 0, len);
                            //把下载进度发送广播给Activity
                            mFinished += len;
                            if (System.currentTimeMillis() - time > 500) {
                                time = System.currentTimeMillis();
                                //保存下载进度
                                mDao.updateThread(mFileInfo.getUrl(), mThreadInfo.getId(), mFinished);
                                intent.putExtra("finished", mFinished * 100 / mFileInfo.getLength());
                                mContext.sendBroadcast(intent);
                                if(isPause){
                                    return;
                                }
                            }
                            //在暂停时保存下载进度
                            if (isPause) {
                                mDao.updateThread(mFileInfo.getUrl(), mThreadInfo.getId(), mFinished);
                                return;
                            }
                        }
                        intent.putExtra("finished", 100);
                        mContext.sendBroadcast(intent);
                        //删除线程信息
                        mDao.deleteThread(mFileInfo.getUrl(), mFileInfo.getId());
                    }
                } catch (MalformedURLException e) {
                    e.printStackTrace();
                } catch (IOException e) {
                    e.printStackTrace();
                } finally {
                    conn.disconnect();
                    try {
                        raf.close();
                        input.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }
    
    

    之后在启动这个下载任务就好了。

    mTask = new DownloadTask(DownloadService.this,fileInfo);
    mTask.download();
    

    广播接收器

    因为我们是通过广播的形式来更新主线程的进度条,所以我们要编写一个广播接收器,并且注册广播接收器。当我们获取到线程中的广播以后,根据广播中传递的消息对ProgressBar的进度进行更新。

    /**
     * 更新进度条的广播接收器
     */
    BroadcastReceiver mReceiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
            if(DownloadService.ACTION_UPDATE.equals(intent.getAction())){
                int finished = intent.getIntExtra("finished",0);
                mPbProgress.setProgress(finished);
            }
        }
    };
    

    对于广播接收器的注册我采用的是动态注册的方法,这里我就不多说了。
    这样我们这个单线程的断点续传功能就已经完成了。
    多线程下载的功能:因为是在这个代码上进行修改的,所有不太好写,之后会在GitHub上更新,直接看更新细节就能看到我有哪些是进行修改的。
    多线程的主要思想:通过将文件分段的方法进行下载,主要是要注意方法的同步,因为涉及到多线程的访问。
    GitHub传送门:戳这里

    展开全文
  • 断点续传android版本

    2016-06-05 01:10:33
    多线程下载之断点续传android版本 第一个页面布局<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_...

    多线程下载之断点续传android版本
    第一个页面布局

    <RelativeLayout 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"
        android:paddingBottom="@dimen/activity_vertical_margin"
        android:paddingLeft="@dimen/activity_horizontal_margin"
        android:paddingRight="@dimen/activity_horizontal_margin"
        android:paddingTop="@dimen/activity_vertical_margin"
        tools:context=".MainActivity" >
    
        <EditText
            android:id="@+id/en_path"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="http://192.168.80.212:8080/feiqiu.exe"
            android:hint="请输入下载路径" />
    
        <EditText
            android:id="@+id/en_threadCount"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_alignParentTop="true"
            android:layout_centerHorizontal="true"
            android:layout_marginTop="55dp"
            android:hint="请输入下载线程数量" >
        </EditText>
    
        <Button
            android:layout_width="100dp"
            android:layout_height="wrap_content"
            android:layout_margin="90dp"
            android:onClick="click"
            android:text="下载" />
    <!-- 写一个线性布局,动态的添加布局 -->
        <LinearLayout
            android:id="@+id/ll_layout"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:layout_marginTop="200dp"
            android:orientation="vertical" >
        </LinearLayout>
    
    </RelativeLayout>

    第二个页面布局

    <?xml version="1.0" encoding="utf-8"?>
    <ProgressBar xmlns:android="http://schemas.android.com/apk/res/android"
        android:id="@+id/progressBar1"
        style="?android:attr/progressBarStyleHorizontal"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" >
    
    </ProgressBar>

    import java.io.BufferedReader;
    import java.io.File;
    import java.io.FileInputStream;
    import java.io.InputStream;
    import java.io.InputStreamReader;
    import java.io.RandomAccessFile;
    import java.net.HttpURLConnection;
    import java.net.URL;
    import java.util.ArrayList;
    import java.util.List;

    import android.app.Activity;
    import android.os.Bundle;
    import android.os.Environment;
    import android.view.View;
    import android.widget.EditText;
    import android.widget.LinearLayout;
    import android.widget.ProgressBar;

    
    public class MainActivity extends Activity {
    
        private EditText en_path;
        private EditText en_threadCount;
        private List<ProgressBar> pbs;
        private int runningThread ;
        private int threadCount;
        private LinearLayout ll_layout;
        private String downThreadCount;
        private String downloadPath;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            //获取我们关心的控件
            en_path = (EditText) findViewById(R.id.en_path);
            en_threadCount = (EditText) findViewById(R.id.en_threadCount);
            ll_layout = (LinearLayout) findViewById(R.id.ll_layout);
            pbs = new ArrayList<ProgressBar>();
        }
    
    
      public void click(View v) {
          downloadPath = en_path.getText().toString().trim();
          downThreadCount = en_threadCount.getText().toString().trim();
          //将从控件获取到的下载数量转换为int型
          threadCount = Integer.parseInt(downThreadCount);    
          //清除集合和进度条
         ll_layout.removeAllViews();  //清除集合中的进度条
         pbs.clear();
         //有几个线程就添加几个进度条
         for (int i = 0; i < threadCount; i++) {
            ProgressBar pb = (ProgressBar) View.inflate(getApplication(), R.layout.item, null);
            pbs.add(pb);             //将进度条添加到集合
            ll_layout.addView(pb);   //将进度条添加到布局
        }
         //开启子线程
         new Thread() {
            public void run() {
                try {
                    URL url = new URL(downloadPath);
                    HttpURLConnection con = (HttpURLConnection) url
                            .openConnection();
                    con.setRequestMethod("GET");
                    con.setConnectTimeout(5000);
                    int code = con.getResponseCode();
                    if (code == 200) {
                        //从服务器获取文件的长度
                        int length = con.getContentLength();  
                        //将线程数赋值
                        runningThread = threadCount;
                        //将文件写到内存
                        RandomAccessFile rafAccessFile  = new RandomAccessFile(getFileName(downloadPath), "rw");
                        rafAccessFile.setLength(length);  //设置该文件的大小
    
                        int blockSize = length / threadCount;    //得到每个线程下载的总大小
    
                        for (int i = 0; i < threadCount; i++) {
                            int startIndex = i * blockSize;      //计算每个线程出开始的位置
                            int endIndex = (i + 1) * blockSize;  //计算每个线程的结束位置
                        if(i == threadCount - 1){                //计算最后一个线程的位置
                            endIndex = length - 1;
                        }
                        new DownLoadThread(i,startIndex,endIndex).start();  //开启线程下载
                        }
    
                    }
                } catch (Exception e) {
    
                    e.printStackTrace();
                }
            }
        }.start();
    
    }
      //下载文件
      class DownLoadThread extends Thread{
          private int threadid;   //线程id
          private int startIndex; //线程的开始位置
          private int endIndex;   //线程的结束位置
          private int pbMax;      //进度条的最大值
          private int pbLastPosition;  //进度条的当前位置
          public DownLoadThread(int threadid,int startIndex,int endIndex){
              this.threadid = threadid;
              this.startIndex = startIndex;
              this.endIndex = endIndex;
          }
    
          @Override
        public void run() {
            try {
                pbMax = endIndex - startIndex;     //计算出进度条的最大值
                URL url = new URL((downloadPath));
                HttpURLConnection con = (HttpURLConnection) url.openConnection();
                con.setRequestMethod("GET");
                con.setConnectTimeout(5000);
                //如果线程下载过程中中断过,则走下面的 if语句,没有则跳过该语句继续执行
                File file = new File(getFileName(downloadPath) + threadid + ".txt");  //存储每个线程的txt文件
                if(file.exists() && file.length() > 0){
                    //用高效的字符流关联该文件
                    BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream(file)));
                    String lastPosition = br.readLine();
                    pbLastPosition = Integer.parseInt(lastPosition);
                    startIndex = Integer.parseInt(lastPosition);   //将读取出来的数据作为当前线程的开始位置
                     br.close();
                }
                //设置Range头,指定下载的开始位置和结束位置
                con.setRequestProperty("Range", "bytes=" + startIndex + "-" + endIndex);
    
                int code = con.getResponseCode();
                if (code == 206) {       //请求资源部分求成功
                    InputStream in = con.getInputStream();
                    RandomAccessFile raf = new RandomAccessFile(getFileName(downloadPath), "rw");
                    raf.seek(startIndex); //设置文件的开始位置
    
                    int len;
                    int total = 0;
                    byte[] arr = new byte[1024*1024];
                    while((len = in.read(arr)) != -1){   
                        total += len;  //获取到正在下载的长度并累加
                        int currentThreadPosition = startIndex + total;   //将当前下载的长度和开始位置相加得到线程当前的位置
    
                        //创建一个当前线程的txt文件,用于存储中断时的位置
                        RandomAccessFile accessFile = new RandomAccessFile(getFileName(downloadPath) + threadid + ".txt", "rwd");
                        //将此位置写入到文件中
                        accessFile.write(String.valueOf(currentThreadPosition).getBytes());
                        accessFile.close();
                        //下载数据(将数据写到文件中,此文件为.exe(以飞秋为例)形式)
                        raf.write(arr, 0, len);
                        //设置进度条的大小
                        pbs.get(threadid).setMax(pbMax);   //进度条的最大值
                        //设置进度条的当前位置
                        pbs.get(threadid).setProgress(pbLastPosition + total - startIndex);
                    }
                    //关流
                    in.close();
                    raf.close();
                    //为什么不用threadCount--,而是给它赋值为runningThread在操作呢?
                    //个人理解:如果直接操作threadCount,那么当threadCount出现错误时全局都会受到影响
                    //但是如果操作runningThread出现错误时,并不会对全局造成影响,并且代码容易维护
                    synchronized ((DownLoadThread.class) ) {//同步:是为了在当前线程能够完全执行,其他线程不中断当前线程
                        runningThread --;
                        if(runningThread == 0){ //如果正在运行的线程等于0,则将生成的临时文件删除
                            for (int i = 0; i < threadCount; i++) {
                                File txtfile = new File(getFileName(downloadPath) + i + ".txt");
                                txtfile.delete();
    
                            }
                        }
                    }
    
                }
            } catch (Exception e) {
    
                e.printStackTrace();
            }
    
        }
      }
    
      public static String getFileName(String path){
          int start = path.lastIndexOf("/") + 1;
          String sdCard = Environment.getExternalStorageDirectory().getPath();
          return sdCard + "/" + path.substring(start);
      }
    
    }
    展开全文
  • 0. 前言在Android开发中,断点续传听起来挺容易,在下载一个文件时点击暂停任务暂停,点击开始会继续下载文件。但是真正实现起来知识点还是蛮多的,因此今天有时间实现了一下,并进行记录。本文原创,转载请注明...
  • 摘要: oss sdk 断点续传功能使用及其相关原理前言 移动端现状 随着移动端设备的硬件水平的不断提高,如今的cpu,内存等方面都大大的超过了一般的pc电脑,因此在现今的程序中,合理的使用多线程去完成一些事情是...
  • 1.导入依赖compile '...uses-permission android:name="android.permission.INTERNET"&gt;&lt;/uses-permission&gt; &lt;uses-permission android:name="android.pe...
  • 我们在刷一下面试题的时候,有时候会看到一些大厂会问关于断点续传的原理,那么今天在这里从 HTTP 断点续传知识和 Android 中如何实现断点续传的思路来做一个关于 Android 断点续传原理的总结。 Http 断点续传知识...
  • android 中多任务断点续传下载, android 中多任务断点续传下载, android 中多任务断点续传下载,下载试试吧
  • Android实现网络多线程断点续传下载 本文续接我上一篇文章《Android实战:简易断点续传下载器实现》 链接地址:http://www.jianshu.com/p/5b2e22c42467 本项目Github地址:...
  • android 断点续传下载

    2020-07-21 09:56:50
    android断点续传下载,不因网络错误、空间不足等原因而停止下载,死循环下载,直到手动退出。
  • Android多线程断点续传

    2016-07-13 20:34:48
    那么,多线程断点需要什么功能?1.多线程下载,2.支持断点。使用多线程的好处:使用多线程下载会提升文件下载的速度。那么多线程下载文件的过程是: (1)首先获得下载文件的长度,然后设置本地文件的长度。 ...
  • android断点续传demo

    2020-05-13 23:30:06
    一个实现断点续传android小demo,帮助理解怎样断点续传下载实现
  • 背景 现在的网络环境已经很完善了,几乎到处都有WIFI,流量多到用不完。随便更新一款APP都不是事儿才多少流量呀,都不是事儿。但是为了本着顾客至上的态度,作为菜鸟的我怎能忍心浪费每位顾客大人的LL呢?...
  • Android使用FTP实现断点续传 断点续传指的是在下载或上传时,将下载或上传任务(一个文件或一个压缩包)人为的划分为几个部分,每一个部分采用一个线程进行上传或下载,如果碰到网络故障,可以从已经上传或下载的...
  • Android断点续传文件下载,简单实用的代码,如需要可参考。 希望不要觉得我又造一个轮子,我先用了网上代码觉得不好使用,而且发现有错误; 有一些功能强大的框架,我懒得去了解,去学习怎样使用它也要花时间; ...
  • Android断点续传原理

    2014-11-24 18:14:25
    以前我曾经看过一个单位的技术标书,其中有下载的断点续传这一要求,给出的offer居然还挺高的...   简单的说,只要利用了HTTP协议(http://www.ietf.org/rfc/rfc2616.txt)中的如下字段来和服务器端交互,就...
  • 本人先前的博客有对多文件分段断点续传的功能进行详细的介绍,如果你有兴趣可以先阅读Android多文件断点续传(一)——数据封装以及界面实现。本人在先前的基础上对代码进行了封装,本帖主要介绍如何集成封装好的...
  • Android 断点续传

    2019-09-22 01:18:01
    断点续传指的是在下载或上传时,将下载或上传任务(一个文件或一个压缩包)人为的划分为几个部分,每一个部分采用一个线程进行上传或下载,如果碰到网络故障,可以从已经上传或下载的部分开始继续上传下载未完成的...
1 2 3 4 5 ... 20
收藏数 7,549
精华内容 3,019
关键字:

断点续传android