目录
一、前言
二、SpringBoot后端接口
三、Android布局
四、Fragment代码实现
(1)获取View控件
(2)请求读写权限,授权再次点击+号打开相册
(3)相册选择回调图片Uri
五、效果测试
六、Android网络请求表单提交图片数据
(1)HttpUtil请求工具类
(2)开始调用后端接口,上传图片
(3)执行成功
七、权限工具类
八、总结
选择图片上传到后端,生成http:域名:端口:/xxx.jpg,将此URL路径存MySQL,Android查询查询数据库获取URL字段数据,使用Glide加载网络图片。
@PostMapping("/uploadFile")
public String uploadFile(@RequestParam(value = "imgFile") MultipartFile imgFile) throws IOException {
if (imgFile != null && !imgFile.isEmpty()) {
//获取文件名
String filename = imgFile.getOriginalFilename(); //图片名
String[] split = new String[0];
if (filename != null) {
split = filename.split("\\.");
}
//只接受jpg、png、jpeg格式图片文件,其它格式的文件可按需添加判断,主要是为了防止上传恶意文件,加强安全性
if ("jpg".equalsIgnoreCase(split[1]) || "png".equalsIgnoreCase(split[1]) || "jpeg".equalsIgnoreCase(split[1])) {
//图片重命名加后缀
String photoName = UUID.randomUUID().toString().replace("-", "") + "." + split[1];
File destFile = new File(logoRealFolderPath + File.separator + photoName);
//判断是否存在, 不存在就创建
if (!destFile.getParentFile().exists()) {
destFile.getParentFile().mkdirs();
}
//压缩图片保存
Thumbnails.of(imgFile.getInputStream()).scale(0.8).toFile(destFile);
//获取协议、服务器IP、端口号、工程路径
String basePath = request.getScheme() + "://" + request.getServerName() + ":" + request.getServerPort() + request.getContextPath();
String httpUrl = basePath + uploadReflexPath + photoName;
System.out.println("完成URL地址 = " + httpUrl);
//获取到URL后,可以将URL保存到数据库中,以便后续使用,这里就不做演示了,使用Mybatis即可
if (testService.addUrl(httpUrl) == 0) {
return "保存URL到数据库失败";
} else {
return "保存URL到数据库成功,文件地址为:" + httpUrl;
}
}
}
return "请上传文件后重试";
}
说明:api接口:/uploadFile,post的key(表单的name):imgFile
bg_box_gray.xml
我是在ViewPager2中Fragment进行布局的,你们可以换成Activity,完全不影响。
代码的话,我进行步骤讲解,涉及到公司业务代码,这里抱歉,不方便全部粘出来,我会尽量按步骤说清楚。
//图标选择
private AppCompatImageView ivRegisterPermissionLogo;
private AppCompatTextView tvPermissionShow;
//图标选择
ivRegisterPermissionLogo = baseFindView(R.id.iv_register_permission_logo);
tvPermissionShow = baseFindView(R.id.tv_permission_show);
tvPermissionShow.setText(R.string.add_permission_logo);
//SD存储权限组
private final String[] PERMISSIONS = new String[]{Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.WRITE_EXTERNAL_STORAGE};
//从相册中选择照片Activity的请求码
public static final int CHOOSE_PHOTO = 2;
//回调选中图片到真实路径
private String logoPath = "";
// 多个权限请求Code
private final int REQUEST_CODE_PERMISSIONS = 3;
//图标选择单击事件,触发时获取权限
ivRegisterPermissionLogo.setOnClickListener(v -> requestMorePermissions());
/**
* 自定义申请多个权限
*/
private void requestMorePermissions() {
PermissionUtils.checkMorePermissions(fragmentContext, PERMISSIONS, new PermissionUtils.PermissionCheckCallBack() {
@Override
public void onHasPermission() {
// 已授予权限,打开相册获取图片真实路径
Log.i(TAG, "已授予权限");
choosePermissionLogo();
}
@Override
public void onUserHasAlreadyTurnedDown(String... permission) {
// 上一次申请权限被拒绝,可用于向用户说明权限原因,然后调用权限申请方法
Log.i(TAG, "上一次申请权限被拒绝");
showExplainDialog((dialog, which) -> PermissionUtils.requestMorePermissions(fragmentContext, PERMISSIONS, REQUEST_CODE_PERMISSIONS));
}
@Override
public void onUserHasAlreadyTurnedDownAndDonAsk(String... permission) {
// 第一次申请权限或被禁止申请权限,建议直接调用申请权限方法。
Log.i(TAG, "第一次申请权限或被禁止申请权限");
PermissionUtils.requestMorePermissions(fragmentContext, PERMISSIONS, REQUEST_CODE_PERMISSIONS);
}
});
}
/**
* 解释权限的dialog
*/
private void showExplainDialog(DialogInterface.OnClickListener onClickListener) {
new AlertDialog.Builder(fragmentContext)
.setTitle("申请内存读写权限")
.setMessage("用于打开相册,读取图片路径")
.setPositiveButton("确定", onClickListener)
.show();
}
/**
* 打开相册选择权限图标
*/
private void choosePermissionLogo() {
//如果重复选择的话,先清空,等新图片路径来重新赋值,如果需要多张路的话,需要使用List来存储多张图片到真实路径
logoPath = "";
Intent pickIntent = new Intent(Intent.ACTION_PICK, null);
// 如果限制上传到服务器的图片类型时可以直接写如:"image/jpeg 、 image/png等的类型"
pickIntent.setDataAndType(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, "image/*");
startActivityForResult(pickIntent, CHOOSE_PHOTO);
}
/**
* 回调申请的权限组
*/
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
if (requestCode == REQUEST_CODE_PERMISSIONS) {
PermissionUtils.onRequestMorePermissionsResult(fragmentContext, PERMISSIONS, new PermissionUtils.PermissionCheckCallBack() {
@Override
public void onHasPermission() {
// 权限已被授予
choosePermissionLogo();
}
@Override
public void onUserHasAlreadyTurnedDown(String... permission) {
// 拒绝权限
Toast.makeText(fragmentContext, "我们需要" + Arrays.toString(permission) + "权限", Toast.LENGTH_SHORT).show();
}
@Override
public void onUserHasAlreadyTurnedDownAndDonAsk(String... permission) {
//已禁止再次询问权限
Toast.makeText(fragmentContext, "我们需要" + Arrays.toString(permission) + "权限", Toast.LENGTH_SHORT).show();
showToAppSettingDialog();
}
});
}
}
说明:权限请求完整工具类,文末贴出来。
//从相册中选择照片Activity的请求码
public static final int CHOOSE_PHOTO = 2;
//回调选中图片到真实路径
private String logoPath = "";
/**
* Android 4.4以上打开相册获取图片真实路径
*/
@TargetApi(19)
private String handleImageOnKitKat(Intent data) {
String imagePath = null;
Uri uri = data.getData();
Log.i(TAG, "获取的uri=" + uri);
if (DocumentsContract.isDocumentUri(fragmentContext, uri)) {
//如果是document类型的Uri ,则通过document_id来处理
String docId = DocumentsContract.getDocumentId(uri);
if ("com.android.providers.media.documents".equals(uri.getAuthority())) {
String id = docId.split(":")[1];//解析出数据格式的ID
String selection = MediaStore.Images.Media._ID + "=" + id;
imagePath = getImagePath(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, selection);
Log.i(TAG, "类型=media.documents");
} else if ("com.android.providers.downloads.documents".equals(uri.getAuthority())) {
Uri contentUri = ContentUris.withAppendedId(Uri.parse("content://downloads/public_downloads"), Long.parseLong(docId));
imagePath = getImagePath(contentUri, null);
Log.i(TAG, "类型=downloads.documents");
}
} else if ("content".equalsIgnoreCase(uri.getScheme())) {
//如果是普通类型的Uri,则使用普通的方式来处理
imagePath = getImagePath(uri, null);
//imagePath = getRealPathFromURI(uri);
Log.i(TAG, "类型=content");
} else if ("file".equalsIgnoreCase(uri.getScheme())) {
//如果是file类型的uri,直接获取图片路径就可以了
imagePath = uri.getPath();
Log.i(TAG, "类型=file");
}
Log.i(TAG, "图片真实路径=" + imagePath);
return imagePath;
}
/**
* Android4.4以下打开相册获取图片真实路径
*/
private String handleImageBeforeKitKat(Intent data) {
Uri uri = data.getData();
String imagePath = getImagePath(uri, null);
Log.i(TAG, "图片真实路径=" + imagePath);
return imagePath;
}
/**
* 相册选择回调图片Uri
*/
@SuppressLint({"ObsoleteSdkInt", "SetTextI18n"})
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
// TODO: 从相册获取
if (requestCode == CHOOSE_PHOTO) {
if (resultCode == RESULT_OK) {
if (Build.VERSION.SDK_INT >= 19) {
logoPath = handleImageOnKitKat(data);
} else {
logoPath = handleImageBeforeKitKat(data);
}
}
}
//必须先进行上面两个handleImage方法去解析Uri,将类型转为真实路径的String路径返回给logoPath,然后进行下面的判断操作业务
if (TextUtils.isEmpty(logoPath) && logoPath.equals("")) {
tvPermissionShow.setText("请您选择一张权限图标");
tvPermissionShow.setTextColor(getResources().getColor(R.color.red)); //字体变红色
} else {
//不为空,将图片路径转为Bitmap后,使用图片控件显示出来
ivRegisterPermissionLogo.setImageBitmap(BitmapFactory.decodeFile(logoPath));
tvPermissionShow.setText("当前图片名:" + logoPath.substring(logoPath.lastIndexOf("/") + 1));
tvPermissionShow.setTextColor(getResources().getColor(R.color.blue));
}
super.onActivityResult(requestCode, resultCode, data);
}
public class HttpUtil {
private static final String TAG = "HttpUtil";
//边界参数
private String boundary;
//文件上传接口监听
private requestListener requestListener;
/**
* 图片上传
*
* @param urlStr URL
* @param imgPath 图片路径
*/
public void upLoadFile(String urlStr, final String imgPath) {
new Thread(() -> {
Looper.prepare();
//自己生一个boundary
boundary = UUID.randomUUID().toString().replace("-", "");
HttpURLConnection conn = null;
try {
String fileName = "zy" + (int) (Math.random() * 1000000) + ".jpg";
StringBuilder sb = new StringBuilder();
//表单数据
sb.append("--").append(boundary).append("\r\n");
//图标
sb.append("Content-Disposition: form-data; name=imgFile" + "; filename=").append(fileName).append("\r\n");
//传图路径创建文件对象
File file = new File(imgPath);
//获取文件名
String filename = file.getName();
//截取文件后缀,对应设置mime-type支持类型
String contentType = "";
if (filename.endsWith(".png")) {
contentType = "image/png";
}
if (filename.endsWith(".jpg")) {
contentType = "image/jpg";
}
if (filename.endsWith(".gif")) {
contentType = "image/gif";
}
if (filename.endsWith(".bmp")) {
contentType = "image/bmp";
}
if (contentType.equals("")) {
contentType = "application/octet-stream";
}
sb.append("Content-Type:").append(contentType).append("\r\n");
sb.append("\r\n");//此时的sd———>Content-Disposition: form-data; name=imgFile; filename=zy+随机数.jpg
//字节流处理
byte[] headerInfo = sb.toString().getBytes(StandardCharsets.UTF_8);
byte[] endInfo = ("\r\n--" + boundary + "--\r\n").getBytes(StandardCharsets.UTF_8);
conn = (HttpURLConnection) new URL(urlStr).openConnection();
conn.setDoInput(true);
conn.setDoOutput(true);
//文件上传必须为POST
conn.setRequestMethod("POST");
//注意这里的格式,模仿表单,出现一点点错误都会导致上传不成功
conn.setRequestProperty("Content-Type", "multipart/form-data; boundary=" + boundary);
//数据长度
conn.setRequestProperty("Content-Length", String.valueOf(file.length() + headerInfo.length + endInfo.length));
//通过conn拿到服务器的字节输出流
OutputStream out = conn.getOutputStream();
//需要上传的文件封装成字节输入流
InputStream in = new FileInputStream(file);
out.write(headerInfo);
byte[] buf = new byte[1024];
int len;
while ((len = in.read(buf)) != -1) out.write(buf, 0, len);
out.write(endInfo);
in.close();
out.close();
if (conn.getResponseCode() == 200) {
requestListener.success(new BufferedReader(new InputStreamReader(conn.getInputStream())).readLine());
}
} catch (Exception e) {
e.printStackTrace();
if (Objects.requireNonNull(e.getMessage()).contains("to connect to")) {
Log.i(TAG, "服务器访问失败,请联系管理员修复");
if (requestListener != null) {
requestListener.fail("{ \"code\": 502,\n \"msg\": \"服务器访问失败,请联系管理员处理\",\n \"data\":{}}");
}
} else if (Objects.requireNonNull(e.getMessage()).contains("Unable to resolve host")) {
Log.i(TAG, "服务器访问失败,请检查网络是否可用");
if (requestListener != null) {
requestListener.fail("{ \"code\": 503,\n \"msg\": \"请求失败,请检查网络是否可用\",\n \"data\":{}}");
}
} else {
Log.i(TAG, "服务器访问失败,未知错误:" + e.getMessage());
if (requestListener != null) {
requestListener.fail("{ \"code\": 504,\n \"msg\": \"服务器访问失败,错误原因未知,请联系管理员处理\",\n \"data\":{}}");
}
}
} finally {
if (conn != null) {
conn.disconnect();
}
}
Looper.loop();
}).start();
}
/**
* 上传监听接口
*
* @param requestListener 上传结果回调
*/
public void setRequestListener(requestListener requestListener) {
this.requestListener = requestListener;
}
/**
* 定义外部请求结果回调接口
*/
public interface requestListener {
/**
* 成功回调
*
* @param resultJson 成功JSON数据
*/
void success(String resultJson) throws JSONException;
/**
* 失败回调
*
* @param resultJson 失败JSON数据
*/
void fail(String resultJson);
}
}
HttpUtil uploadFileUtil = new HttpUtil();
uploadFileUtil.upLoadFile(Constant.ADD_PERMISSION_INFO);
uploadFileUtil.setRequestListener(new HttpUtil.requestListener() {
@Override
public void success(String resultJson) {
Log.i("zyLogin", "成功: " + resultJson);
}
@Override
public void fail(String resultJson) {
Log.i("zyLogin", "失败: " + resultJson);
}
});
说明:执行成功后,后端业务代码处理后,会将图片信息存入到云服务器Linux的硬盘内存上,并将Servlet映射路径后外网可访问的URL地址存进数据库,当Android使用Glide就能直接加载图片信息啦。
public class PermissionUtils {
/**
* 检测权限
*
* @return true:已授权; false:未授权;
*/
public static boolean checkPermission(Context context, String permission) {
if (ContextCompat.checkSelfPermission(context, permission) == PackageManager.PERMISSION_GRANTED)
return true;
else
return false;
}
/**
* 检测多个权限
*
* @return 未授权的权限
*/
public static List checkMorePermissions(Context context, String[] permissions) {
List permissionList = new ArrayList<>();
for (int i = 0; i < permissions.length; i++) {
if (!checkPermission(context, permissions[i]))
permissionList.add(permissions[i]);
}
return permissionList;
}
/**
* 请求权限
*/
public static void requestPermission(Context context, String permission, int requestCode) {
ActivityCompat.requestPermissions((Activity) context, new String[]{permission}, requestCode);
}
/**
* 请求多个权限
*/
public static void requestMorePermissions(Context context, List permissionList, int requestCode) {
String[] permissions = (String[]) permissionList.toArray(new String[permissionList.size()]);
requestMorePermissions(context, permissions, requestCode);
}
/**
* 请求多个权限
*/
public static void requestMorePermissions(Context context, String[] permissions, int requestCode) {
ActivityCompat.requestPermissions((Activity) context, permissions, requestCode);
}
/**
* 判断是否已拒绝过权限
* 如果应用之前请求过此权限但用户拒绝,此方法将返回 true;
* 如果应用第一次请求权限或 用户在过去拒绝了权限请求,
* 并在权限请求系统对话框中选择了 Don't ask again 选项,此方法将返回 false。
*/
public static boolean judgePermission(Context context, String permission) {
return ActivityCompat.shouldShowRequestPermissionRationale((Activity) context, permission);
}
/**
* 检测权限并请求权限:如果没有权限,则请求权限
*/
public static void checkAndRequestPermission(Context context, String permission, int requestCode) {
if (!checkPermission(context, permission)) {
requestPermission(context, permission, requestCode);
}
}
/**
* 检测并请求多个权限
*/
public static void checkAndRequestMorePermissions(Context context, String[] permissions, int requestCode) {
List permissionList = checkMorePermissions(context, permissions);
requestMorePermissions(context, permissionList, requestCode);
}
/**
* 检测权限
* 具体实现由回调接口决定
*/
public static void checkPermission(Context context, String permission, PermissionCheckCallBack callBack) {
if (checkPermission(context, permission)) { // 用户已授予权限
callBack.onHasPermission();
} else {
if (judgePermission(context, permission)) // 用户之前已拒绝过权限申请
callBack.onUserHasAlreadyTurnedDown(permission);
else // 用户之前已拒绝并勾选了不在询问、用户第一次申请权限。
callBack.onUserHasAlreadyTurnedDownAndDonAsk(permission);
}
}
/**
* 检测多个权限
* 具体实现由回调接口决定
*/
public static void checkMorePermissions(Context context, String[] permissions, PermissionCheckCallBack callBack) {
List permissionList = checkMorePermissions(context, permissions);
if (permissionList.size() == 0) { // 用户已授予权限
callBack.onHasPermission();
} else {
boolean isFirst = true;
for (int i = 0; i < permissionList.size(); i++) {
String permission = permissionList.get(i);
if (judgePermission(context, permission)) {
isFirst = false;
break;
}
}
String[] unauthorizedMorePermissions = (String[]) permissionList.toArray(new String[permissionList.size()]);
if (isFirst)// 用户之前已拒绝过权限申请
callBack.onUserHasAlreadyTurnedDownAndDonAsk(unauthorizedMorePermissions);
else // 用户之前已拒绝并勾选了不在询问、用户第一次申请权限。
callBack.onUserHasAlreadyTurnedDown(unauthorizedMorePermissions);
}
}
/**
* 检测并申请权限
*/
public static void checkAndRequestPermission(Context context, String permission, int requestCode, PermissionRequestSuccessCallBack callBack) {
if (checkPermission(context, permission)) {// 用户已授予权限
callBack.onHasPermission();
} else {
requestPermission(context, permission, requestCode);
}
}
/**
* 检测并申请多个权限
*/
public static void checkAndRequestMorePermissions(Context context, String[] permissions, int requestCode, PermissionRequestSuccessCallBack callBack) {
List permissionList = checkMorePermissions(context, permissions);
if (permissionList.size() == 0) { // 用户已授予权限
callBack.onHasPermission();
} else {
requestMorePermissions(context, permissionList, requestCode);
}
}
/**
* 判断权限是否申请成功
*/
public static boolean isPermissionRequestSuccess(int[] grantResults) {
if (grantResults.length > 0
&& grantResults[0] == PackageManager.PERMISSION_GRANTED)
return true;
else
return false;
}
/**
* 用户申请权限返回
*/
public static void onRequestPermissionResult(Context context, String permission, int[] grantResults, PermissionCheckCallBack callback) {
if (PermissionUtils.isPermissionRequestSuccess(grantResults)) {
callback.onHasPermission();
} else {
if (PermissionUtils.judgePermission(context, permission)) {
callback.onUserHasAlreadyTurnedDown(permission);
} else {
callback.onUserHasAlreadyTurnedDownAndDonAsk(permission);
}
}
}
/**
* 用户申请多个权限返回
*/
public static void onRequestMorePermissionsResult(Context context, String[] permissions, PermissionCheckCallBack callback) {
boolean isBannedPermission = false;
List permissionList = checkMorePermissions(context, permissions);
if (permissionList.size() == 0)
callback.onHasPermission();
else {
for (int i = 0; i < permissionList.size(); i++) {
if (!judgePermission(context, permissionList.get(i))) {
isBannedPermission = true;
break;
}
}
// 已禁止再次询问权限
if (isBannedPermission)
callback.onUserHasAlreadyTurnedDownAndDonAsk(permissions);
else // 拒绝权限
callback.onUserHasAlreadyTurnedDown(permissions);
}
}
/**
* 跳转到权限设置界面
*/
public static void toAppSetting(Context context) {
Intent intent = new Intent();
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
if (Build.VERSION.SDK_INT >= 9) {
intent.setAction("android.settings.APPLICATION_DETAILS_SETTINGS");
intent.setData(Uri.fromParts("package", context.getPackageName(), null));
} else if (Build.VERSION.SDK_INT <= 8) {
intent.setAction(Intent.ACTION_VIEW);
intent.setClassName("com.android.settings", "com.android.settings.InstalledAppDetails");
intent.putExtra("com.android.settings.ApplicationPkgName", context.getPackageName());
}
context.startActivity(intent);
}
public interface PermissionRequestSuccessCallBack {
/**
* 用户已授予权限
*/
void onHasPermission();
}
public interface PermissionCheckCallBack {
/**
* 用户已授予权限
*/
void onHasPermission();
/**
* 用户已拒绝过权限
*
* @param permission:被拒绝的权限
*/
void onUserHasAlreadyTurnedDown(String... permission);
/**
* 用户已拒绝过并且已勾选不再询问选项、用户第一次申请权限;
*
* @param permission:被拒绝的权限
*/
void onUserHasAlreadyTurnedDownAndDonAsk(String... permission);
}
}
仅自己学习记录,如有错误,敬请谅解~,谢谢~~