Retrofit2实现图片文字上传

2023-05-16

目录

 

前言

一、效果展示

二、基本配置

三、代码实战

3.1、创建RetrofitManager和APIService

3.2、准备好选择的图片

3.3、开始构造参数

3.4、实现上传

附:UploadHelper.java源码


前言

距离上一篇文章到现在已经有将近半年的时间了,因为换了一座城市,到现在才算是刚刚熟悉起来吧,所以这段时间一直没能静下心来去总结,今天是周末,首先祝大家都能度过一个开心快乐的周末时光,我这个屌丝只能坐在家里码码文字了。今天总结一下关于在Android开发中使用Retrofit2实现类似于Web端表单文件上传的技术实现。

工作了也有将近三年的时间了,这期间关于这一块的实现方法也是换了几波,从最开始使用的基于Volley开发的MultipartRequest,到后来的OkHttp3,再到现在使用的Retrofit2,总结下来,其实都是大同小异。客户端这边主要就是构造请求参数,不同的参数需要指定其对应的参数类型,具体体现就是在构造参数时,需要指定Content-Type的类型(如果你平时观察过http请求,肯定会在Request Headers就是请求头中看到过它,我下面的日志截图里也有),然后构造完参数以后,剩下的就是和发送普通的Http请求一样了。服务端会有一个叫做enctype的东西,它的作用是告知服务器请求正文的MIME类型(和请求消息头的headers作用一样),然后服务端就根据对应的类型去解析,比如普通文字类型直接根据对应实体中的字段获取对应的值,文件类型的通过IO流读取写入文件,这样就完成了整个的上传流程。那么接下来,就来具体说说Android客户端Retrofit2的实现过程,至于服务端因为我懂得不多,所以就不在这里丢人了,简单点的大家可以尝试着写个Servlet去测试测试。

一、效果展示

这里录了一个gif效果图,可以很清晰的看到上传的结果,我的网速太快了,我都没看清:

不过确实是上传成功了,来看一下日志:

二、基本配置

既然是图文上传,这里重点讲的是上传,所以图片选择这一块我不打算多讲了,大家可以根据自己的实际业务去寻找对应的图片选择器(github上有很多),我这里使用的是MultiImageSelector,我把它的代码下载下来了,在本地项目中新建了一个Android Library,并给项目主Module添加上依赖,这样可以根据自己的需要进行定制,网络库这里肯定是选择Retrofit2.x版本了,直接添加对应的依赖到build.gradle文件中即可,结构如下图所示:

另外给大家推荐几款我觉得还不错的图片选择器的开源库,我这里因为没那么多需求,所以选了上面这个体积比较小的:

GalleryFinal:https://github.com/pengjianbo/GalleryFinal

PhotoPicker:https://github.com/donglua/PhotoPicker

Matisse(知乎开源的小清新款):https://github.com/zhihu/Matisse

三、代码实战

3.1、创建RetrofitManager和APIService

在最初的http协议中,没有定义上传文件的Method,为了实现这个功能,http协议组改造了post请求,添加了一种post规范,设定这种规范的Content-Type为multipart/form-data;boundary=bound,其中bound,其中{bound}是定义的分隔符,用于分割各项内容(文件,key-value对),不然服务器无法正确识别各项内容。post body里需要用到,尽量保证随机唯一。Retrofit是个网络代理框架,负责封装请求,然后把请求分发给http协议具体实现者-httpclient。retrofit默认的httpclient是okhttp。Retrofit会根据注解封装网络请求,待httpclient请求完成后,把原始response内容通过转化器(converter)转化成我们需要的对象(object)。那么Retrofit和okhttp怎么封装这些multipart/form-data上传数据呢?答案如下:

@retrofit2.http.Multipart: 标记一个请求是multipart/form-data类型,需要和@retrofit2.http.POST一同使用,并且方法参数必须是@retrofit2.http.Part注解。 

@retrofit2.http.Part: 代表Multipart里的一项数据,即用${bound}分隔的内容块。 
@retrofit2.http.PartMap:用于表单字段,默认接受的类型是Map<String,RequestBody>,可用于实现多文件上传。

了解了上面这些,就可以开始定义我们的ApiService了,注意这是个接口类,定义如下:

返回值类型这里Retrofit返回的是Call类型,如果你是使用的RxJava,这里直接返回Observable就行了,泛型这里传入的是服务端返回内容的一个实体类,这样最后直接根据实体类中的字段去进行结果的处理就OK了!

定义完了接口,接下来我们再去定义一个Retrofit的处理类,当然你也可以不定义,直接根据Retrofit的官方api在用到的地方调用也行,但是实际项目中肯定是要封装一个类,不然要写多少遍啊,不符合我们CV工程师的特点了!关于Retrofit的用法不再细说了,你既然能看到这里了,肯定不是个小白了,直接上代码:

package com.nari.yihui.api;

import android.support.annotation.NonNull;
import android.util.Log;

import com.nari.yihui.BaseApplication;
import com.nari.yihui.commonlib.ui.smartshow.SmartToast;
import com.nari.yihui.constants.ApiConstant;
import com.nari.yihui.helper.UserManager;
import com.nari.yihui.utils.CommonUtil;

import java.io.IOException;
import java.util.concurrent.TimeUnit;

import okhttp3.Interceptor;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import okhttp3.logging.HttpLoggingInterceptor;
import retrofit2.Retrofit;
import retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory;
import retrofit2.converter.gson.GsonConverterFactory;

/**
 * Created by 纪安奇 on 2018/6/11.
 * Retrofit的管理类
 */

@SuppressWarnings("FieldCanBeLocal")
public class RetrofitManager {

    //读超时时长,单位:毫秒
    public static final int READ_TIME_OUT = 15 * 1000;
    //写超时时长,单位:毫秒
    public static final int WRITE_TIME_OUT = 15 * 1000;
    //连接超时时长,单位:毫秒
    public static final int CONNECT_TIME_OUT = 15 * 1000;

    private static Retrofit mRetrofit; //Retrofit对象
    private static ApiService mApiService; //接口类实例对象
    private static OkHttpClient mClient; //OKHttp对象

    //获取ApiService对象
    public static ApiService getInstance() {
        if (CommonUtil.isNetworkAvailable(BaseApplication.getAppContext())) {
            //配置OkHttpClient
            OkHttpClient.Builder builder = new OkHttpClient.Builder();
            builder.readTimeout(READ_TIME_OUT, TimeUnit.MILLISECONDS)
                    .writeTimeout(WRITE_TIME_OUT, TimeUnit.MILLISECONDS)
                    .connectTimeout(CONNECT_TIME_OUT, TimeUnit.MILLISECONDS)
                    .addInterceptor(getLoggerInterceptor());
            //添加请求头Header
            if (UserManager.getInstance().getUser() != null) {
                builder.addInterceptor(new Interceptor() {
                    @Override
                    public Response intercept(@NonNull Chain chain) throws IOException {
                        Request request = chain.request()
                                .newBuilder()
                                .addHeader("Cookie", "sid=" + UserManager.getInstance().getUser().getObj().getSid())
                                .addHeader("Content-Type", "text/html;charset:utf-8")
                                .build();
                        return chain.proceed(request);
                    }
                });
            }
            mClient = builder.build();
            //配置Retrofit
            mRetrofit = new Retrofit.Builder()
                    .baseUrl(ApiConstant.baseUrl)
                    .addConverterFactory(GsonConverterFactory.create())
                    .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
                    .client(mClient)
                    .build();
            //创建ApiService对象
            mApiService = mRetrofit.create(ApiService.class);
        } else {
            SmartToast.showInCenter("当前网络连接失败");
        }
        return mApiService;
    }

    //设置日志拦截器,打印返回的数据
    private static HttpLoggingInterceptor getLoggerInterceptor() {
        //日志显示级别
        HttpLoggingInterceptor.Level level = HttpLoggingInterceptor.Level.BODY;
        //新建log拦截器
        HttpLoggingInterceptor loggingInterceptor = new HttpLoggingInterceptor(new HttpLoggingInterceptor.Logger() {
            @Override
            public void log(@NonNull String message) {
                Log.e("ApiUrl------>", message);
            }
        });
        loggingInterceptor.setLevel(level);
        return loggingInterceptor;
    }

}

3.2、准备好选择的图片

既然是图片文字上传,首先我们肯定得准备好图片和文字,上面已经大致说了怎么去实现图片的选择,这里不会详细的说,因为篇幅有限,所以只讲如何上传。首先来看一下服务端需要我们如何拼接参数去提交的呢?

可以看到输入的意见文字字段为content,它需要我们和其它两部分内容一起拼接成一个json串然后提交,图片部分需要我们按照key-value的形式进行提交,key就是file,value就是我们对应的图片,有几张图片就有几组file=?,然后这两大部分一起构造成最后需要提交的参数。在我的项目中我把最后需要上传的图片文件都存放到了一个File[]类型的数组中,那么根据服务端的要求也就会写成(file,file[0])这种成对的形式。

3.3、开始构造参数

有了上面的思路之后,就开始干吧。我在本地创建了一个UploadHelper类,初步的构想是将这个类采用单例模式的形式,然后对于参数这部分采用建造者模式,这样方便参数的添加,后面我会给出这个类的代码,这里就先截图了:

整个类采用单例模式:

对于参数部分,采用建造者模式进行构建:

我这里对于Map的value类型做了一个判断,文本和图片会根据对应的类型添加到Map中,是不是很方便,现在可以去真正的构造参数了,请看代码:

首先构造json串:

String jsonString = "{\"name\":\"" + name + "\",\"content\":\"" + content +
                            "\",\"province\":\"" +
                            BaseApplication.getSpUtil().getString(SPConstant.SP_PROVINCE) + "\"}";

接着构造图片,并且合并上面的json串:

helper = UploadHelper.getInstance();
        if (bmp.size() == 1) {
            helper.addParameter("file", files[0]);
        } else if (bmp.size() == 2) {
            helper.addParameter("file", files[0]);
            helper.addParameter("file", files[1]);
        } else if (bmp.size() == 3) {
            helper.addParameter("file", files[0]);
            helper.addParameter("file", files[1]);
            helper.addParameter("file", files[2]);
        }
        Map<String, RequestBody> params = helper.addParameter("param", json).builder();

3.4、实现上传

最后是发送我们的Http请求,实现上传的功能,这里根据处理结果做一些UI上的更新:

LoadingDialog.show(this, "正在上传,请稍候...", false);
        ApiConstant.baseUrl = ApiConstant.getBaseUrl(BaseUrlCode.FEEDBACK_UPLOAD);
        RetrofitManager.getInstance().uploadFeedback(params).enqueue(new Callback<CommonJsonModel>() {
            @Override
            public void onResponse(@NonNull Call<CommonJsonModel> call, @NonNull Response<CommonJsonModel> response) {
                if (response.isSuccessful()) {
                    LoadingDialog.canceled();
                    if (response.body() != null) {
                        if (response.body().getCode() == 1000) {
                            SmartToast.showInCenter("上传成功");
                            finish();
                            overridePendingTransition(R.anim.in_from_right, R.anim.finish_to_right);
                        } else if (response.body().getCode() == 2004 || response.body().getCode() == 2006) {
                            SmartToast.showInCenter("登录失效,请重新登录");
                            CommonUtil.loginOut(FeedBackActivity.this);
                        } else {
                            SmartToast.showInCenter(response.body().getMsg());
                        }
                    }
                }
            }

            @Override
            public void onFailure(@NonNull Call<CommonJsonModel> call, @NonNull Throwable t) {
                LoadingDialog.canceled();
                LogUtil.e("意见反馈上传图文异常------" + t.getMessage());
            }
        });

以上就是整个图文上传的实现过程,如果有不对的地方,还请告知,谢谢大家!

附:UploadHelper.java源码

package com.nari.yihui.helper;

import android.annotation.SuppressLint;
import android.app.Activity;
import android.content.Intent;
import android.graphics.Bitmap;
import android.net.Uri;
import android.os.Environment;
import android.provider.MediaStore;

import com.nari.yihui.commonlib.imageselector.MultiImageSelectorActivity;
import com.nari.yihui.constants.Constant;
import com.nari.yihui.utils.FileUtils;
import com.nari.yihui.utils.LogUtil;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import okhttp3.MediaType;
import okhttp3.RequestBody;

/**
 * 包名:com.nari.yihui.helper
 * 文件名:   UploadHelper
 * 创建时间:   2018/7/19 18:50
 * 作者:     纪安奇
 * 作用:图文上传工具类
 */
public class UploadHelper {
    private volatile static UploadHelper mInstance;
    public static Map<String, RequestBody> params;
    private Uri photoUri;

    private UploadHelper() {}

    //单例模式
    public static UploadHelper getInstance() {
        if (mInstance == null) {
            synchronized (UploadHelper.class) {
                if (mInstance == null)
                    mInstance = new UploadHelper();
                params = new HashMap<>();
            }
        }
        return mInstance;
    }

    //根据传进来的Object对象来判断是String还是File类型的参数
    public UploadHelper addParameter(String key, Object o) {
        RequestBody body = null;
        if (o instanceof String) {
            body = RequestBody.create(MediaType.parse("text/plain;charset=UTF-8"), (String) o);
        } else if (o instanceof File) {
            body = RequestBody.create(MediaType.parse("multipart/form-data;charset=UTF-8"), (File) o);
        }
        params.put(key, body);
        return this;
    }

    //建造者模式
    public Map<String, RequestBody> builder() {
        return params;
    }

    //清除参数
    public void clear(){
        params.clear();
    }

    //最终上传的Bitmap保存为File对象
    public void saveBitmapFile(List<Bitmap> mList,File[] files) {
        String root = Environment.getExternalStorageDirectory().toString();
        File myDir = new File(root + "/suggestionUpload");
        if (myDir.exists()) {
            myDir.delete();
        }
        myDir.mkdirs();
        for (int i = 0; i < mList.size(); i++) {
            files[i] = new File(myDir, "ims" + i + ".JPEG");
            try {
                if (files[i].exists()) {
                    files[i].delete();
                }
                files[i].createNewFile();
                FileOutputStream out = new FileOutputStream(files[i]);
                mList.get(i).compress(Bitmap.CompressFormat.JPEG, 100, out);
                out.flush();
                out.close();
                LogUtil.e("最终上传图片的路径------>" + files[i].getAbsolutePath());
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    //启用裁剪
    public void startPhotoZoom(Activity mContext, List<String> drr) {
        try {
            // 获取系统时间 然后将裁剪后的图片保存至指定的文件夹
            @SuppressLint("SimpleDateFormat")
            SimpleDateFormat sDateFormat = new SimpleDateFormat("yyyyMMddhhmmss");
            String address = sDateFormat.format(new Date());
            if (!FileUtils.isFileExist("")) {
                FileUtils.createSDDir("");
            }
            drr.add(FileUtils.SDPATH + address + ".JPEG");
            @SuppressLint("SdCardPath")
            Uri imageUri = Uri.parse("file:///sdcard/formats/" + address + ".JPEG");
            final Intent intent = new Intent("com.android.camera.action.CROP");
            // 照片URL地址
            intent.setDataAndType(photoUri, "image/*");
            intent.putExtra("crop", "true");
            intent.putExtra("aspectX", 1);
            intent.putExtra("aspectY", 1);
            intent.putExtra("outputX", 480);
            intent.putExtra("outputY", 480);
            // 输出路径
            intent.putExtra(MediaStore.EXTRA_OUTPUT, imageUri);
            // 输出格式
            intent.putExtra("outputFormat", Bitmap.CompressFormat.JPEG.toString());
            // 不启用人脸识别
            intent.putExtra("noFaceDetection", false);
            intent.putExtra("return-data", false);
            mContext.startActivityForResult(intent, Constant.REQUEST_CUTTING_CODE);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    //拍照
    public void takePhoto(Activity mContext) {
        try {
            Intent openCameraIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
            String sdcardState = Environment.getExternalStorageState();
            String sdcardPathDir = Environment.getExternalStorageDirectory().getPath() + "/myImage/";
            File file = null;
            if (Environment.MEDIA_MOUNTED.equals(sdcardState)) {
                // 有sd卡,是否有myImage文件夹
                File fileDir = new File(sdcardPathDir);
                if (!fileDir.exists()) {
                    fileDir.mkdirs();
                }
                // 是否有headImg文件
                file = new File(sdcardPathDir + System.currentTimeMillis() + ".JPEG");
            }
            if (file != null) {
                String path = file.getPath();
                photoUri = Uri.fromFile(file);
                openCameraIntent.putExtra(MediaStore.EXTRA_OUTPUT, photoUri);
                mContext.startActivityForResult(openCameraIntent, Constant.REQUEST_CAMERA_CODE);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    //从相册选择
    public void albmSelect(Activity mContext,int maxCanSelectNum,List<String> drr) {
        int selectedMode = MultiImageSelectorActivity.MODE_MULTI;
        int maxNum = maxCanSelectNum - drr.size();
        Intent intent = new Intent(mContext, MultiImageSelectorActivity.class);
        // 是否显示拍摄图片
        intent.putExtra(MultiImageSelectorActivity.EXTRA_SHOW_CAMERA, false);
        // 最大可选择图片数量
        intent.putExtra(MultiImageSelectorActivity.EXTRA_SELECT_COUNT, maxNum);
        // 选择模式
        intent.putExtra(MultiImageSelectorActivity.EXTRA_SELECT_MODE, selectedMode);
        mContext.startActivityForResult(intent, Constant.REQUEST_ALBUM_CODE);
    }

}

 

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Retrofit2实现图片文字上传 的相关文章

  • 树莓派——开启xrdp,换镜像,双击打开sh等等__一蓑烟雨任平生

    文章目录 前言一 树莓派是什么 xff1f 二 烧卡开机1 镜像用树莓派官网的镜像2 开机基本设置3 设置SSH和VNC4 查看本地IP5 设定固定IP 二 深度设置1 更换镜像源2 安装远程桌面3 修改sh脚本 Linux基本命令使用总结
  • 关于QImage无法设置透明色的问题

    明明在setPixelColor时 xff0c 设置的QColor为 xff08 254 254 254 0 xff09 xff0c 但是最后的结果却是白色 这是由于在设置QImage格式时 xff0c 该格式不支持透明色 xff0c 可以
  • 程序员必备的 40 个 VSCode 扩展

    在编写代码时 xff0c 一个富有成效的工作空间不仅仅是要找到合适的代码编辑器 开箱即用的VS Code是为开发人员制作的 根据2021年StackOverflow的调查 xff0c 71 06 的受访者将Visual Studio Cod
  • django debug toolbar 不显示

    python3 6 43 django2 0 xff0c 安装了django debug toolbar xff0c 根据官方文档配置之后却没看到调试栏 xff0c 增加了SHOW TOOLBAR CALLBACK字段后才显示 settin
  • 驳 GarbageMan 的《一个超复杂的简介递归》——对延迟计算的实验和思考

    这是一篇因骂战而起的博文 xff0c GarbageMan 在该文章回复中不仅对我进行了侮辱 xff0c 还涉及了我的母校 xff0c 特写此文用理性的分析和实验予以回击 在此也劝告 GarbageMan xff0c 没什么本事就别在那叫嚣
  • Debian_DNS服务搭建

    DNS是什么 xff1f DNS xff0c Domain Name System或者Domain Name Service xff08 域名系统或者余名服务 xff09 域名系统为Internet上的主机分配域名地址和IP地址 用户使用域
  • 解决MySQL无法插入中文数据问题(UTF-8编码)

    我花了好几个小时找过各种方法 xff0c 最终靠这个方法实现了中文插入 xff0c 我都快要喜极而泣了 xff0c 分享给大家 xff0c 真的很实用 一些关于查看和修改字符集的MySQL知识 xff1a 查看mysql的字符集 xff1a
  • PHP判断IP是否属于某个网段

    IP通过ip2long转换后可以进行比较 span class php span class hljs preprocessor lt php span span class hljs comment 判断IP是否在某个网络内 span c
  • phpstore 2016.1版激活方法

    激活请填写下面的地址吧 xff1a span class hljs label http span idea span class hljs preprocessor qinxi span 1992 span class hljs prep
  • Group by无法排序,但可以通过子查询实现

    lt pre name 61 34 code 34 class 61 34 sql 34 gt select from table where id in select max id from table where tid in 0 10
  • ext4文件系统恢复被删除的文件

    ext4magic工具 可以恢复出被rm f删除的文件 xff08 只要原始数据块未被新数据所覆盖 xff09 ext4magic device M d savedir 可参考 https sourceforge net projects
  • nginx反向代理经验整理

    location flag proxy pass http 127 0 0 1 19999 上面这段配置的反向代理实际访问路径是 usr www location flag proxy pass http 127 0 0 1 19999 上
  • Git一些使用注意事项

    Git创建远程空仓库时要注意加上 shared 61 group 即命令 xff1a 初始化远程空仓库 xff1a git init bare shared 61 group 给空仓库设置组共享 xff1a git config core
  • 使用WSL在Windows上安装Ubuntu

    1 清理环境 查看当前的wsl 状态 xff0c wsl list 可以列出当前系统中已安装的子系统 选择需要清理的系统 xff0c 然后用 wsl unregister lt DistributionName gt 即可完成卸载 将 ws
  • CentOS/Ubuntu/Debian常用版本更换国内源的方法

    Linux系统安装完后软件源一般都是国外服务器 xff0c 在国内特别慢 xff0c 这时候就需要更换国内的镜像源 如163 aliyun 还有各高校镜像源等 记住先备份原始源在更换源 xff0c 以防以后备用 一 centos 1 备份
  • C++ 的引用类型

    C 43 43 的引用类型 在翻旧文的时候 xff0c 发现这么一篇文章 xff1a 关于一道C 43 43 笔试题的纠结 xff0c 学计算机的伤不起啊 当时可能是觉得 Placement new 的语法1比较新鲜 xff0c 所以印象比
  • Debian 10(buster) 更换国内软件源

    今天安装了个debian10 xff0c 发现网上包括各大镜像网站提供的源地址都有点问题 xff0c 经测试 xff0c Debian 10 xff08 buster xff09 可用的国内软件源如下 xff08 阿里云源 xff09 xf
  • 华为交换机SNMP配置

    华为交换机SNMP配置 snmp服务配置 交换机内设置snmp一般只需要启动snmp服务和配置团体名称 xff0c 然后设置下版本就可以了 全局模式下 xff0c 配置命令 1 启动snmp服务 xff1a snmp agent 2 设置团
  • qt 菜单栏创建

    h文件内容 xff1a pragma once include lt QtWidgets QMainWindow gt include 34 ui QtWidgetsApplication2 h 34 include lt QLabel g
  • 立志学习编程的第一天 2021-05-06

    一直很遗憾大学没能报计算机专业 xff08 还一直觉得自己是被耽误的程序员来着 xff09 现在且用业余时间来试试 xff0c 且拭目以待吧 xff01 学习内容 xff1a Clojure for the Brave and True 工

随机推荐

  • 关于Clojure的Emacs配置 2021-05-07

    首先安装lein和Emacs xff1a https leiningen org http www gnu org software emacs 安装好Emacs之后 xff0c 找到 emacs d 对windows系统 xff0c 文件
  • Clojure基础语法学习笔记(一)

    首先推荐两个目前正在学的免费学习资源 xff1a Functional programming in Clojure Clojure for the Brave and True 都是英文的 xff0c 第一个是边学边练的形式 xff0c
  • matlab interp2函数详解

    切入正题 xff1a 下面是一个测试INTERP2 xff08 xff09 函数的MATLAB代码 function INTERP2 TEST I 61 2 3 6 8 3 5 1 7 4 2 9 6 6 8 1 3 X Y 61 mesh
  • word2016转mathtype

    用word2016自带公式编辑器给师兄敲了一堆公式之后 xff0c 发现毕业论文要求用mathtype 官网给的解决措施是 xff1a 1 http www mathtype cn wenti wordgongshi zhuanhuan h
  • 虚拟机中Linux扩容硬盘空间

    在初始安装CentOS时 xff0c 只给了硬盘空间30GB xff0c 现在因为需要 xff0c 所以需要扩容 1 关闭虚拟机中的系统 xff0c 打开虚拟机的设置 xff0c 修改磁盘空间到合适的大小 xff0c 再重启系统 2 打开终
  • Vue3 - setup语法糖

    与setup函数不同的是 xff0c 在script标签中添加setup 1 变量 方法不需要 return 出来 属性和方法也不用返回 xff0c 也不用写setup函数 xff0c 也不用写export default xff0c 甚至
  • Autoware 安装(源码)过程 与 踩坑记录(Ubuntu18.04)

    目录 autoware 源码安装 安装 ROS Melodic xff1a 设置软件源 设置密钥 xff1a 安装ROS xff1a rosdep xff1a 安装rosinstall 添加ROS环境变量 配置ROS环境变量 创建工作目录
  • 读论文:Feedback Network for Image Super-Resolution

    源码 xff1a https github com Paper99 SRFBN CVPR19 1 介绍 xff08 1 xff09 基于深度学习的方法的优势主要来自其两个关键因素 xff1a 深度和跳跃链接 第一 xff0c 保留更多的上下
  • Kolla-ansible部署OpenStack Train实践

    Kolla ansible部署OpenStack Train实践 前言系统环境设置安装pip和docker安装ansible安装kolla ansible配置文件修改执行部署 登录openstack写在最后部署过程中遇到的问题总结 前言 最
  • Windows 7 如何升级 PowerShell

    操作环境 xff1a Windows 7 旗舰版 Service Pack 1 x64 PowerShell 2 0 gt PowerShell 4 0 解决过程 xff1a 1 下载Windows6 1 KB2819745 x64 Mul
  • 最简单的算法:线性查找法

    目录 写在前面 一 什么是算法 二 线性查找法 2 1 实现线性查找法 2 2 思维拓展 使用泛型 2 3 自定义类测试泛型方法 2 4 循环不变量 三 复杂度分析 3 1 复杂度分析简介 3 2 常见的算法复杂度 四 算法性能测试 写在前
  • Android仿抖音主页效果实现

    目录 写在前面 一 准备工作 1 1 主页面布局 1 2 列表Item布局 1 3 列表Item适配器 二 自定义LayoutManager 三 实现播放 补充 xff1a 源码地址 xff1a https github com JArch
  • 数据结构基础之动态数组

    目录 前言 1 Java中的数组 2 实现动态数组 2 1 基本类结构设计 2 2 添加元素 2 3 查询 amp 修改元素 2 4 包含 amp 搜索 amp 删除 2 5 数组扩容 前言 今天我们来学习一下关于数据结构的一些基础知识 x
  • 数据结构基础之栈和队列

    目录 前言 1 栈 2 队列 2 1 实现队列 2 2 循环队列 前言 上一篇中我们介绍了数据结构基础中的 动态数组 xff0c 本篇我们继续来学习两种基本的数据结构 栈和队列 1 栈 特点 xff1a 栈也是一种线性结构 xff0c 相比
  • 数据结构基础之链表

    目录 前言 1 什么是链表 2 添加元素 3 虚拟头结点 4 查询 amp 修改元素 5 删除元素 附 xff1a 完整代码 前言 又到周末了 xff0c 修整了一天 xff0c 继续来写点东西吧 xff0c 今天 xff0c 我们来学习数
  • Android开发之局部广播的使用——LocalBroadcast

    一直以来都想着写一篇关于四大组件的文章 xff0c 可是一直懒一直都没去写 xff0c 今天终于抱起了电脑来敲一篇 这篇文章是关于安卓四大组件之一的广播的使用 xff0c 网上关于这方面的文章也是相当多 xff0c 我这里根据我一年多的工作
  • 基于OkHttp3封装网络请求框架

    前言 网络请求可以说是开发一款移动APP最核心的基础功能了 xff0c 通过实际工作中以及浏览了许多网络框架之后 xff0c 本篇在这里分享慕课一位老师基于OkHttp封装的一个轻量的网络框架 xff0c 至于为什么说它轻量 xff0c 因
  • c语言实现冒泡排序

    include lt stdio h gt void bubbleSort int arr int n int i j temp for i 61 0 i lt n 1 i 43 43 for j 61 0 j lt n i 1 j 43
  • 快速解决:ViewPager+多Fragment切换出现空白页面的问题

    前言 前两天在做项目的过程中遇到一个问题 xff0c 以前没遇到过 xff0c 所以这里做个笔记记录下来 xff0c 也是分享给同样遇到这个问题还尚未解决的猿友们 问题描述 xff1a 项目的主页框架是采用Activity 43 Fragm
  • Retrofit2实现图片文字上传

    目录 前言 一 效果展示 二 基本配置 三 代码实战 3 1 创建RetrofitManager和APIService 3 2 准备好选择的图片 3 3 开始构造参数 3 4 实现上传 附 xff1a UploadHelper java源码