Android 更换用户头像(拍照、相册选取)

简介: Android 更换用户头像(拍照、相册选取)

Android 更换头像


运行效果图:


2021030515551067.gif


前言


 做Android应用开发,通常是有很多的功能组成,今天就来看一下这个用户头像更换的功能该怎么去写。相信很多的小伙伴都写过这个功能,因为作为一个APP来说这是很普遍的功能,基本都会有。只要你的APP有用户模块,就会有用户的个人信息的修改,比如常规的手机号码修改、地址修改、头像修改、昵称修改等。这里面技术含量高一点的就是头像修改了,进入正题吧。


正文


 这里我还是新建一个项目来做这个头像修改的功能,这样对于没有接触过这个功能的朋友更友好,这也是我一直以来的写作风格,不要嫌我啰嗦啊。


一、新建项目


创建一个名为ChangeAvatarDemo的项目


20210303160954348.png


项目创建好之后,先想清楚你的这个功能需要什么,换头像常规肯定是上传到后台去,那么你肯定是要有网络权限的,其次如果你的网络请求地址是http开头的话,而在Android9.0及以上版本则要配置http访问许可才行,之后你是否会用到一些第三方框架,比如圆形头像,圆角头像、图片加载、动态权限请求。


二、配置项目


基于这些考虑,首先打开app模块下的build.gradle,在dependencies闭包下添加如下依赖:


  //权限请求框架
    implementation 'com.tbruyelle.rxpermissions2:rxpermissions:0.9.4@aar'
    implementation 'io.reactivex.rxjava2:rxandroid:2.0.2'
    implementation "io.reactivex.rxjava2:rxjava:2.0.0"
    //热门强大的图片加载器
    implementation 'com.github.bumptech.glide:glide:4.11.0'
    annotationProcessor 'com.github.bumptech.glide:compiler:4.11.0'
    //Google Material控件,以及迁移到AndroidX下一些控件的依赖
    implementation 'com.google.android.material:material:1.2.0'


然后在android闭包下指定JDK版本为1.8


  compileOptions {
        sourceCompatibility = 1.8
        targetCompatibility = 1.8
    }


添加位置如下图所示:


20210305144826689.png


然后点击右上角Sync进行同步,到这里gradle就配置完成了。


然后打开AndroidManifest.xml,在里面配置如下权限:


  <!--网络权限-->
    <uses-permission android:name="android.permission.INTERNET"/>
    <!--相机权限-->
    <uses-permission android:name="android.permission.CAMERA"/>
    <!-- 读写文件权限 -->
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />


添加位置如下图所示:


20210305145318707.png


这里还有一个要适配,那就是在Android10.0时增加了作用域存储,因此我这个不用这个作用域存储,所以在你的application标签下增加这样一句话


  android:requestLegacyExternalStorage="true"

20210309194831707.png


三、布局、样式改动


首先打开项目中的styles.xml,在里面增加一个样式:


  <!-- 圆形图片 -->
    <style name="circleImageStyle">
        <item name="cornerFamily">rounded</item>
        <item name="cornerSize">50%</item>
    </style>


这是一个弹窗的布局文件,里面提供你选择拍照、打开相册、取消。而且从命名来看,这是一个底部弹窗。所以需要一个地方去触发这个弹窗从屏幕底部出现。下面打开activity_main.xml,修改代码后如下所示:


<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:gravity="center"
    android:orientation="vertical"
    tools:context=".MainActivity">
    <!--圆形图片-->
    <com.google.android.material.imageview.ShapeableImageView
        android:id="@+id/iv_head"
        android:layout_width="200dp"
        android:layout_height="200dp"
        android:onClick="changeAvatar"
        android:src="@mipmap/ic_launcher"
        app:shapeAppearanceOverlay="@style/circleImageStyle" />
</LinearLayout>


这里我用了一个ShapeableImageView,这是material库里面的一个控件,你只要知道它比普通的ImageView要🐮🍺就可以了,想要详细了解的看看Android Material UI控件之ShapeableImageView。


这里我指定了app:shapeAppearanceOverlay="@style/circleImageStyle",也就是说它变成了一个圆形图片控件。


布局就写完了。


四、权限请求


进入到MainActivity,先声明变量


  //权限请求
    private RxPermissions rxPermissions;


先写一个Toast提示方法。


  /**
     * Toast提示
     *
     * @param msg
     */
    private void showMsg(String msg) {
        Toast.makeText(this, msg, Toast.LENGTH_SHORT).show();
    }


然后可以写一个checkVersion()方法,用于检查当前的Android版本,并且给你提示。


  /**
     * 检查版本
     */
    private void checkVersion() {
        //Android6.0及以上版本
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
            //如果你是在Fragment中,则把this换成getActivity()
            rxPermissions = new RxPermissions(this);
            //权限请求
            rxPermissions.request(Manifest.permission.CAMERA, Manifest.permission.WRITE_EXTERNAL_STORAGE)
                    .subscribe(granted -> {
                        if (granted) {//申请成功
                            showMsg("已获取权限");
                        } else {//申请失败
                            showMsg("权限未开启");
                        }
                    });
        } else {
            //Android6.0以下
            showMsg("无需请求动态权限");
        }
    }


然后你要在onCreate()中调用checkVersion()。使用户一进入这个页面就进行检查版本和授权。


不过这里还要防范一个问题,那就是假如用户没有通过权限。再创建一个变量


  //是否拥有权限
    private boolean hasPermissions = false;


然后赋值


20210305151528222.png


只有权限全部通过授权之后才会是true。


五、底部弹窗显示


如果我没有猜错的话,你的activity_main.xml中还有一个地方报错。


不过不要担心,先增加两个变量

  //底部弹窗
    private BottomSheetDialog bottomSheetDialog;
    //弹窗视图
    private View bottomView;


然后新增一个changeAvatar()方法,里面的代码如下:


   /**
     * 更换头像
     *
     * @param view
     */
    public void changeAvatar(View view) {
        bottomSheetDialog = new BottomSheetDialog(this);
        bottomView = getLayoutInflater().inflate(R.layout.dialog_bottom, null);
        bottomSheetDialog.setContentView(bottomView);
        bottomSheetDialog.getWindow().findViewById(R.id.design_bottom_sheet).setBackgroundColor(Color.TRANSPARENT);
        TextView tvTakePictures = bottomView.findViewById(R.id.tv_take_pictures);
        TextView tvOpenAlbum = bottomView.findViewById(R.id.tv_open_album);
        TextView tvCancel = bottomView.findViewById(R.id.tv_cancel);
        //拍照
        tvTakePictures.setOnClickListener(v -> {
            showMsg("拍照");
            bottomSheetDialog.cancel();
        });
        //打开相册
        tvOpenAlbum.setOnClickListener(v -> {
            showMsg("打开相册");
            bottomSheetDialog.cancel();
        });
        //取消
        tvCancel.setOnClickListener(v -> {
            bottomSheetDialog.cancel();
        });
        bottomSheetDialog.show();
    }


这个方法就是配置弹窗的视图,并且绑定视图中的控件,设置点击事件。现在你再去看你的activity_main.xml布局,就不会报错了。并且如果你现在运行的话,当你点击图片是底部会出现弹窗。然后点击弹窗中的三个控件,或者弹窗外阴影区域都会关闭弹窗。


六、工具类


这里我会添加两个工具类,用来协助我们开发。


在com.llw.changeavatar下新建一个utils包,在这个包下新建一个BitmapUtils类,里面的代码如下:


package com.llw.changeavatar.utils;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.LinearGradient;
import android.graphics.Paint;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffXfermode;
import android.graphics.RadialGradient;
import android.graphics.RectF;
import android.graphics.Shader;
import android.util.Base64;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
/**
 * Bitmap工具类
 */
public class BitmapUtils {
    /**
     * bitmap转为base64
     *
     * @param bitmap
     * @return
     */
    public static String bitmapToBase64(Bitmap bitmap) {
        String result = null;
        ByteArrayOutputStream baos = null;
        try {
            if (bitmap != null) {
                baos = new ByteArrayOutputStream();
                bitmap.compress(Bitmap.CompressFormat.JPEG, 100, baos);
                baos.flush();
                baos.close();
                byte[] bitmapBytes = baos.toByteArray();
                result = Base64.encodeToString(bitmapBytes, Base64.DEFAULT);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                if (baos != null) {
                    baos.flush();
                    baos.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return result;
    }
    /**
     * base64转为bitmap
     *
     * @param base64Data
     * @return
     */
    public static Bitmap base64ToBitmap(String base64Data) {
        byte[] bytes = Base64.decode(base64Data, Base64.DEFAULT);
        return BitmapFactory.decodeByteArray(bytes, 0, bytes.length);
    }
    /**
     * url转bitmap
     * @param url
     * @return
     */
    public static Bitmap urlToBitmap(final String url){
        final Bitmap[] bitmap = {null};
        new Thread(() -> {
            URL imageurl = null;
            try {
                imageurl = new URL(url);
            } catch (MalformedURLException e) {
                e.printStackTrace();
            }
            try {
                HttpURLConnection conn = (HttpURLConnection)imageurl.openConnection();
                conn.setDoInput(true);
                conn.connect();
                InputStream is = conn.getInputStream();
                bitmap[0] = BitmapFactory.decodeStream(is);
                is.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }).start();
        return bitmap[0];
    }
}


然后再新建一个CameraUtils类,代码如下;


package com.llw.changeavatar.utils;
import android.annotation.TargetApi;
import android.content.ContentUris;
import android.content.ContentValues;
import android.content.Context;
import android.content.Intent;
import android.database.Cursor;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Matrix;
import android.media.ExifInterface;
import android.net.Uri;
import android.os.Build;
import android.os.Environment;
import android.provider.DocumentsContract;
import android.provider.MediaStore;
import android.util.Log;
import android.widget.ImageView;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
/**
 * 相机、相册工具类
 */
public class CameraUtils {
    /**
     * 相机Intent
     * @param context
     * @param outputImagePath
     * @return
     */
    public static Intent getTakePhotoIntent(Context context, File outputImagePath) {
        // 激活相机
        Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
        // 判断存储卡是否可以用,可用进行存储
        if (hasSdcard()) {
            if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) {
                // 从文件中创建uri
                Uri uri = Uri.fromFile(outputImagePath);
                intent.putExtra(MediaStore.EXTRA_OUTPUT, uri);
            } else {
                //兼容android7.0 使用共享文件的形式
                ContentValues contentValues = new ContentValues(1);
                contentValues.put(MediaStore.Images.Media.DATA, outputImagePath.getAbsolutePath());
                Uri uri = context.getApplicationContext().getContentResolver().insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, contentValues);
                intent.putExtra(MediaStore.EXTRA_OUTPUT, uri);
            }
        }
        return intent;
    }
    /**
     * 相册Intent
     * @return
     */
    public static Intent getSelectPhotoIntent() {
        Intent intent = new Intent("android.intent.action.GET_CONTENT");
        intent.setType("image/*");
        return intent;
    }
    /**
     * 判断sdcard是否被挂载
     */
    public static boolean hasSdcard() {
        return Environment.getExternalStorageState().equals(
                Environment.MEDIA_MOUNTED);
    }
    /**
     * 4.4及以上系统处理图片的方法
     */
    @TargetApi(Build.VERSION_CODES.KITKAT)
    public static String getImageOnKitKatPath(Intent data, Context context) {
        String imagePath = null;
        Uri uri = data.getData();
        Log.d("uri=intent.getData :", "" + uri);
        if (DocumentsContract.isDocumentUri(context, uri)) {
            //数据表里指定的行
            String docId = DocumentsContract.getDocumentId(uri);
            Log.d("getDocumentId(uri) :", "" + docId);
            Log.d("uri.getAuthority() :", "" + uri.getAuthority());
            if ("com.android.providers.media.documents".equals(uri.getAuthority())) {
                String id = docId.split(":")[1];
                String selection = MediaStore.Images.Media._ID + "=" + id;
                imagePath = getImagePath(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, selection, context);
            } else if ("com.android.providers.downloads.documents".equals(uri.getAuthority())) {
                Uri contentUri = ContentUris.withAppendedId(Uri.parse("content://downloads/public_downloads"), Long.valueOf(docId));
                imagePath = getImagePath(contentUri, null, context);
            }
        } else if ("content".equalsIgnoreCase(uri.getScheme())) {
            imagePath = getImagePath(uri, null, context);
        }
        return imagePath;
    }
    /**
     * 通过uri和selection来获取真实的图片路径,从相册获取图片时要用
     */
    public static String getImagePath(Uri uri, String selection, Context context) {
        String path = null;
        Cursor cursor = context.getContentResolver().query(uri, null, selection, null, null);
        if (cursor != null) {
            if (cursor.moveToFirst()) {
                path = cursor.getString(cursor.getColumnIndex(MediaStore.Images.Media.DATA));
            }
            cursor.close();
        }
        return path;
    }
    /**
     * 更改图片显示角度
     * @param filepath
     * @param orc_bitmap
     * @param iv
     */
    public static void ImgUpdateDirection(String filepath, Bitmap orc_bitmap, ImageView iv) {
        //图片旋转的角度
        int digree = 0;
        //根据图片的filepath获取到一个ExifInterface的对象
        ExifInterface exif = null;
        try {
            exif = new ExifInterface(filepath);
            if (exif != null) {
                // 读取图片中相机方向信息
                int ori = exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_UNDEFINED);
                // 计算旋转角度
                switch (ori) {
                    case ExifInterface.ORIENTATION_ROTATE_90:
                        digree = 90;
                        break;
                    case ExifInterface.ORIENTATION_ROTATE_180:
                        digree = 180;
                        break;
                    case ExifInterface.ORIENTATION_ROTATE_270:
                        digree = 270;
                        break;
                    default:
                        digree = 0;
                        break;
                }
            }
            //如果图片不为0
            if (digree != 0) {
                // 旋转图片
                Matrix m = new Matrix();
                m.postRotate(digree);
                orc_bitmap = Bitmap.createBitmap(orc_bitmap, 0, 0, orc_bitmap.getWidth(),
                        orc_bitmap.getHeight(), m, true);
            }
            if (orc_bitmap != null) {
                iv.setImageBitmap(orc_bitmap);
            }
        } catch (IOException e) {
            e.printStackTrace();
            exif = null;
        }
    }
    /**
     * 4.4以下系统处理图片的方法
     */
    public static String getImageBeforeKitKatPath(Intent data, Context context) {
        Uri uri = data.getData();
        String imagePath = getImagePath(uri, null, context);
        return imagePath;
    }
    /**
     * 比例压缩
     * @param image
     * @return
     */
    public static Bitmap compression(Bitmap image) {
        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
        image.compress(Bitmap.CompressFormat.JPEG, 100, outputStream);
        //判断如果图片大于1M,进行压缩避免在生成图片(BitmapFactory.decodeStream)时溢出
        if (outputStream.toByteArray().length / 1024 > 1024) {
            //重置outputStream即清空outputStream
            outputStream.reset();
            //这里压缩50%,把压缩后的数据存放到baos中
            image.compress(Bitmap.CompressFormat.JPEG, 50, outputStream);
        }
        ByteArrayInputStream inputStream = new ByteArrayInputStream(outputStream.toByteArray());
        BitmapFactory.Options options = new BitmapFactory.Options();
        //开始读入图片,此时把options.inJustDecodeBounds 设回true了
        options.inJustDecodeBounds = true;
        Bitmap bitmap = BitmapFactory.decodeStream(inputStream, null, options);
        options.inJustDecodeBounds = false;
        int outWidth = options.outWidth;
        int outHeight = options.outHeight;
        //现在主流手机比较多是800*480分辨率,所以高和宽我们设置为
        float height = 800f;//这里设置高度为800f
        float width = 480f;//这里设置宽度为480f
        //缩放比。由于是固定比例缩放,只用高或者宽其中一个数据进行计算即可
        int zoomRatio = 1;//be=1表示不缩放
        if (outWidth > outHeight && outWidth > width) {//如果宽度大的话根据宽度固定大小缩放
            zoomRatio = (int) (options.outWidth / width);
        } else if (outWidth < outHeight && outHeight > height) {//如果高度高的话根据宽度固定大小缩放
            zoomRatio = (int) (options.outHeight / height);
        }
        if (zoomRatio <= 0) {
            zoomRatio = 1;
        }
        options.inSampleSize = zoomRatio;//设置缩放比例
        options.inPreferredConfig = Bitmap.Config.RGB_565;//降低图片从ARGB888到RGB565
        //重新读入图片,注意此时已经把options.inJustDecodeBounds 设回false了
        inputStream = new ByteArrayInputStream(outputStream.toByteArray());
        //压缩好比例大小后再进行质量压缩
        bitmap = BitmapFactory.decodeStream(inputStream, null, options);
        return bitmap;
    }
}


工具类里面都有注释,我就不多说了。


七、打开相机、相册


声明变量


  //存储拍完照后的图片
    private File outputImagePath;
    //启动相机标识
    public static final int TAKE_PHOTO = 1;
    //启动相册标识
    public static final int SELECT_PHOTO = 2;


拍照方法:


  /**
     * 拍照
     */
    private void takePhoto() {
        if (!hasPermissions) {
            showMsg("未获取到权限");
            checkVersion();
            return;
        }
        SimpleDateFormat timeStampFormat = new SimpleDateFormat(
                "yyyy_MM_dd_HH_mm_ss");
        String filename = timeStampFormat.format(new Date());
        outputImagePath = new File(getExternalCacheDir(),
                filename + ".jpg");
        Intent takePhotoIntent = CameraUtils.getTakePhotoIntent(this, outputImagePath);
        // 开启一个带有返回值的Activity,请求码为TAKE_PHOTO
        startActivityForResult(takePhotoIntent, TAKE_PHOTO);
    }


打开相册方法:


  /**
     * 打开相册
     */
    private void openAlbum() {
        if (!hasPermissions) {
            showMsg("未获取到权限");
            checkVersion();
            return;
        }
        startActivityForResult(CameraUtils.getSelectPhotoIntent(), SELECT_PHOTO);
    }


方法写完了记得要调用才行,如下图在changeAvatar中调用。


20210305154606394.png


现在你运行,你就会发现会跳转到相机和打开相册。但是你还是要回来的。


八、页面返回显示图片


先声明如下变量


  //图片控件
    private ShapeableImageView ivHead;
    //Base64
    private String base64Pic;
    //拍照和相册获取图片的Bitmap
    private Bitmap orc_bitmap;
    //Glide请求图片选项配置
    private RequestOptions requestOptions = RequestOptions.circleCropTransform()
            .diskCacheStrategy(DiskCacheStrategy.NONE)//不做磁盘缓存
            .skipMemoryCache(true);//不做内存缓存


然后别忘了绑定图片控件


20210305160714584.png


下面重写onActivityResult方法,获取返回的路径。


  /**
     * 返回到Activity
     * @param requestCode
     * @param resultCode
     * @param data
     */
    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        switch (requestCode) {
            //拍照后返回
            case TAKE_PHOTO:
                if (resultCode == RESULT_OK) {
                  //显示图片
                    displayImage(outputImagePath.getAbsolutePath());
                }
                break;
            //打开相册后返回
            case SELECT_PHOTO:
                if (resultCode == RESULT_OK) {
                    String imagePath = null;
                    //判断手机系统版本号
                    if (Build.VERSION.SDK_INT > Build.VERSION_CODES.KITKAT) {
                        //4.4及以上系统使用这个方法处理图片
                        imagePath = CameraUtils.getImageOnKitKatPath(data, this);
                    } else {
                        imagePath = CameraUtils.getImageBeforeKitKatPath(data, this);
                    }
                    //显示图片
                    displayImage(imagePath);
                }
                break;
            default:
                break;
        }
    }


这里调用了displayImage方法,代码如下:


  /**
     * 通过图片路径显示图片
     */
    private void displayImage(String imagePath) {
        if (!TextUtils.isEmpty(imagePath)) {
            //显示图片
            Glide.with(this).load(imagePath).apply(requestOptions).into(ivHead);
            //压缩图片
            orc_bitmap = CameraUtils.compression(BitmapFactory.decodeFile(imagePath));
            //转Base64
            base64Pic = BitmapUtils.bitmapToBase64(orc_bitmap);
        } else {
            showMsg("图片获取失败");
        }
    }


那么到这里代码就写完了,下面运行一下吧。


2021030515551067.gif


九、本地缓存


如果你目前还没有与后台进行交互的话,那要让你的图片持久显示,那么你可以用到缓存。在utils包下新建一个SPUtils类,代码如下:


package com.llw.changeavatar.util;
import android.content.Context;
import android.content.SharedPreferences;
/**
 * sharepref工具类
 */
public class SPUtils {
    private static final String NAME="config";
    public static void putBoolean(String key, boolean value, Context context) {
        SharedPreferences sp = context.getSharedPreferences(NAME,
                Context.MODE_PRIVATE);
        sp.edit().putBoolean(key, value).commit();
    }
    public static boolean getBoolean(String key, boolean defValue, Context context) {
        SharedPreferences sp = context.getSharedPreferences(NAME,
                Context.MODE_PRIVATE);
        return sp.getBoolean(key, defValue);
    }
    public static void putString(String key, String value, Context context) {
        SharedPreferences sp = context.getSharedPreferences(NAME,
                Context.MODE_PRIVATE);
        sp.edit().putString(key, value).commit();
    }
    public static String getString(String key, String defValue, Context context) {
        if(context!=null){
            SharedPreferences sp = context.getSharedPreferences(NAME,
                    Context.MODE_PRIVATE);
            return sp.getString(key, defValue);
        }
        return "";
    }
    public static void putInt(String key, int value, Context context) {
        SharedPreferences sp = context.getSharedPreferences(NAME,
                Context.MODE_PRIVATE);
        sp.edit().putInt(key, value).commit();
    }
    public static int getInt(String key, int defValue, Context context) {
        SharedPreferences sp = context.getSharedPreferences(NAME,
                Context.MODE_PRIVATE);
        return sp.getInt(key, defValue);
    }
    public static void remove(String key, Context context) {
        SharedPreferences sp = context.getSharedPreferences(NAME,
                Context.MODE_PRIVATE);
        sp.edit().remove(key).commit();
    }
}


刚才的地址这个类提供了缓存的存取方法,支持三种数据类型,String、Int、boolean。

而刚才的图片路径是String类型的,于是你可以这么写。


20210305160324336.png


在拿到路径之后放入缓本地存中,注意我用的imageUrl作为Key,那么取出缓存也同样需要使用这个key。在什么地方取缓存呢?当然是一进入这个页面就取。就写在onCreate方法中。


    //取出缓存
        String imageUrl = SPUtils.getString("imageUrl",null,this);
        if(imageUrl != null){
            Glide.with(this).load(imageUrl).apply(requestOptions).into(ivHead);
        }

20210305161036436.png


这样就实现了本地图片缓存了,运行效果如下图


image.gif


可以看到,当我杀死程序之后再进入时,它显示的是我之前从相册中选取的图片。这就证明本地缓存成功了,并且可以使用。


十、后台获取


这个由于我无法实际操作,因此我就说一下方式。


实际中大部分的图片都是不会放到缓存里面的,因为会很占空间,第二个是缓存是少量的存储。


首先拿到拍照或者打开相册后的图片路径之后,这个地址当然不是直接发送给后台的,根据我的经验,它们通常需要的是图片的base64,如下图所示:


20210305161916345.png


这里的base64Pic是String类型的,它的数据会比较长,如果你的后台要求使用这种方式的话,那么你记得让他把这个字段的上限放到最大,否则会存储不完成,造成丢失。后台拿到这个base64Pic之后,会上传到一个服务器地址,然后在那里转换成图片,返回一个图片的url地址,通常是网址,这个网址你是后台的本地环境还是测试、正式开发环境,后台的本地环境,则只能在你当前的网络与后台处于同一局域网的情况下才能访问,比如你们连接同一个wifi,而测试、正式环境则不需要在同一局域网也可以访问,这一点你需要注意。


还有一个就是这个base64Pic,就有是只要直接传就可以了,还有的需要你这样拼接一下:

"data:image/jpeg;base64,"+base64Pic


这与你的后台对应的图片转换地址的要求有关系。说道这个网络还有一个地方要配置一下:

首先在你的res下新建一个xml文件夹,在这个文件夹下新建一个network_security_config.xml,里面的代码如下:


<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
    <base-config cleartextTrafficPermitted="true" />
</network-security-config>


然后在AndroidManifest.xml中配置:


20210305163142449.png


这个配置就是为了防一手http请求,因为Android9.0及以后的版本默认是https请求,上面的配置就是允许http请求。


十一、源码


源码地址:ChangeAvatarDemo


总结


  文章就结束了,能帮到你那就最好了,山高水长,后会有期~

相关文章
|
6月前
|
Android开发
Android调用相机与相册的方法1
Android调用相机与相册的方法
81 0
|
API Android开发 计算机视觉
视觉智能平台有android人脸识别拍照demo?
视觉智能平台有android人脸识别拍照demo么?
104 0
|
6月前
|
XML Java Android开发
Android Studio App开发之使用相机拍摄照片和从相册中选取图片(附源码 超详细必看)
Android Studio App开发之使用相机拍摄照片和从相册中选取图片(附源码 超详细必看)
828 0
|
6月前
|
Android开发
Android保存图片到相册(适配android 10以下及以上)
Android保存图片到相册(适配android 10以下及以上)
136 1
|
6月前
|
存储 Linux Android开发
RK3568 Android/Linux 系统动态更换 U-Boot/Kernel Logo
RK3568 Android/Linux 系统动态更换 U-Boot/Kernel Logo
852 0
|
6月前
|
Android开发
Android调用相机与相册的方法2
Android调用相机与相册的方法
69 0
|
Java Android开发
Android 保存资源图片到相册最新写法适用于Android10.0及以上
Android 保存资源图片到相册最新写法适用于Android10.0及以上
827 0
|
6月前
|
XML vr&ar Android开发
【Android App】实战项目之虚拟现实(VR)的全景相册(附源码和演示视频 可用于学习和大作业)
【Android App】实战项目之虚拟现实(VR)的全景相册(附源码和演示视频 可用于学习和大作业)
326 2
|
Android开发
Android -- 相册图片发送彩信
Android -- 相册图片发送彩信
113 0
|
存储 数据可视化 Android开发
Android 实现拍照功能,并将图片保存到本地存储
Android 实现拍照功能,并将图片保存到本地存储
690 0