网络通信——下载管理器DownloadManager——主动轮询当前的下载进度
===========================================================================================
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" package="com.example.myapplication"> <!-- 互联网 --> <uses-permission android:name="android.permission.INTERNET" /> <!-- 定位 --> <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" /> <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" /> <!-- 存储卡读写 --> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /> <!-- 下载时不提示通知栏 --> <uses-permission android:name="android.permission.DOWNLOAD_WITHOUT_NOTIFICATION" /> <!-- 下载时不提示通知栏 --> <uses-permission android:name="android.permission.DOWNLOAD_WITHOUT_NOTIFICATION" /> <!-- 相机 --> <uses-permission android:name="android.permission.CAMERA" /> <!-- 录音 --> <uses-permission android:name="android.permission.RECORD_AUDIO" /> <!-- 存储卡读写 --> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" /> <application android:allowBackup="true" android:dataExtractionRules="@xml/data_extraction_rules" android:fullBackupContent="@xml/backup_rules" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:roundIcon="@mipmap/ic_launcher_round" android:supportsRtl="true" android:theme="@style/Theme.MyApplication" android:usesCleartextTraffic="true" tools:targetApi="31"> <activity android:name=".MainActivity7" android:exported="false" /> <activity android:name=".MainActivity6" android:exported="false" /> <activity android:name=".MainActivity5" android:exported="false" android:label="@string/title_activity_main5" /> <activity android:name=".MainActivity4" android:exported="false" android:label="@string/title_activity_main4" /> <activity android:name=".MainActivity3" android:exported="false" android:label="@string/title_activity_main3" /> <activity android:name=".MainActivity2" android:exported="false" android:label="@string/title_activity_main2" /> <activity android:name=".MainActivity" android:exported="true"> <!-- android:theme="@style/AppCompatTheme" /> --> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> </application> </manifest>
布局:
<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.myapplication.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>
TextProgressCircle
package com.example.myapplication.widget; import android.annotation.SuppressLint; import android.content.Context; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.graphics.Paint.Style; import android.graphics.Rect; import android.graphics.RectF; import android.util.AttributeSet; import android.view.View; import com.example.myapplication.util.Utils; @SuppressLint("DrawAllocation") public class TextProgressCircle extends View { private Context mContext; // 声明一个上下文对象 private Paint mPaintBack = new Paint(); // 声明一个背景画笔对象 private Paint mPaintFore = new Paint(); // 声明一个前景画笔对象 private Paint mPaintText = new Paint(); // 声明一个文本画笔对象 private int mLineWidth; // 线条的宽度 private int mProgress = 0; // 进度值 public TextProgressCircle(Context context) { this(context, null); } public TextProgressCircle(Context context, AttributeSet attr) { super(context, attr); mContext = context; initPaint(); // 初始化画笔 } // 初始化画笔 private void initPaint() { mLineWidth = Utils.dip2px(mContext, 10); // 以下初始化背景画笔 mPaintBack.setColor(Color.LTGRAY); mPaintBack.setStrokeWidth(mLineWidth); mPaintBack.setStyle(Style.STROKE); // 以下初始化前景画笔 mPaintFore.setColor(Color.GREEN); mPaintFore.setStrokeWidth(mLineWidth); mPaintFore.setStyle(Style.STROKE); // 以下初始化文本画笔 mPaintText.setColor(Color.BLUE); mPaintText.setTextSize(Utils.dip2px(mContext, 50)); } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); int width = getMeasuredWidth(); // 获得视图测量后的宽度 int height = getMeasuredHeight(); // 获得视图测量后的高度 int diameter = Math.min(width, height); // 宽度和高度取较小的那个作为进度圆圈的直径 RectF rectF = new RectF((width - diameter) / 2 + mLineWidth, (height - diameter) / 2 + mLineWidth, (width + diameter) / 2 - mLineWidth, (height + diameter) / 2 - mLineWidth); // 在画布上绘制完整的背景圆圈 canvas.drawArc(rectF, 0, 360, false, mPaintBack); // 在画布上绘制规定进度的前景圆弧 canvas.drawArc(rectF, 0, mProgress * 360 / 100, false, mPaintFore); String text = mProgress + "%"; Rect rect = new Rect(); mPaintText.getTextBounds(text, 0, text.length(), rect); // 获得进度文字的矩形边界 int x = (width / 2) - rect.centerX(); // 计算进度文字左上角的横坐标 int y = (height / 2) - rect.centerY(); // 计算进度文字左上角的纵坐标 canvas.drawText(text, x, y, mPaintText); // 在画布上绘制进度文字 } // 设置进度值 public void setProgress(int progress) { mProgress = progress; invalidate(); // 立刻刷新视图 } }
MainActivity
package com.example.myapplication; import android.annotation.SuppressLint; import android.app.DownloadManager; 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.os.Looper; 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.myapplication.widget.TextProgressCircle; import java.util.HashMap; @SuppressLint({"SetTextI18n","DefaultLocale"}) public class MainActivity 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_main); 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(Looper.myLooper()); // 声明一个处理器对象 // 定义一个下载进度的刷新任务 private Runnable mRefresh = new Runnable() { @Override public void run() { boolean isFinish = false; DownloadManager.Query down_query = new DownloadManager.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); // 移除刷新任务 } }