Android Studio App开发之下载管理器DownloadManager中显示、轮询下载进度、利用POST上传文件讲解及实战(附源码)

news2025/7/16 7:13:55

运行有问题或需要源码请点赞关注收藏后评论区留言~~~

一、在通知栏显示下载进度

利用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>

创作不易 觉得有帮助请点赞关注收藏~~~

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/8435.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

NAFNet(ECCV 2022)-图像修复论文解读

文章目录解决问题算法背景Simple BaselinePlain Block归一化激活函数Attention机制总结NAFNetSimpleGate替换GELUSCA替换CA总结实验应用RGB图像去噪图像去模糊RAW图像去噪结论论文: 《Simple Baselines for Image Restoration》github: https://github.com/megvii-research/NAF…

同事:这个页面的逻辑没什么能复用的,不抽组件也没什么影响吧?

前言 最近在维护同事的一个项目时&#xff0c;发现有不少单个vue文件一千余行&#xff0c;同一个文件上有倒计时、有输入信息的表单&#xff1b; 当时我就在想&#xff1a;是不是策划经常改需求或者排期紧急&#xff0c;所以没抽组件呢。 沟通过程 以下同事称为阿A 我&#…

【附源码】计算机毕业设计JAVA家庭理财管理系统

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat8.5 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; Springboot mybatis Maven Vue 等等组成&#xff0c;B/…

Java基础—Document类型的变化

Document类型的变化 Document类型的变化中唯一与命名空间无关的方法是importNode()。这个方法的用途是从一个文档中取得一个节点&#xff0c;然后将其导入到另一个文档&#xff0c;使其成为这个文档结构的一部分。需要注意的是&#xff0c;每个节点都有一个ownerDocument属性&…

G1D13-Apt论文阅读fraudgitKGbookrce33-36php环境搭建

一、APT论文 今天终于把6个模型论文和一篇综述读完了&#xff01;&#xff01;&#xff01; 今天主要读了一篇论文写了个总表。发现之前读的论文都忘了&#xff0c;所以 明天要复习一下模型&#xff0c;记录在文档中&#xff0c;并完善模型对比的总表&#xff0c;并且把代码下…

[附源码]java毕业设计基于web的建筑合同管理系统

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybatis Maven Vue 等等组成&#xff0c;B/S模式 M…

嵌入式FreeRTOS学习九,任务链表的构成,TICK时间中断和任务状态切换调度

一. tskTaskControlBlock 函数结构体 在tskTaskControlBlock 任务控制块结构体中&#xff0c;其中有任务状态链表和事件链表两个链表成员&#xff0c;首先介绍任务状态链表这个结构&#xff0c;这个链表通常用于管理不同状态的任务&#xff1b;通常&#xff0c;操作系统任务有…

CPU、内存、磁盘性能监控

CPU监控 网络由设备、服务器、路由器、交换机和其他网络组件组成。CPU 是网络中所有硬件设备的组成部分。它负责设备的稳定性和性能。企业严重依赖网络&#xff0c;企业硬件的处理能力决定了网络的容量。随着 CPU 功能和硬件的快速发展&#xff0c;组织必须规划其容量并监控其…

成功上岸,刚转行自学Python的小姑娘,每个月入1W+......

我是一名2020年毕业的本科生&#xff0c;大学学的专业是机械设计制造及其自动化。 在大学期间&#xff0c;觉得机械专业实在枯燥无味&#xff0c;没有一点点成就感&#xff0c;每天就是画图纸&#xff0c;测量零件&#xff0c;计算数据&#xff0c;一切都是纸上谈兵。但凡有因…

甲氧基PEG多巴胺DPA-mPEG,Dopamine-mPEG,PEG化的多巴胺具有良好的水溶性

英文名称&#xff1a;mPEG-DPA mPEG-Dopamine 中文名称&#xff1a;甲氧基-聚乙二醇-多巴胺 分子量&#xff1a;1k&#xff0c;2k&#xff0c;3.4k&#xff0c;5k&#xff0c;10k 产地&#xff1a;广州 品牌&#xff1a;为华生物 存储条件&#xff1a;≤-4℃低温干燥保存…

Netty-实验

Netty应用实例-群聊系统 实例要求&#xff1a; &#xff08;1&#xff09;编写一个Netty群聊系统&#xff0c;实现服务端和客户端之间的数据简单通讯&#xff08;非阻塞&#xff09; &#xff08;2&#xff09;实现多人群聊 &#xff08;3&#xff09;服务器端&#xff1a;可…

论文阅读笔记 | 三维目标检测——PointRCNN

如有错误&#xff0c;恳请指出。 文章目录1. 背景2. 网络结构2.1 Proposal Generation2.2 Proposal Refinement3. 实验部分3.1 kitti上的测评3.2 消融实验paper&#xff1a;《PointRCNN: 3D Object Proposal Generation and Detection from Point Cloud》文章比较复杂&#xff…

一文详解Redis企业版软件!

一、Redis企业版软件概述 Redis企业版软件&#xff08;Redis Enterprise&#xff09;是企业级的数据库软件&#xff0c;也是一款实时数据平台&#xff0c;为全球超过8500家知名企业提供实时数据服务。具有线性可扩展性、高可用性、持久性、备份和恢复、地理分布、分层内存访问…

WhatsApp群发系统-SendWS拓客系统功能后台介绍(五):WhatsApp筛号群发,群发超链

WhatsApp群发系统 基于WhatsApp进行群发功能&#xff0c;将品牌和产品推送给全世界各地的人们或者选择筛选好的用户&#xff0c;进行针对性的群发&#xff0c;提升了品牌和产品的影响力&#xff0c;让更多人了解认识品牌&#xff0c;帮助客户低成本实现WhatsApp营销精准拓客。…

windows和linux可以共用的端口连通性是否丢包测试工具paping

通常我们在系统无论是在windows还是linux&#xff0c;都会使用telnet命令来测试端口的连通性&#xff0c;但此命令只能测试是否通&#xff0c;无法测试是否有丢包或者是否有中断。paping这个工具就应用而生&#xff0c;它可以在多系统环境下进行像ping一样测试。 一、下载&…

【vscode】远程容器内开发python

一、环境 本人的远程开发环境&#xff1a; docker容器miniconda 常用的IDE&#xff1a; pyCharm专业版vsCodeRemote Development插件Python插件 由于pyCharm专业版要么花钱要么破解&#xff0c;我选择了vscode插件的方式&#xff0c;插件都是microsoft出品。 二、使用 服务…

记一道前端高难度面试题

目录 提问&#xff1a;如何让下面的这行代码成立 1.错误原因 2.思路 3.解题 4.小结 提问&#xff1a;如何让下面的这行代码成立 var [a,b] {a:1,b:2} 直接运行会报错&#xff0c;报错信息如下&#xff1a; Uncaught TypeError: {(intermediate value)(intermediate valu…

手柄零件的工艺设计

手柄零件的工艺设计 目录 一、零件的工艺分析及生产类型的确定 1.零件的作用-------------------------------------------------------------------------- 3 2.热处理-------------------------------------------------------------------------------- 3 3.零件的生产类型-…

架构师书籍推荐

讲实话&#xff0c;要看书只能看看架构师思维相关的数据&#xff0c;开拓一下思路就行&#xff0c;看看别人的看法和观念。 架构师需要积累的技术不要从书上来&#xff0c;去官网看他的说明书&#xff0c;一切纯讲技术类的书籍都有滞后性。 正在用的技术要时常关注一下他官网…

数据结构-图的基本概念

目录 完全图无向图有向图路径长度回路或环⭐⭐无向图-->连通图和连通分量⭐⭐有向图-强连通图和强连通分量完全图 无向图 无向图中每两个顶点之间都存在着一条边。 称为完全图 无向完全图包含n(n-1)/2条边。 有向图 有向图每两个顶点之间都存在着方向相反的两条边。 称…