一手遮天 Android - 后台服务: DownloadManager

项目地址 https://github.com/webabcd/AndroidDemo
作者 webabcd

一手遮天 Android - 后台服务: DownloadManager

示例如下:

/service/DownloadManagerDemo1.java

/**
 * DownloadManager 的示例(下载任务会在通知栏显示,即使 app 被杀也能继续下载)
 * 本例以一个 apk 文件下载为例,演示如何新增任务,删除任务,获取任务状态和下载进度,以及下载完成后弹出安装界面
 *
 * 注:
 * 1、我这里模拟器运行失败,真机运行正常
 * 2、请先进入 /storage/StorageDemo3.java 动态申请权限(我懒得在这里写了)
 */

package com.webabcd.androiddemo.service;

import androidx.appcompat.app.AppCompatActivity;

import android.app.DownloadManager;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.database.Cursor;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.os.Environment;
import android.os.StrictMode;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;

import com.webabcd.androiddemo.R;

import java.io.File;
import java.util.Locale;

public class DownloadManagerDemo1 extends AppCompatActivity {

    private final String LOG_TAG = "DownloadManagerDemo1";

    private Thread mThread;
    private DownloadManager mDownloadManager;
    private long mDownloadId = -1; // 下载任务的唯一标识

    private boolean mRegistered = false;
    private DownloadReceiver mDownloadReceiver;

    private Button mButton1;
    private Button mButton2;
    private Button mButton3;
    private TextView mTextView1;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_service_downloadmanagerdemo1);

        mButton1 = findViewById(R.id.button1);
        mButton2 = findViewById(R.id.button2);
        mButton3 = findViewById(R.id.button3);
        mTextView1 = findViewById(R.id.textView1);

        sample();
        registerReceiver();
    }

    private void sample() {
        // 新增下载任务
        mButton1.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                // 用于在 7.0 或以上系统,让下载的 apk 文件可以被直接安装(否则会因为读取不到 apk 而无法安装)
                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
                    StrictMode.VmPolicy.Builder localBuilder = new StrictMode.VmPolicy.Builder();
                    StrictMode.setVmPolicy(localBuilder.build());
                }

                // 通过下载地址实例化 DownloadManager.Request 对象
                String downloadUrl = "https://mmgrapp-75037.gzc.vod.tencent-cloud.com/secure/GodDresser/1/2/3/102027/tencentmobilemanager_20210401113919_8.11.0_android_build6724_102027.apk";
                DownloadManager.Request request = new DownloadManager.Request(Uri.parse(downloadUrl));

                // 移动网络和 wifi 网络均可下载
                request.setAllowedNetworkTypes(DownloadManager.Request.NETWORK_MOBILE | DownloadManager.Request.NETWORK_WIFI);
                // 通知栏标题
                request.setTitle("download_title");
                // 通知栏内容
                request.setDescription("download_description");
                // 7.0 或以上系统适配
                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
                    request.setRequiresDeviceIdle(false); // 是否只能在系统空闲时执行
                    request.setRequiresCharging(false); // 是否只能在充电状态下执行
                }

                // 下载过程和下载完成后通知栏有通知消息
                request.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE | DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED);
                // 隐藏通知栏的下载任务的消息,需要在 AndroidManifest.xml 配置权限 <uses-permission android:name="android.permission.DOWNLOAD_WITHOUT_NOTIFICATION" />
                // request.setNotificationVisibility(DownloadManager.Request.VISIBILITY_HIDDEN);
                // 下载文件的类型为 apk
                request.setMimeType("application/vnd.android.package-archive");
                // 指定文件保存地址,需要动态申请权限,否则会报错 No permission to write to...(懒得写了,请先进入 /storage/StorageDemo3.java 动态申请权限)
                File file = new File(Environment.getExternalStorageDirectory(), "myTest.apk");
                Log.d(LOG_TAG, "localUrl: " + Uri.fromFile(file).toString());
                request.setDestinationUri(Uri.fromFile(file));

                // 实例化 DownloadManager
                mDownloadManager = (DownloadManager)DownloadManagerDemo1.this.getSystemService(Context.DOWNLOAD_SERVICE);
                // 将下载请求加入下载队列,返回数据为该下载任务的标识
                mDownloadId = mDownloadManager.enqueue(request);
                Log.d(LOG_TAG, "downloadId: " + mDownloadId);
            }
        });

        // 删除当前的下载任务(如何手动 pause/resume 呢?我不知道)
        mButton2.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                if (mDownloadId > -1) {
                    mDownloadManager = (DownloadManager) DownloadManagerDemo1.this.getSystemService(Context.DOWNLOAD_SERVICE);
                    mDownloadManager.remove(mDownloadId);
                }
            }
        });

        // 跳转到系统下载界面
        mButton3.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                DownloadManagerDemo1.this.startActivity(new android.content.Intent(DownloadManager.ACTION_VIEW_DOWNLOADS));
            }
        });

        // 获取任务状态
        mThread = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    while (true) {
                        if (mDownloadId == -1) {
                            continue;
                        }

                        // 实例化 DownloadManager.Query 对象
                        DownloadManager.Query query = new DownloadManager.Query();
                        // 查询指定的 downloadId(可以不指定查询条件,那样就是查询全部下载任务)
                        query.setFilterById(mDownloadId);
                        // 根据查询条件获取游标
                        Cursor cursor = mDownloadManager.query(query);
                        if (cursor.moveToFirst()) {

                            int downloadIdIndex = cursor.getColumnIndex(DownloadManager.COLUMN_ID);
                            int remoteUrlIndex = cursor.getColumnIndex(DownloadManager.COLUMN_URI);
                            int localUrlIndex = 0;
                            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N){
                                // 7.0 或以上系统使用 COLUMN_LOCAL_URI 字段
                                localUrlIndex = cursor.getColumnIndex(DownloadManager.COLUMN_LOCAL_URI);
                            } else {
                                localUrlIndex = cursor.getColumnIndex(DownloadManager.COLUMN_LOCAL_FILENAME);
                            }
                            int totalBytesIndex = cursor.getColumnIndex(DownloadManager.COLUMN_TOTAL_SIZE_BYTES);
                            int downloadedBytesIndex = cursor.getColumnIndex(DownloadManager.COLUMN_BYTES_DOWNLOADED_SO_FAR);
                            int taskStatusIndex = cursor.getColumnIndex(DownloadManager.COLUMN_STATUS);

                            // 以下几个字段看名字就知道意思,就不细说了
                            long downloadId = cursor.getLong(downloadIdIndex);
                            String remoteUrl = cursor.getString(remoteUrlIndex);
                            String localUrl = cursor.getString(localUrlIndex);
                            long totalBytes = cursor.getLong(totalBytesIndex);
                            long downloadedBytes = cursor.getLong(downloadedBytesIndex);

                            // 任务状态包括如下几个可能的值
                            // DownloadManager.STATUS_PENDING, DownloadManager.STATUS_RUNNING, DownloadManager.STATUS_PAUSED, DownloadManager.STATUS_SUCCESSFUL, DownloadManager.STATUS_FAILED
                            int taskStatus = cursor.getInt(taskStatusIndex);

                            writeMessage(String.format(Locale.US, "downloadId:%d\nremoteUrl:%s\nlocalUrl:%s\ntotalBytes:%d\ndownloadedBytes:%d\ntaskStatus:%d",
                                    downloadId, remoteUrl, localUrl, totalBytes, downloadedBytes, taskStatus));
                        }
                        cursor.close();

                        if (Thread.interrupted()) {
                            break;
                        }
                        Thread.sleep(1000);
                    }
                } catch (InterruptedException e) {
                    Log.d(LOG_TAG, "线程被 interrupted 了");
                }
            }
        });
        mThread.setName("thread_DownloadManagerDemo1");
        mThread.setDaemon(true);
        mThread.start();
    }

    private void writeMessage(final String message) {
        runOnUiThread(new Runnable() {
            @Override
            public void run() {
                mTextView1.setText(message);
            }
        });
    }

    @Override
    public void onDestroy() {
        if (mThread != null) {
            mThread.interrupt();
            mThread = null;
        }

        unregisterReceiver();

        super.onDestroy();
    }



    // 通过监听广播,获取任务完成事件(也可以通过轮询任务的方式获取下载任务是否完成)和通知栏中的任务点击事件(这个只能通过广播来获取了)
    public void registerReceiver() {
        mDownloadReceiver = new DownloadReceiver();
        IntentFilter filter = new IntentFilter();
        filter.addAction(DownloadManager.ACTION_DOWNLOAD_COMPLETE); // 下载任务完成的广播
        filter.addAction(DownloadManager.ACTION_NOTIFICATION_CLICKED); // 在通知栏点击下载任务之后的广播
        this.registerReceiver(mDownloadReceiver, filter);
        mRegistered = true;
    }
    public void unregisterReceiver() {
        if (mRegistered) {
            try {
                this.unregisterReceiver(mDownloadReceiver);
                mRegistered = false;
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    public class DownloadReceiver extends BroadcastReceiver {
        @Override
        public void onReceive(Context context, Intent intent) {
            // 下载任务完成
            if (intent.getAction().equals(DownloadManager.ACTION_DOWNLOAD_COMPLETE)) {
                // 获取当前完成的下载任务的 downloadId
                long downloadId = intent.getLongExtra(DownloadManager.EXTRA_DOWNLOAD_ID, -1);

                // 通过 downloadId 获取本地文件地址
                DownloadManager downloadManager = (DownloadManager)context.getSystemService(Context.DOWNLOAD_SERVICE);
                Uri localUri = downloadManager.getUriForDownloadedFile(downloadId);
                if (localUri != null) {
                    // 弹出 apk 安装界面
                    Intent install = new Intent(Intent.ACTION_VIEW);
                    install.setDataAndType(localUri, "application/vnd.android.package-archive");
                    install.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
                    context.startActivity(install);
                } else {
                    Log.e(LOG_TAG, "download error");
                }
            }
            // 在通知栏点击下载任务之后,则跳转到系统下载界面
            else if (intent.getAction().equals(DownloadManager.ACTION_NOTIFICATION_CLICKED)) {
                Intent viewDownloadIntent = new Intent(DownloadManager.ACTION_VIEW_DOWNLOADS);
                viewDownloadIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
                context.startActivity(viewDownloadIntent);
            }
        }
    }
}

/layout/activity_service_downloadmanagerdemo1.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <Button
        android:id="@+id/button1"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="新增下载任务" />

    <Button
        android:id="@+id/button2"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="删除当前的下载任务" />

    <Button
        android:id="@+id/button3"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="跳转到系统下载界面" />

    <TextView
        android:id="@+id/textView1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />

</LinearLayout>

项目地址 https://github.com/webabcd/AndroidDemo
作者 webabcd

posted @ 2021-06-03 08:12  webabcd  阅读(284)  评论(0编辑  收藏  举报