网络通信——下载管理器DownloadManager——在通知栏显示下载进度
尽管直接使用GET方式也能下载网络文件,但这种方式存在以下缺点:
(1)无法断点续传,一旦中途失败,只能从头开始获取。
(2)不是真正意义上的下载操作,没法设置下载参数。
(3)下载过程中无法在界面上展示下载状态。
================================================================================================
布局:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" android:padding="5dp" > <LinearLayout android:layout_width="match_parent" android:layout_height="40dp" > <TextView android:layout_width="wrap_content" android:layout_height="match_parent" android:gravity="center" android:text="请选择要下载的安装包:" android:textColor="@color/black" android:textSize="17sp" /> <Spinner android:id="@+id/sp_apk_url" android:layout_width="0dp" android:layout_height="match_parent" android:layout_weight="1" android:spinnerMode="dialog" /> </LinearLayout> <TextView android:id="@+id/tv_apk_result" android:layout_width="match_parent" android:layout_height="wrap_content" android:textColor="@color/black" android:textSize="17sp" /> </LinearLayout>
ApkConstant
package com.example.myapplication.constant; import com.example.myapplication.R; public class ApkConstant { public static String[] NAME_ARRAY = { "爱奇艺", "酷狗音乐", "美图秀秀", "微信", "淘宝", "QQ" }; public static String[] PACKAGE_ARRAY = { "com.qiyi.video", "com.kugou.android", "com.mt.mtxx.mtxx", "com.tencent.mm", "com.taobao.taobao", "com.tencent.mobileqq" }; public static int[] ICON_ARRAY = { R.drawable.icon_aiqiyi, R.drawable.icon_kugou, R.drawable.icon_meitu, R.drawable.icon_weixin, R.drawable.icon_taobao, R.drawable.icon_qq }; public static String[] URL_ARRAY = { "http://103.231.68.98/McDonald/e/6880639/0/0/0/1632195714369/package_800120950.1632195714369'", "http://103.231.68.98/McDonald/e/6881873/0/0/0/1632195604939/package_10849.1632195604939'", "http://103.231.68.98/McDonald/e/6884026/0/0/0/1632196015129/package_9290.1632196015129'", "http://103.231.68.98/McDonald/e/6882606/0/0/0/1632197046375/package_17857.1632197046375'", "http://103.231.68.98/McDonald/e/6879467/0/0/0/1632194248559/package_1902.1632194248559'", "http://103.231.68.98/McDonald/e/6883576/0/0/0/1632196869079/package_2092.1632196869079'" }; }
DateUtil
package com.example.myapplication.util; import android.annotation.SuppressLint; import android.text.TextUtils; import java.text.SimpleDateFormat; import java.util.Date; @SuppressLint("SimpleDateFormat") public class DateUtil { // 获取当前的日期时间 public static String getNowDateTime() { SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmss"); return sdf.format(new Date()); } // 获取当前的时间 public static String getNowTime() { SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss"); return sdf.format(new Date()); } // 获取当前的时间(精确到毫秒) public static String getNowTimeDetail() { SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss.SSS"); return sdf.format(new Date()); } // 获取指定格式的日期时间 public static String getNowDateTime(String formatStr) { String format = formatStr; if (TextUtils.isEmpty(format)) { format = "yyyyMMddHHmmss"; } SimpleDateFormat sdf = new SimpleDateFormat(format); return sdf.format(new Date()); } public static String formatDate(long time) { Date date = new Date(time); SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); return sdf.format(date); } // 重新格式化日期字符串 public static String convertDateString(String strTime) { String newTime = strTime; try { SimpleDateFormat oldFormat = new SimpleDateFormat("yyyyMMddHHmmss"); Date date = oldFormat.parse(strTime); SimpleDateFormat newFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); newTime = newFormat.format(date); } catch (Exception e) { e.printStackTrace(); } return newTime; } }
MainActivity
package com.example.myapplication; import android.annotation.SuppressLint; import android.app.DownloadManager; import android.app.DownloadManager.Request; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.net.Uri; import android.os.Bundle; import android.os.Environment; import android.util.Log; import android.view.View; import android.widget.AdapterView; import android.widget.AdapterView.OnItemSelectedListener; import android.widget.ArrayAdapter; import android.widget.Spinner; import android.widget.TextView; import androidx.appcompat.app.AppCompatActivity; import com.example.myapplication.constant.ApkConstant; import com.example.myapplication.util.DateUtil; @SuppressLint({"SetTextI18n","DefaultLocale"}) public class MainActivity extends AppCompatActivity { private static final String TAG = "DownloadApkActivity"; private Spinner sp_apk_url; // 安装包链接的下拉框 private TextView tv_apk_result; private boolean isFirstSelect = true; // 是否首次选择 private DownloadManager mDownloadManager; // 声明一个下载管理器对象 private long mDownloadId = 0; // 下载编号 @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); tv_apk_result = findViewById(R.id.tv_apk_result); // 从系统服务中获取下载管理器 mDownloadManager = (DownloadManager) getSystemService(Context.DOWNLOAD_SERVICE); initApkSpinner(); // 初始化安装包链接的下拉框 } // 初始化安装包链接的下拉框 private void initApkSpinner() { ArrayAdapter<String> apkUrlAdapter = new ArrayAdapter<String>(this, R.layout.item_select, ApkConstant.NAME_ARRAY); sp_apk_url = findViewById(R.id.sp_apk_url); sp_apk_url.setPrompt("请选择要下载的安装包"); sp_apk_url.setAdapter(apkUrlAdapter); sp_apk_url.setOnItemSelectedListener(new ApkUrlSelectedListener()); sp_apk_url.setSelection(0); } class ApkUrlSelectedListener implements OnItemSelectedListener { public void onItemSelected(AdapterView<?> arg0, View arg1, int arg2, long arg3) { if (isFirstSelect) // 刚打开页面时不需要执行下载动作 { isFirstSelect = false; return; } startDownload(arg2); // 开始下载指定序号的apk文件 } public void onNothingSelected(AdapterView<?> arg0) {} } // 开始下载指定序号的apk文件 private void startDownload(int pos) { tv_apk_result.setText("正在下载" + ApkConstant.NAME_ARRAY[pos] + "的安装包,请到通知栏查看下载进度"); Uri uri = Uri.parse(ApkConstant.URL_ARRAY[pos]); // 根据下载地址构建一个Uri对象 Request down = new Request(uri); // 创建一个下载请求对象,指定从哪里下载文件 down.setTitle(ApkConstant.NAME_ARRAY[pos] + "下载信息"); // 设置任务标题 down.setDescription(ApkConstant.NAME_ARRAY[pos] + "安装包正在下载"); // 设置任务描述 // 设置允许下载的网络类型 down.setAllowedNetworkTypes(Request.NETWORK_MOBILE | Request.NETWORK_WIFI); // 设置通知栏在下载进行时与完成后都可见 down.setNotificationVisibility(Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED); // 设置下载文件在私有目录的保存路径。从Android10开始,只有保存到公共目录的才会在系统下载页面显示,保存到私有目录的不在系统下载页面显示 down.setDestinationInExternalFilesDir(this, Environment.DIRECTORY_DOWNLOADS, pos + ".apk"); // 设置下载文件在公共目录的保存路径。保存到公共目录需要申请存储卡的读写权限 //down.setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, pos + ".apk"); mDownloadId = mDownloadManager.enqueue(down); // 把下载请求对象加入到下载队列 } // 定义一个下载完成的广播接收器。用于接收下载完成事件 private class DownloadCompleteReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { if (intent.getAction().equals(DownloadManager.ACTION_DOWNLOAD_COMPLETE)) // 下载完毕 { // 从意图中解包获得下载编号 long downId = intent.getLongExtra(DownloadManager.EXTRA_DOWNLOAD_ID, -1); Log.d(TAG, "下载完成 id : " + downId + ", mDownloadId=" + mDownloadId); tv_apk_result.setVisibility(View.VISIBLE); String desc = String.format("%s 编号%d的下载任务已完成", DateUtil.getNowTime(), downId); tv_apk_result.setText(desc); // 显示下载任务的完成描述 } } } // 定义一个通知栏点击的广播接收器。用于接收下载通知栏的点击事件,在下载过程中有效,下载完成后失效 private class NotificationClickReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { if (intent.getAction().equals(DownloadManager.ACTION_NOTIFICATION_CLICKED)) // 点击了通知栏 { // 从意图中解包获得被点击通知的下载编号 long[] downIds = intent.getLongArrayExtra(DownloadManager.EXTRA_NOTIFICATION_CLICK_DOWNLOAD_IDS); for (long downId : downIds) { Log.d(TAG, "点击通知 id : " + downId + ", mDownloadId=" + mDownloadId); if (downId == mDownloadId) // 找到当前的下载任务 { String desc = String.format("%s 点击了编号%d的下载通知", DateUtil.getNowTime(), downId); tv_apk_result.setText(desc); // 显示下载任务的点击描述 } } } } } private DownloadCompleteReceiver completeReceiver; // 声明一个下载完成的广播接收器 private NotificationClickReceiver clickReceiver; // 声明一个通知栏点击的广播接收器 @Override public void onStart() { super.onStart(); completeReceiver = new DownloadCompleteReceiver(); // 创建一个下载完成的广播接收器 // 注册接收器,注册之后才能正常接收广播 registerReceiver(completeReceiver, new IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE)); clickReceiver = new NotificationClickReceiver(); // 创建一个通知栏点击的广播接收器 // 注册接收器,注册之后才能正常接收广播 registerReceiver(clickReceiver, new IntentFilter(DownloadManager.ACTION_NOTIFICATION_CLICKED)); } @Override public void onStop() { super.onStop(); unregisterReceiver(completeReceiver); // 注销下载完成的广播接收器 unregisterReceiver(clickReceiver); // 注销通知栏点击的广播接收器 } }