Android文件下載詳解(仿第一行代碼)


其實第一行代碼的例子我早就學習過了,現在手上一個項目需要使用到這些東西,准備在操作之前復習一下,就順便發個博客加深一下自己對Android服務和文件下載這一塊的理解,博客可能有點長我盡量寫的詳細一點便於理解。

1.首先新建一個Android項目

2.添加okhttp依賴,這里我們使用okhttp來訪問網絡,其實我是習慣使用http的,但是不可否認okhttp很強大

dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {
        exclude group: 'com.android.support', module: 'support-annotations'
    })
    compile 'com.android.support:appcompat-v7:24.2.1'
    testCompile 'junit:junit:4.12'
    compile 'com.squareup.okhttp3:okhttp:3.4.1'//就添加這一行
}

3.定義一個回調接口DownloadListener ,用於對下載狀態的監聽和對調

public interface DownloadListener {
    void onProgress(int progress);//通知當前下載進度
    void onSuccess();//通知下載成功
    void onFiled();//通知下載失敗
    void onPaused();//下載暫停
    void inCanceled();//下載取消
}

4.開始編寫下載功能。我們使用AsyncTask來實現,AsyncTask是Android提供的輕量級的異步類,我們可以再里面完成一些耗時的工作,比如下載

新建一個DownloadTask繼承AsyncTask,這個代碼有一丟丟長啊,首先我大概說下,下面主要的有三個函數,doInBackground用於執行下載邏輯,onProgressUpdate用於更新下載進度,onPostExecute用於通知最終結果,下面雖然代碼長,我幾乎所有的主要內容全給了備注,相信就算是新手也看的懂得

 

package com.example.dowmloadfile.Myclass;

import android.os.AsyncTask;
import android.os.Environment;


import com.example.dowmloadfile.imp.DownloadListener;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.RandomAccessFile;

import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;

/**
 * Created by 海綿寶寶 on 2019/5/13.
 */
/**三個參數第一個為需要傳入給后台的數據類型,第二個是進度顯示類型,第三個為使用Integer作為反饋結果類型**/
public class DownLoadTask extends AsyncTask<String,Integer,Integer> {

    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 downloadListener;

    private boolean isCanceled=false;

    private boolean isPaused=false;

    private int lastProgress;
    //通過DownLoadTask回調下載狀態
    public DownLoadTask(DownloadListener listener){
        downloadListener=listener;//傳入一個剛才定義的接口,用於回調
    }
    @Override//后台執行具體的下載邏輯
    protected Integer doInBackground(String... params) {//參數為下載的URL地址
        InputStream is=null;
        RandomAccessFile savedFile=null;
        File file=null;
        try{
            long downloadLength=0;
            String downloadUrl=params[0];
            String fileName=downloadUrl.substring(downloadUrl.lastIndexOf("/"));//解析出下載的文件名
            String derectory= Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).getPath();//獲取本地的下載文件夾的路徑
            file=new File(derectory+fileName);
            if (file.exists()){
                downloadLength=file.length();
            }
            long contentLength=getContentLength(downloadUrl);//讀取下載文件的字節數
            if (contentLength==0){//下載文件長度為0說明文件不存在,返回下載失敗
                return TYPE_FAILED;
            }else if (contentLength==downloadLength){//下載文件與本地文件大小一致說明已下載返回下載成功
                return TYPE_SUCCESS;
            }
            OkHttpClient client=new OkHttpClient();
            Request request=new Request.Builder().addHeader("RANGE","bytes="+downloadLength+"-").url(downloadUrl).build();//head用於告訴服務器我們從那個字節開始下載
            Response response=client.newCall(request).execute();
            if (response!=null){
                //以java流的方式不斷獲取信息,不斷寫入本地
                is=response.body().byteStream();
                savedFile=new RandomAccessFile(file,"rw");
                savedFile.seek(downloadLength);//跳過已經下載的字節
                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+downloadLength)*100/contentLength);//計算當前進度
                        publishProgress(progress);//在通知中顯示
                    }
                }
                response.body().close();
                return TYPE_SUCCESS;
            };
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            try {
                if (is!=null){
                    is.close();
                }else if (savedFile!=null){
                    savedFile.close();
                }else if (isCanceled&&file!=null){
                    file.delete();
                }
            }catch (Exception e){
                e.printStackTrace();
            }
        }
        return TYPE_FAILED;
    }

    @Override//用於比較下載進度然后使用onProgress來更改下載進度的通知
    protected void onProgressUpdate(Integer... values) {
        int progress=values[0];//獲取下載進度
        if (progress>lastProgress){//如果進度發生了變化,那么更新進度顯示
            downloadListener.onProgress(progress);
            lastProgress=progress;
        }
    }

    @Override//根據傳入的參數進行回調
    protected void onPostExecute(Integer integer) {
        switch (integer) {
            case TYPE_SUCCESS:
                downloadListener.onSuccess();
                break;
            case TYPE_FAILED:
                downloadListener.onFiled();
                break;
            case TYPE_PAUSED:
                downloadListener.onPaused();
                break;
            case TYPE_CANCELED:
                downloadListener.inCanceled();
            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;
    }

}

 

5.右擊com.example.dowmloadfile-》New-》Service-》Service新建一個服務然后修改其中的代碼

下面代碼也比較長,簡述一下就是,先創建了一個DownloadListener實現了我們之前建立的接口的具體方法,為了使DownloadService 可以和活動通信我們建立了一個DownloadBinder 將DownloadListener以參數形式傳入,最后getNotification用於顯示進度內容

package com.example.dowmloadfile.Myservice;

import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.Service;
import android.content.Intent;
import android.graphics.BitmapFactory;
import android.os.Binder;
import android.os.Environment;
import android.os.IBinder;
import android.support.v4.app.NotificationCompat;
import android.widget.Toast;

import com.example.dowmloadfile.MainActivity;
import com.example.dowmloadfile.Myclass.DownLoadTask;
import com.example.dowmloadfile.R;
import com.example.dowmloadfile.imp.DownloadListener;

import java.io.File;

public class DownloadService extends Service {
    private DownLoadTask downLoadTask;
    private String downloadUrl;
    //創建一個DownloadListener並實現其中的方法
    private DownloadListener downloadListener=new DownloadListener() {
        @Override//以通知的方式顯示進度條
        public void onProgress(int progress) {
            //使用getNotificationManager函數構建一個用於顯示下載進度的通知
            //使用notify去觸發這個通知
            getNotificationManager().notify(1,getNotification("Download...",progress));
        }

        @Override
        public void onSuccess() {
            downLoadTask=null;
            //關閉前台服務通知
            //下面自己寫了一個通知用於通知下載成功
            stopForeground(true);
            getNotificationManager().notify(1,getNotification("download succeed",-1));
            Toast.makeText(DownloadService.this,"Download Succeed",Toast.LENGTH_LONG).show();
            String directory= Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).getPath();
        }

        @Override
        public void onFiled() {
            downLoadTask=null;
            //關閉前台服務通知
            stopForeground(true);
            getNotificationManager().notify(1,getNotification("download filed",-1));
            Toast.makeText(DownloadService.this,"Download failed",Toast.LENGTH_LONG).show();
        }

        @Override
        public void onPaused() {
            downLoadTask=null;
            Toast.makeText(DownloadService.this,"Download Paused",Toast.LENGTH_LONG).show();
        }

        @Override
        public void inCanceled() {
            downLoadTask=null;
            //關閉前台服務通知
            stopForeground(true);
            Toast.makeText(DownloadService.this,"Download canceled",Toast.LENGTH_LONG).show();
        }
    };

    //用於使服務可以和活動通信
    private DownloadBinder mBinder=new DownloadBinder();
    @Override
    public IBinder onBind(Intent intent) {
        // TODO: Return the communication channel to the service.
        return mBinder;
    }
    //用於使服務可以和活動通信
    public class DownloadBinder extends Binder{
        //開始下載
        public void startDownload(String url){
            if(downLoadTask==null){
                downloadUrl=url;
                //創建一個DownLoadTask,將上面的downloadListener傳入
                downLoadTask=new DownLoadTask(downloadListener);
                //使用execute開啟下載
                downLoadTask.execute(downloadUrl);
                //startForeground使服務成為一個前台服務以創建持續運行的通知
                startForeground(1,getNotification("download...",0));
                Toast.makeText(DownloadService.this,"Download",Toast.LENGTH_LONG).show();
            }
        }
        //暫停下載
        public void pauseDownload(){
            if (downLoadTask!=null){
                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_LONG).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();
    }
}

 

 

6.后台的代碼就是上面那些,剩下的就好理解多了,現在修改xml文件,定義三個按鈕分別用於開始下載,暫停下載,取消下載

 

<?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"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="start download"/>
    <Button
        android:id="@+id/pause"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="start pause"/>
    <Button
        android:id="@+id/cancel"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="start cancel"/>

</LinearLayout>

 

7.修改Main.java給按鈕添加點擊事件,將前台和我們寫好的后台聯系起來,主函數較為簡單,細心一點都能看懂的

package com.example.dowmloadfile;

import android.Manifest;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.content.pm.PackageManager;
import android.os.IBinder;
import android.support.v4.app.ActivityCompat;
import android.support.v4.content.ContextCompat;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;

import com.example.dowmloadfile.Myservice.DownloadService;

public class MainActivity extends AppCompatActivity implements View.OnClickListener{
    private DownloadService.DownloadBinder downloadBinder;
    //ServiceConnection主要用於獲取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 start=(Button)findViewById(R.id.start);
        Button pause=(Button)findViewById(R.id.pause);
        Button cancel=(Button)findViewById(R.id.cancel);
        start.setOnClickListener(this);
        pause.setOnClickListener(this);
        cancel.setOnClickListener(this);
        Intent intent=new Intent(this, DownloadService.class);
        startService(intent);//啟動服務保證服務一直運行
        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 onClick(View view){
        if(downloadBinder==null)
        {
            return;
        }
        switch (view.getId()){
            case R.id.start:
                String url="http://47.107.108.172:8080/Competition/fileUpload/xueyuanFile/1/10/12e1d4ba00aa43a1a69cf572d3d91816_關於舉辦第四屆全國移動互聯創新大賽的通知.pdf";
                downloadBinder.startDownload(url);
                break;
            case R.id.pause:
                downloadBinder.pauseDownload();
                break;
            case R.id.cancel:
                downloadBinder.cancelDownload();
                break;
            default:
                break;
        }
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        //解除服務的綁定,否則可能會造成消息泄露
        unbindService(connection);
    }
}

8.此時Android應該已經幫我們注冊好了權限,不過為了保險起見我們檢查一下

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.dowmloadfile">

    <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:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>

        <service
            android:name=".Myservice.DownloadService"
            android:enabled="true"
            android:exported="true"></service>
    </application>

</manifest>

9.現在程序就可以運行了

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM