AndroidAndroid开发程序员

10探究服务-下载实例

2018-04-02  本文已影响24人  何惧l

编写一个完整版的下载实例

  1. 在项目中添加使用的依赖库,编辑build.gradle文件
dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    implementation 'com.android.support:appcompat-v7:26.1.0'
    implementation 'com.android.support.constraint:constraint-layout:1.0.2'
    testImplementation 'junit:junit:4.12'
    androidTestImplementation 'com.android.support.test:runner:1.0.1'
    androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.1'

    compile 'com.squareup.okhttp3:okhttp:3.4.1'
}
  1. 定义回调接口,用于对下载过程中的各种状态进行监听和回调,建一个接口DownloadListener
// 自定义一个回调接口
public interface DownloadListener {

    // 用于通知当前下载的进度
    void onProgress(int progress);

    // 用于通知下载成功事件
    void onSuccess();

    // 下载失败事件
    void onFailed();

    //下载暂停事件
    void onPaused();

    // 下载取消事件
    void onCanceled();
}
  1. 编写下载功能,使用AsyncTask来进行实现,DownloadTask继承自AsyncTask
// 编写下载功能,第一个参数表示执行这个的时候要传入一个参数给后台
    //第二个参数表示使用整型来作为进度条单位
    // 第三个参数用整型表示返回的结果类型
public class DownloadTask extends AsyncTask<String,Integer,Integer> {

    // 成功
    private static final int TYPE_SUCCESS = 0;
    //失败
    private static final int TYPE_FAILED = 1;
    // 暂停
    private static final int TYPE_PAUSED = 2;
    // 取消
    private static final int TYPE_CANCELED = 3;

    private DownloadListener listener;

    // 是否取消
    private boolean isCanceled = false;
    // 是否暂停
    private boolean isPaused = false;

    // 下载速度
    private int lastProgress;


    // 在构造函数中传入自定义的接口,将下载的状态通过这个参数进行回调
    public DownloadTask(DownloadListener listener){
        this.listener = listener;
    }


    // 负责后台下载逻辑
    @Override
    protected Integer doInBackground(String... strings) {
        InputStream is = null;
        RandomAccessFile savedFile = null;
        File file = null;

        try{
            // 记录已经下载的文件长度
            long downloadedLength = 0;
            // 获取下载的URL地址
            String downloadUrl = strings[0];
            // 根据下载的URL地址解析出下载的文件名
            String fileName = downloadUrl.substring(downloadUrl.lastIndexOf("/"));
            // 指定将文件下载到Environment.DIRECTORY_DOWNLOADS目录下,也就是sd卡的Download目录下
            String directory = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).getPath();
            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;
            }

            // 这里使用的是OkHttp发送一个网络请求,
            // 注意这里在请求中添加了一个header,用于告诉服务器想要从那个字节开始读取
            // 因为下载获得部分就不用再下载了
            OkHttpClient client = new OkHttpClient();
            Request request = new Request.Builder()
                    .addHeader("RANGE","bytes="+downloadedLength+"-")
                    .url(downloadUrl)
                    .build();
            //Request对象就是服务器返回的数据
            Response response = client.newCall(request).execute();

            //进行判断
            // 读取服务器响应的数据,并使用java流的方法,不断的读取,不断的写入,
            // 在这个过程中要判断用户是否点击了暂停或者取消,要做出响应的判断
            // 如果一切顺利的话,则实时计算当前的下载进度,调用 publishProgress()方法进行通知
            if (request != null){
                // 获取返回的数据
                is = response.body().byteStream();
                savedFile = new RandomAccessFile(file,"rw");
                // 这里跳过已经下载的字节
                savedFile.seek(downloadedLength);
                byte[] b = new byte[1024];
                int total = 0;
                int len;
                while ((len = is.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);
                    }
                }
                response.body().close();
                return TYPE_SUCCESS;
            }
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            try{
                if (is != null){
                    is.close();
                }
                if (savedFile != null){
                    savedFile.close();
                }
                if (isCanceled && file != null){
                    file.delete();
                }
            }catch (Exception e){
                e.printStackTrace();
            }
        }
        return TYPE_FAILED;
    }




    //当后台任务中调用了publishProgress(Progress...)方法之后
    // 用于更新当前下载的进度,首先获取到当前的下载速度和上次保存的下载速度进行对比
    // 如果有变化的时候,就调用DownloadListener的onProgress()方法用于通知当前下载进度
    @Override
    protected void onProgressUpdate(Integer... values) {
        int progress = values[0];
        if (progress > lastProgress){
            // 接口中的方法,用于通知下载进度
            listener.onProgress(progress);
            lastProgress = progress;
        }
    }


    // 这个方法中的参数就是doInBackground()方法中return返回的数据
    // 通知最终下载的结果
    @Override
    protected void onPostExecute(Integer integer) {
        switch (integer){
            case TYPE_SUCCESS:
                listener.onSuccess();
                break;
            // 失败
            case TYPE_FAILED:
                listener.onFailed();
                break;
            // 暂停
            case TYPE_PAUSED:
                listener.onPaused();
                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.close();
            return contentLength;
        }
        return 0;
    }

}

  1. 具体的下载就是这样,为了让这个下载可以在后台一直运行,需要创建一个服务,

public class DownloadService extends Service {

    private DownloadTask downloadTask;

    // 下载的地址
    private String downloadUrl;

    // 首先创建一个DownloadListener匿名类实例,在这个匿名类中实现这五个方法
    private DownloadListener listener = new DownloadListener() {
        // 用于通知当前下载的进度
        @Override
        public void onProgress(int progress) {
            // getNotificationManager().notify()方法,用于触发通知
            // 这样就可以在下拉状态栏中实时看到当前的下载的进度
            getNotificationManager().notify(1,getNotification("Downloading...",progress));
        }

        // 用于通知下载成功事件
        @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();
        }

        // 下载失败事件
        @Override
        public void onFailed() {

            downloadTask = null;
            stopForeground(true);
            getNotificationManager().notify(1,getNotification("Download Failed",-1));
            Toast.makeText(DownloadService.this,"Download Failed",Toast.LENGTH_SHORT).show();
        }

        //下载暂停事件
        @Override
        public void onPaused() {
            downloadTask = null;
            Toast.makeText(DownloadService.this,"Download Paused",Toast.LENGTH_SHORT).show();
        }

        // 下载取消

        @Override
        public void onCanceled() {
            downloadTask = null;
            Toast.makeText(DownloadService.this,"Download Canceled",Toast.LENGTH_SHORT).show();
        }
    };





    //创建一个下载的对象
    private DownloadBinder mBinder = new DownloadBinder();

    @Override
    public IBinder onBind(Intent intent) {
        return mBinder;
    }

    // 借助这个可以让服务和活动之间进行通信
    class DownloadBinder extends Binder{
        // 开始下载
        public void startDownload(String url){
            if (downloadTask == null){
                // 获取连接
                downloadUrl = url;
                // 创建一个DownloadTask的实例,把刚才的DownloadListener的实例传入进去
                downloadTask = new DownloadTask(listener);
                // 调用execute()方法开始下载,把下载地址传入
                downloadTask.execute(downloadUrl);
                // 为了让这个下载的服务成为一个前台的服务,调用这个startForeground()方法
                // 这样就在系统的状态栏中创建了以一个持续运行的通知了
                startForeground(1,getNotification("Downloadign...",0));
                Toast.makeText(DownloadService.this,"Downloading...",Toast.LENGTH_SHORT).show();
            }
        }

        // 暂停下载
        public void pauseDownload(){
            if (downloadTask != null){
                // 简单的调用了DownloadTask的pauseDownload()方法
                downloadTask.pauseDownload();
            }
        }

        // 下载取消
        public void cancelDownload(){
            if (downloadTask != null){
                downloadTask.cancelDownload();
            }else {
                if (downloadUrl !=null){
                    // 取消文件把文件删除,并通知关闭
                    String fileName = downloadUrl.substring(downloadUrl.lastIndexOf("/"));
                    String directory = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).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();

                }
            }
        }
    }


    private NotificationManager getNotificationManager(){
        return (NotificationManager)getSystemService(NOTIFICATION_SERVICE);
    }


    // 这个方法用于显示下载进度的通知
    private Notification getNotification(String title,int progress){
        Intent intent = new Intent(this,MainActivity.class);
        PendingIntent pi = PendingIntent.getActivity(this,0,intent,0);
        NotificationCompat.Builder builder = new NotificationCompat.Builder(this);
        builder.setSmallIcon(R.mipmap.ic_launcher);
        builder.setLargeIcon(BitmapFactory.decodeResource(getResources(),R.mipmap.ic_launcher));
        builder.setContentIntent(pi);
        builder.setContentTitle(title);
        if (progress > 0){
            builder.setContentText(progress + "%");
            // 这个是设置进度条的,第一个参数是总长度,第二个是当前的长度,第三个是是否使用模糊进度条
            builder.setProgress(100,progress,false);
        }
        return builder.build();
    }


}


  1. 现在下载的服务也就成功的实现了,开始写前端页面
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"

    android:layout_width="match_parent"
    android:layout_height="match_parent"
    >


    <Button
        android:id="@+id/start_download"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="start Download"/>
    <Button
        android:id="@+id/pause_download"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="pause Download"/>
    <Button
        android:id="@+id/cancel_download"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="cancel Download"/>

</LinearLayout>

  1. 最后就是修改MainActivity中的代码
public class MainActivity extends AppCompatActivity implements View.OnClickListener{


    private DownloadService.DownloadBinder downloadBinder;


    //创建一个匿名类进行活动和事件的绑定
    private ServiceConnection connection = new ServiceConnection() {
        // 绑定成功的话
        @Override
        public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
            // 通过向下转型获取到了DownloadBinder的实例
            // 有了这个实例就可以在活动中调用服务提供的各种方法了
            downloadBinder = (DownloadService.DownloadBinder)iBinder;
        }

        @Override
        public void onServiceDisconnected(ComponentName componentName) {
        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Button startDownload = (Button)findViewById(R.id.start_download);
        Button spauseDownload = (Button)findViewById(R.id.pause_download);
        Button cancelDownload = (Button)findViewById(R.id.cancel_download);
        startDownload.setOnClickListener(this);
        spauseDownload.setOnClickListener(this);
        cancelDownload.setOnClickListener(this);
        Intent intent = new Intent(this,DownloadService.class);
        // 启动服务
        startService(intent);
        // 绑定服务,第二个参数是前面创建ServiceConnection的实例
        bindService(intent,connection,BIND_AUTO_CREATE);

        // 运行时申请权限 WRITE_EXTERNAL_STORAGE
        // 文件要下载到SD卡,所以要申请权限
        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 onClick(View view) {
        if (downloadBinder == null){
            return;
        }
        switch (view.getId()){
            case R.id.start_download:
                //String url = "https://qd.myapp.com/myapp/qqteam/AndroidQQ/mobileqq_android.apk";
                String url = "https://dl.hdslb.com/mobile/latest/iBiliPlayer-bili.apk";

                downloadBinder.startDownload(url);
                break;
            case R.id.pause_download:
                downloadBinder.pauseDownload();
                break;
            case R.id.cancel_download:
                downloadBinder.cancelDownload();
                break;
            default:
                break;
        }
    }

    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull 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);
    }

}

  1. 当然了,在AndroidManifest.xml文件中还要声明使用到的权限,以及服务
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.md.servertest">

    <uses-permission android:name="android.permission.INTERNET"/>
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <service
            android:name=".DownloadService"
            android:enabled="true"
            android:exported="true" />

        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN"/>
                <category android:name="android.intent.category.LAUNCHER"/>
            </intent-filter>
        </activity>
    </application>

</manifest>

  1. 这个时候运行程序就可以了,同意权限,点击开始下载按钮,


    下载主页面.png
上一篇 下一篇

猜你喜欢

热点阅读