运行有问题或需要源码请点赞关注收藏后评论区留言~~~
一、在通知栏显示下载进度
利用GET方式读取数据有很多缺点比如1:无法端点续传 一旦中途失败只能重新获取
2:不是真正意义上的下载操作 无法设置参数
3:下载过程中无法在界面上上展示下载状态
因为下载功能比较常用而且业务功能比较单一,所以Android专门提供了下载管理DownloadManager,方便开发者统一管理下载操作
主要步骤可分为以下两步
1:构建下载请求
2:管理下载队列
此外还有两种下载时间 开发者可通过监听对应的广播消息进行对应的处理
1:正在下载之时的通知栏点击事件
2:下载完成事件
下面是利用DownloadManager下载APK文件的实例 效果如下
在下拉框中选择要下载的安装包然后到通知栏中查看即可
代码如下
Java类
package com.example.chapter14; 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.chapter14.constant.ApkConstant; import com.example.chapter14.util.DateUtil; @SuppressLint({"SetTextI18n","DefaultLocale"}) public class DownloadApkActivity 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_download_apk); 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); // 注销通知栏点击的广播接收器 } }
XML文件
<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>
二、主动轮询当前的下载进度
如果APP自己也想了解当前的下载进度,就要调用管理器的query方法, 常用方法如下
setFilterById 根据编号过滤下载任务
setFilterByStatus 根据状态过滤下载任务
oredrBy 结果集安装指定字段排序
一旦把下载任务加入到下载队列中,就能调用下载管理器对象的query方法,获得任务信息结果集的游标对象
效果如下 可以在页面上动态展示网络图片的下载进度,并且自定义了圆形进度圈
此处连接真机测试效果更佳
代码如下
Java类
package com.example.chapter14; import android.annotation.SuppressLint; import android.app.DownloadManager; import android.app.DownloadManager.Query; import android.app.DownloadManager.Request; import android.content.Context; import android.database.Cursor; import android.net.Uri; import android.os.Bundle; import android.os.Environment; import android.os.Handler; import android.view.View; import android.widget.AdapterView; import android.widget.AdapterView.OnItemSelectedListener; import android.widget.ArrayAdapter; import android.widget.ImageView; import android.widget.Spinner; import android.widget.TextView; import androidx.appcompat.app.AppCompatActivity; import com.example.chapter14.widget.TextProgressCircle; import java.util.HashMap; @SuppressLint("DefaultLocale") public class DownloadImageActivity extends AppCompatActivity { private Spinner sp_image_url; // 图片链接的下拉框 private ImageView iv_image_url; private TextProgressCircle tpc_progress; // 定义一个文本进度圈对象 private TextView tv_image_result; private boolean isFirstSelect = true; // 是否首次选择 private Uri mImageUri; // 图片的路径对象 private DownloadManager mDownloadManager; // 声明一个下载管理器对象 private long mDownloadId = 0; // 当前任务的下载编号 private static HashMap<Integer, String> mStatusMap = new HashMap<Integer, String>(); // 下载状态映射 static { // 初始化下载状态映射 mStatusMap.put(DownloadManager.STATUS_PENDING, "挂起"); mStatusMap.put(DownloadManager.STATUS_RUNNING, "运行中"); mStatusMap.put(DownloadManager.STATUS_PAUSED, "暂停"); mStatusMap.put(DownloadManager.STATUS_SUCCESSFUL, "成功"); mStatusMap.put(DownloadManager.STATUS_FAILED, "失败"); } private String[] imageDescArray = { "洱海公园", "丹凤亭", "宛在堂", "满庭芳", "玉带桥", "眺望洱海", "洱海女儿", "海心亭", "洱海岸边", "烟波浩渺" }; private String[] imageUrlArray = { "https://b255.photo.store.qq.com/psb?/V11ZojBI0Zz6pV/nYJcslMIrGeDrujE5KZF2xBW8rjXMIVetZfrOAlSamM!/b/dPwxB5iaEQAA", "https://b255.photo.store.qq.com/psb?/V11ZojBI0Zz6pV/Adcl9XVS.RBED4D8shjceYHOhhR*6mcNyCcq24kJG2k!/b/dPwxB5iYEQAA", "https://b255.photo.store.qq.com/psb?/V11ZojBI0Zz6pV/bg*X6nT03YUReoJ97ked266WlWG3IzLjBdwHpKqkhYY!/b/dOg5CpjZEAAA", "https://b255.photo.store.qq.com/psb?/V11ZojBI0Zz6pV/JOPAKl9BO1wragCEIVzXLlHwj83qVhb8uNuHdmVRwP4!/b/dPwxB5iSEQAA", "https://b255.photo.store.qq.com/psb?/V11ZojBI0Zz6pV/7hHOgBEOBshH*7YAUx7RP0JzPuxRBD727mblw9TObhc!/b/dG4WB5i2EgAA", "https://b255.photo.store.qq.com/psb?/V11ZojBI0Zz6pV/m4Rjx20D9iFL0D5emuYqMMDji*HGQ2w2BWqv0zK*tRk!/b/dGp**5dYEAAA", "https://b255.photo.store.qq.com/psb?/V11ZojBI0Zz6pV/swfCMVl7Oefv8xgboV3OqkrahEs33KO7XwwH6hh7bnY!/b/dECE*5e9EgAA", "https://b256.photo.store.qq.com/psb?/V11ZojBI0Zz6pV/tpRlB0oozaD9PyBtCmf3pQ5QY0keJJxYGX93I7n5NwQ!/b/dAyVmZiVEQAA", "https://b256.photo.store.qq.com/psb?/V11ZojBI0Zz6pV/wMX2*LM6y.mBsFIYu8spAa7xXWUkPD.GHyazd.vMmYA!/b/dGYwoZjREQAA", "https://b255.photo.store.qq.com/psb?/V11ZojBI0Zz6pV/2vl1n0KmKTPCv944MVJgLxKAhMiM*sqajIFQ43c*9DM!/b/dPaoCJhuEQAA", }; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_download_image); iv_image_url = findViewById(R.id.iv_image_url); // 从布局文件中获取名叫tpc_progress的文本进度圈 tpc_progress = findViewById(R.id.tpc_progress); tv_image_result = findViewById(R.id.tv_image_result); // 从系统服务中获取下载管理器 mDownloadManager = (DownloadManager) getSystemService(Context.DOWNLOAD_SERVICE); initImageSpinner(); // 初始化下载图片的下拉框 } // 初始化下载图片的下拉框 private void initImageSpinner() { ArrayAdapter<String> imageUrlAdapter = new ArrayAdapter<String>(this, R.layout.item_select, imageDescArray); sp_image_url = findViewById(R.id.sp_image_url); sp_image_url.setPrompt("请选择要下载的图片"); sp_image_url.setAdapter(imageUrlAdapter); sp_image_url.setOnItemSelectedListener(new ImageUrlSelectedListener()); sp_image_url.setSelection(0); } class ImageUrlSelectedListener implements OnItemSelectedListener { public void onItemSelected(AdapterView<?> arg0, View arg1, int arg2, long arg3) { if (isFirstSelect) { // 刚打开页面时不需要执行下载动作 isFirstSelect = false; return; } startDownload(arg2); // 开始下载指定序号的图片文件 } public void onNothingSelected(AdapterView<?> arg0) {} } // 开始下载指定序号的图片文件 private void startDownload(int pos) { iv_image_url.setImageDrawable(null); // 清空图像视图 tpc_progress.setProgress(0); // 设置文本进度圈的当前进度为0,最大进度为100 tpc_progress.setVisibility(View.VISIBLE); // 显示文本进度圈 Uri uri = Uri.parse(imageUrlArray[pos]); // 根据图片的下载地址构建一个路径对象 Request down = new Request(uri); // 创建一个下载请求对象,指定从哪里下载文件 // 设置允许下载的网络类型 down.setAllowedNetworkTypes(Request.NETWORK_MOBILE | Request.NETWORK_WIFI); down.setNotificationVisibility(Request.VISIBILITY_HIDDEN); // 设置不在通知栏显示 // 设置下载文件在本地的保存路径 down.setDestinationInExternalFilesDir(this, Environment.DIRECTORY_DCIM, pos + ".jpg"); mDownloadId = mDownloadManager.enqueue(down); // 把下载请求对象加入到下载队列 mHandler.post(mRefresh); // 启动下载进度的刷新任务 } private Handler mHandler = new Handler(); // 声明一个处理器对象 // 定义一个下载进度的刷新任务 private Runnable mRefresh = new Runnable() { @Override public void run() { boolean isFinish = false; Query down_query = new Query(); // 创建一个下载查询对象,按照下载编号过滤 down_query.setFilterById(mDownloadId); // 设置下载查询对象的编号过滤器 // 向下载管理器查询下载任务,并返回查询结果集的游标 Cursor cursor = mDownloadManager.query(down_query); while (cursor.moveToNext()) { int uriIdx = cursor.getColumnIndex(DownloadManager.COLUMN_LOCAL_URI); int mediaIdx = cursor.getColumnIndex(DownloadManager.COLUMN_MEDIA_TYPE); int totalIdx = cursor.getColumnIndex(DownloadManager.COLUMN_TOTAL_SIZE_BYTES); int nowIdx = cursor.getColumnIndex(DownloadManager.COLUMN_BYTES_DOWNLOADED_SO_FAR); int statusIdx = cursor.getColumnIndex(DownloadManager.COLUMN_STATUS); if (cursor.getString(uriIdx) == null) { break; } // 根据总大小和已下载大小,计算当前的下载进度 int progress = (int) (100 * cursor.getLong(nowIdx) / cursor.getLong(totalIdx)); tpc_progress.setProgress(progress); // 设置文本进度圈的当前进度 if (progress == 100) { // 下载完毕 isFinish = true; } // 获得实际的下载状态 int status = isFinish ? DownloadManager.STATUS_SUCCESSFUL : cursor.getInt(statusIdx); mImageUri = Uri.parse(cursor.getString(uriIdx)); String desc = String.format("文件路径:%s\n媒体类型:%s\n文件总大小:%d字节" + "\n已下载大小:%d字节\n下载进度:%d%%\n下载状态:%s", mImageUri.toString(), cursor.getString(mediaIdx), cursor.getLong(totalIdx), cursor.getLong(nowIdx), progress, mStatusMap.get(status)); tv_image_result.setText(desc); // 显示图片下载任务的下载详情 } cursor.close(); // 关闭数据库游标 if (!isFinish) { // 下载未完成,则继续刷新 mHandler.postDelayed(this, 50); // 延迟50毫秒后再次启动刷新任务 } else { // 下载已完成,则显示图片 tpc_progress.setVisibility(View.INVISIBLE); // 隐藏文本进度圈 iv_image_url.setImageURI(mImageUri); // 设置图像视图的图片路径 } } }; @Override protected void onDestroy() { super.onDestroy(); mHandler.removeCallbacks(mRefresh); // 移除刷新任务 } }
XML文件
<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"> <ScrollView android:layout_width="match_parent" android:layout_height="wrap_content"> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical"> <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_image_url" android:layout_width="0dp" android:layout_height="match_parent" android:layout_weight="1" android:spinnerMode="dialog" /> </LinearLayout> <FrameLayout android:layout_width="match_parent" android:layout_height="wrap_content"> <ImageView android:id="@+id/iv_image_url" android:layout_width="match_parent" android:layout_height="match_parent" android:background="@drawable/downloading" android:scaleType="fitCenter" /> <com.example.chapter14.widget.TextProgressCircle android:id="@+id/tpc_progress" android:layout_width="match_parent" android:layout_height="350dp" android:layout_gravity="center" android:background="#99ffffff" android:visibility="invisible" /> </FrameLayout> <TextView android:id="@+id/tv_image_result" android:layout_width="match_parent" android:layout_height="wrap_content" android:textColor="@color/black" android:textSize="17sp" /> </LinearLayout> </ScrollView> </LinearLayout>
三、利用POST方式上传文件
对于社交类APP来说,上传文件是必不可少的功能,因此有必要要掌握文件上传的相关技术。
一样按照HTTP访问的POST流程,只是要采取multipart/form-data的方式分段传输,并加入分段传输的边界字符串即可
效果如下 连接真机测试效果更佳~~~
代码如下
Java类
package com.example.chapter14; import android.annotation.SuppressLint; import android.content.Intent; import android.net.Uri; import android.os.Bundle; import android.os.Environment; import android.text.TextUtils; import android.util.Log; import android.view.View; import android.widget.TextView; import androidx.appcompat.app.AppCompatActivity; import com.example.chapter14.task.UploadTask; import com.example.chapter14.task.UploadTask.OnUploadListener; import com.example.chapter14.constant.UrlConstant; import com.example.chapter14.util.DateUtil; import com.example.chapter14.util.FileUtil; @SuppressLint("SetTextI18n") public class HttpUploadActivity extends AppCompatActivity implements View.OnClickListener, OnUploadListener { private final static String TAG = "HttpUploadActivity"; private int CHOOSE_CODE = 3; // 只在相册挑选图片的请求码 private TextView tv_file_path; private String mFilePath; // 图片文件的路径 @Override protected void onCreate(Bundle selectdInstanceState) { super.onCreate(selectdInstanceState); setContentView(R.layout.activity_http_upload); tv_file_path = findViewById(R.id.tv_file_path); findViewById(R.id.btn_file_select).setOnClickListener(this); } @Override public void onClick(View v) { if (v.getId() == R.id.btn_file_select) { // 创建一个内容获取动作的意图(准备跳到系统相册) Intent albumIntent = new Intent(Intent.ACTION_GET_CONTENT); albumIntent.setType("image/*"); // 类型为图像 startActivityForResult(albumIntent, CHOOSE_CODE); // 打开系统相册 } } @Override protected void onActivityResult(int requestCode, int resultCode, Intent intent) { super.onActivityResult(requestCode, resultCode, intent); if (resultCode == RESULT_OK && requestCode == CHOOSE_CODE) { // 从相册回来 if (intent.getData() != null) { // 从相册选择一张照片 Uri uri = intent.getData(); // 获得已选择照片的路径对象 // 获得图片的临时保存路径 mFilePath = String.format("%s/%s.jpg", getExternalFilesDir(Environment.DIRECTORY_PICTURES), "photo_"+ DateUtil.getNowDateTime()); FileUtil.saveFileFromUri(this, uri, mFilePath); // 保存为临时文件 tv_file_path.setText("上传文件的路径为:" + mFilePath); UploadTask task = new UploadTask(); // 创建文件上传线程 task.setOnUploadListener(this); // 设置文件上传监听器 task.execute(mFilePath); // 把文件上传线程加入到处理队列 } } } // 在文件上传结束后触发 public void finishUpload(String result) { // 以下拼接文件上传的结果描述 String desc = String.format("上传文件的路径:%s\n上传结果:%s\n预计下载地址:%s%s", mFilePath, (TextUtils.isEmpty(result))?"失败":result, UrlConstant.REQUEST_URL, mFilePath.substring(mFilePath.lastIndexOf("/"))); tv_file_path.setText(desc); Log.d(TAG, desc); } }
XML文件
<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/btn_file_select" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="选择待上传的图片" android:textColor="@color/black" android:textSize="17sp" /> <TextView android:id="@+id/tv_file_path" android:layout_width="match_parent" android:layout_height="wrap_content" android:paddingLeft="5dp" android:textColor="@color/black" android:textSize="17sp" /> </LinearLayout>
创作不易 觉得有帮助请点赞关注收藏~~~