Android 拍照选择图片并上传功能的实现思路(包含权限动态获取)

作为一个Android新手,想实现手机拍照并上传的功能,经过查找资料,已实现此功能。在此记录备忘。老鸟请忽略。

一、实现思路:

1.Android手机客户端,拍照(或选择图片),然后上传到服务器。

2.服务器端接收手机端上传上来的图片。

二、实现步骤:

1.按惯例,先放效果图:

项目结构:

2.activity_main.xml

<?xml version="1.0" encoding="utf-8"?>

<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">

<TextView

android:layout_width="wrap_content"

android:layout_height="wrap_content"

android:text="图片预览" />

<ImageView

android:id="@+id/imageView"

android:layout_width="match_parent"

android:layout_height="400dp"

android:background="#fff"

android:padding="1dp"

android:scaleType="fitXY" />

<LinearLayout

android:layout_width="wrap_content"

android:layout_height="wrap_content"

android:layout_gravity="center_horizontal"

android:orientation="horizontal">

<Button

android:id="@+id/btnPhoto"

android:layout_width="wrap_content"

android:layout_height="wrap_content"

android:text="拍照" />

<Button

android:id="@+id/btnSelect"

android:layout_width="wrap_content"

android:layout_height="wrap_content"

android:text="选择" />

</LinearLayout>

</LinearLayout>

3.MainActivity.java

package com.qingshan.note;

import androidx.annotation.NonNull;

import androidx.annotation.RequiresApi;

import androidx.appcompat.app.AppCompatActivity;

import androidx.core.app.ActivityCompat;

import androidx.core.content.ContextCompat;

import android.Manifest;

import android.app.AlertDialog;

import android.content.ContentValues;

import android.content.DialogInterface;

import android.content.Intent;

import android.content.pm.PackageManager;

import android.graphics.Bitmap;

import android.graphics.BitmapFactory;

import android.graphics.Matrix;

import android.net.Uri;

import android.os.Build;

import android.os.Bundle;

import android.os.Environment;

import android.provider.MediaStore;

import android.provider.Settings;

import android.view.View;

import android.widget.Button;

import android.widget.ImageView;

import android.widget.Toast;

import java.io.BufferedReader;

import java.io.DataInputStream;

import java.io.DataOutputStream;

import java.io.File;

import java.io.FileInputStream;

import java.io.FileNotFoundException;

import java.io.FileOutputStream;

import java.io.IOException;

import java.io.InputStreamReader;

import java.io.OutputStream;

import java.net.HttpURLConnection;

import java.net.URL;

import java.text.SimpleDateFormat;

import java.util.ArrayList;

import java.util.Date;

import java.util.HashMap;

import java.util.Iterator;

import java.util.List;

import java.util.Map;

public class MainActivity extends AppCompatActivity implements View.OnClickListener {

private Button btnPhoto, btnSelect;

private Intent intent;

private final int CAMERA = 1;//事件枚举(可以自定义)

private final int CHOOSE = 2;//事件枚举(可以自定义)

private final String postUrl = "http://qingshanboke.com/Home/AndoridUploadFile";//接收上传图片的地址

String photoPath = "";//要上传的图片路径

private final int permissionCode = 100;//权限请求码

//权限集合,对应在AndroidManifest.xml文件中添加配置

// <uses-permission android:name="android.permission.CAMERA" />

// <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />

// <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />

// <uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>

// <uses-permission android:name="android.permission.INTERNET"/>

String[] permissions = new String[]{

Manifest.permission.CAMERA,

Manifest.permission.WRITE_EXTERNAL_STORAGE,

Manifest.permission.ACCESS_NETWORK_STATE,

Manifest.permission.ACCESS_WIFI_STATE,

Manifest.permission.INTERNET

};

AlertDialog alertDialog;

@Override

protected void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

setContentView(R.layout.activity_main);

//6.0才用动态权限

if (Build.VERSION.SDK_INT >= 23) {

checkPermission();

}

btnPhoto = findViewById(R.id.btnPhoto);

btnSelect = findViewById(R.id.btnSelect);

btnPhoto.setOnClickListener(this);

btnSelect.setOnClickListener(this);

}

//检查权限

private void checkPermission() {

List<String> permissionList = new ArrayList<>();

for (int i = 0; i < permissions.length; i++) {

if (ContextCompat.checkSelfPermission(this, permissions[i]) != PackageManager.PERMISSION_GRANTED) {

permissionList.add(permissions[i]);

}

}

if (permissionList.size() <= 0) {

//说明权限都已经通过,可以做你想做的事情去

} else {

//存在未允许的权限

ActivityCompat.requestPermissions(this, permissions, permissionCode);

}

}

//授权后回调函数

@Override

public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {

super.onRequestPermissionsResult(requestCode, permissions, grantResults);

boolean haspermission = false;

if (permissionCode == requestCode) {

for (int i = 0; i < grantResults.length; i++) {

if (grantResults[i] == -1) {

haspermission = true;

}

}

if (haspermission) {

//跳转到系统设置权限页面,或者直接关闭页面,不让他继续访问

permissionDialog();

} else {

//全部权限通过,可以进行下一步操作

}

}

}

//打开手动设置应用权限

private void permissionDialog() {

if (alertDialog == null) {

alertDialog = new AlertDialog.Builder(this)

.setTitle("提示信息")

.setMessage("当前应用缺少必要权限,该功能暂时无法使用。如若需要,请单击【确定】按钮前往设置中心进行权限授权。")

.setPositiveButton("设置", new DialogInterface.OnClickListener() {

@Override

public void onClick(DialogInterface dialog, int which) {

cancelPermissionDialog();

Uri packageURI = Uri.parse("package:" + getPackageName());

Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS, packageURI);

startActivity(intent);

}

})

.setNegativeButton("取消", new DialogInterface.OnClickListener() {

@Override

public void onClick(DialogInterface dialog, int which) {

cancelPermissionDialog();

}

})

.create();

}

alertDialog.show();

}

//用户取消授权

private void cancelPermissionDialog() {

alertDialog.cancel();

}

@Override

public void onClick(View v) {

switch (v.getId()) {

//拍照按钮事件

case R.id.btnPhoto:

//方法一:这样拍照只能取到缩略图(不清晰)

//intent = new Intent(android.provider.MediaStore.ACTION_IMAGE_CAPTURE);

//startActivityForResult(intent, CAMERA);

//方法二:指定加载路径图片路径(保存原图,清晰)

String SD_PATH = Environment.getExternalStorageDirectory().getPath() + "/拍照上传示例/";

SimpleDateFormat format = new SimpleDateFormat("yyyyMMddHHmmss");

String fileName = format.format(new Date(System.currentTimeMillis())) + ".JPEG";

photoPath = SD_PATH + fileName;

File file = new File(photoPath);

if (!file.getParentFile().exists()) {

file.getParentFile().mkdirs();

}

//兼容7.0以上的版本

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {

try {

ContentValues values = new ContentValues(1);

values.put(MediaStore.Images.Media.MIME_TYPE, "image/jpg");

values.put(MediaStore.Images.Media.DATA, photoPath);

Uri tempuri = getContentResolver().insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values);

Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);

intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);

if (tempuri != null) {

intent.putExtra(MediaStore.EXTRA_OUTPUT, tempuri);

intent.putExtra(MediaStore.EXTRA_VIDEO_QUALITY, 1);

}

startActivityForResult(intent, CAMERA);

} catch (Exception e) {

e.printStackTrace();

}

} else {

intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);

Uri uri = Uri.fromFile(file);

intent.putExtra(MediaStore.EXTRA_OUTPUT, uri); //指定拍照后的存储路径,保存原图

startActivityForResult(intent, CAMERA);

}

break;

//选择按钮事件

case R.id.btnSelect:

intent = new Intent(Intent.ACTION_PICK, android.provider.MediaStore.Images.Media.EXTERNAL_CONTENT_URI);

startActivityForResult(intent, CHOOSE);

break;

}

}

@RequiresApi(api = Build.VERSION_CODES.O)

@Override

protected void onActivityResult(int requestCode, int resultCode, Intent data) {

super.onActivityResult(requestCode, resultCode, data);

switch (requestCode) {

// 调用照相机拍照

case CAMERA:

if (resultCode == RESULT_OK) {

//对应方法一:图片未保存,需保存文件到本地

// Bundle bundle = data.getExtras();

// Bitmap bitmap = (Bitmap) bundle.get("data");

// String savePath;

// String SD_PATH = Environment.getExternalStorageDirectory().getPath() + "/拍照上传示例/";

// SimpleDateFormat format = new SimpleDateFormat("yyyyMMddHHmmss");

// String fileName = format.format(new Date(System.currentTimeMillis())) + ".JPEG";

// if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {

// savePath = SD_PATH;

// } else {

// Toast.makeText(MainActivity.this, "保存失败!", Toast.LENGTH_SHORT).show();

// return;

// }

// photoPath = savePath + fileName;

// File file = new File(photoPath);

// try {

// if (!file.exists()) {

// file.getParentFile().mkdirs();

// file.createNewFile();

// }

// FileOutputStream stream = new FileOutputStream(file);

// bitmap.compress(Bitmap.CompressFormat.JPEG, 100, stream);

// Toast.makeText(MainActivity.this, "保存成功,位置:" + file.getAbsolutePath(), Toast.LENGTH_SHORT).show();

// } catch (IOException e) {

// e.printStackTrace();

// }

//对应方法二:图片已保存,只需读取就行了

try {

FileInputStream stream = new FileInputStream(photoPath);

Bitmap bitmap = BitmapFactory.decodeStream(stream);

//预览图片

ImageView image = findViewById(R.id.imageView);

image.setImageBitmap(bitmap);

//上传图片(Android 4.0 之后不能在主线程中请求HTTP请求)

File file = new File(photoPath);

if (file.exists()) {

new Thread(new Runnable() {

@Override

public void run() {

//文本字段(用于验证用户身份)

HashMap<String, String> form = new HashMap<String, String>();

form.put("username", "zhangqs");

form.put("password", "123456");

//图片字段

HashMap<String, String> file = new HashMap<String, String>();

file.put(PathHelper.getFileNameFromPath(photoPath), photoPath);

formUpload(postUrl, form, file);

}

}).start();

}

} catch (FileNotFoundException e) {

e.printStackTrace();

}

}

break;

// 选择图片库的图片

case CHOOSE:

if (resultCode == RESULT_OK) {

try {

Uri uri = data.getData();

photoPath = PathHelper.getRealPathFromUri(MainActivity.this, uri);

Bitmap bitmap = MediaStore.Images.Media.getBitmap(this.getContentResolver(), uri);

//压缩图片

bitmap = scaleBitmap(bitmap, (float) 0.5);

//预览图片

ImageView image = findViewById(R.id.imageView);

image.setImageBitmap(bitmap);

//上传图片(Android 4.0 之后不能在主线程中请求HTTP请求)

File file = new File(photoPath);

if (file.exists()) {

new Thread(new Runnable() {

@Override

public void run() {

//文本字段(用于验证用户身份)

HashMap<String, String> form = new HashMap<String, String>();

form.put("username", "zhangqs");

form.put("password", "123456");

//图片字段

HashMap<String, String> file = new HashMap<String, String>();

file.put(PathHelper.getFileNameFromPath(photoPath), photoPath);

formUpload(postUrl, form, file);

}

}).start();

}

} catch (IOException e) {

e.printStackTrace();

}

}

break;

}

}

//压缩图片

public Bitmap scaleBitmap(Bitmap origin, float ratio) {

if (origin == null) {

return null;

}

int width = origin.getWidth();

int height = origin.getHeight();

Matrix matrix = new Matrix();

matrix.preScale(ratio, ratio);

Bitmap newBM = Bitmap.createBitmap(origin, 0, 0, width, height, matrix, false);

return newBM;

}

//POST 表单提交

@RequiresApi(api = Build.VERSION_CODES.O)

public static String formUpload(String posturl, Map<String, String> textMap, Map<String, String> fileMap) {

String res = "";

HttpURLConnection conn = null;

String BOUNDARY = "---------------------------123821742118716"; //boundary就是request头和上传文件内容的分隔符

try {

URL url = new URL(posturl);

conn = (HttpURLConnection) url.openConnection();

conn.setConnectTimeout(5000);

conn.setReadTimeout(30000);

conn.setDoOutput(true);

conn.setDoInput(true);

conn.setUseCaches(false);

conn.setRequestMethod("POST");

conn.setRequestProperty("Connection", "Keep-Alive");

conn.setRequestProperty("User-Agent", "Mozilla/5.0 (Windows; U; Windows NT 6.1; zh-CN; rv:1.9.2.6)");

conn.setRequestProperty("Content-Type", "multipart/form-data; boundary=" + BOUNDARY);

OutputStream out = new DataOutputStream(conn.getOutputStream());

// text

if (textMap != null) {

StringBuffer buffer = new StringBuffer();

Iterator iter = textMap.entrySet().iterator();

while (iter.hasNext()) {

Map.Entry entry = (Map.Entry) iter.next();

String inputName = (String) entry.getKey();

String inputValue = (String) entry.getValue();

if (inputValue == null) {

continue;

}

buffer.append("\r\n").append("--").append(BOUNDARY).append("\r\n");

buffer.append("Content-Disposition: form-data; name=\"" + inputName + "\"\r\n\r\n");

buffer.append(inputValue);

}

out.write(buffer.toString().getBytes());

}

// file

if (fileMap != null) {

Iterator iter = fileMap.entrySet().iterator();

while (iter.hasNext()) {

Map.Entry entry = (Map.Entry) iter.next();

String inputName = (String) entry.getKey();

String inputValue = (String) entry.getValue();

if (inputValue == null) {

continue;

}

File file = new File(inputValue);

String filename = file.getName();

String contentType = "";

if (filename.endsWith(".jpg")) {

contentType = "image/jpg";

} else if (filename.endsWith(".png")) {

contentType = "image/png";

} else if (contentType == null || contentType.equals("")) {

contentType = "application/octet-stream";

}

StringBuffer buffer = new StringBuffer();

buffer.append("\r\n").append("--").append(BOUNDARY).append("\r\n");

buffer.append("Content-Disposition: form-data; name=\"" + inputName + "\"; filename=\"" + filename + "\"\r\n");

buffer.append("Content-Type:" + contentType + "\r\n\r\n");

out.write(buffer.toString().getBytes());

DataInputStream in = new DataInputStream(new FileInputStream(file));

int bytes = 0;

byte[] bufferOut = new byte[1024];

while ((bytes = in.read(bufferOut)) != -1) {

out.write(bufferOut, 0, bytes);

}

in.close();

}

}

byte[] endData = ("\r\n--" + BOUNDARY + "--\r\n").getBytes();

out.write(endData);

out.flush();

out.close();

// 读取返回数据

StringBuffer buffer = new StringBuffer();

BufferedReader reader = new BufferedReader(new InputStreamReader(conn.getInputStream()));

String line = null;

while ((line = reader.readLine()) != null) {

buffer.append(line).append("\n");

}

res = buffer.toString();

reader.close();

reader = null;

} catch (Exception e) {

System.out.println("发送POST请求出错。" + posturl);

e.printStackTrace();

} finally {

if (conn != null) {

conn.disconnect();

conn = null;

}

}

return res;

}

}

4.辅助类 PathHelper.java

package com.qingshan.note;

import android.annotation.SuppressLint;

import android.content.ContentUris;

import android.content.Context;

import android.database.Cursor;

import android.net.Uri;

import android.os.Build;

import android.provider.DocumentsContract;

import android.provider.MediaStore;

//Android 路径辅助类

public class PathHelper {

//适配api19以下(不包括api19),根据uri获取图片的绝对路径

public static String getRealPathFromUri(Context context, Uri uri) {

int sdkVersion = Build.VERSION.SDK_INT;

if (sdkVersion >= 19) { // api >= 19

return getRealPathFromUriAboveApi19(context, uri);

} else { // api < 19

return getRealPathFromUriBelowAPI19(context, uri);

}

}

/**

* 适配api19以下(不包括api19),根据uri获取图片的绝对路径

*

* @param context 上下文对象

* @param uri 图片的Uri

* @return 如果Uri对应的图片存在, 那么返回该图片的绝对路径, 否则返回null

*/

private static String getRealPathFromUriBelowAPI19(Context context, Uri uri) {

return getDataColumn(context, uri, null, null);

}

/**

* 适配api19及以上,根据uri获取图片的绝对路径

*

* @param context 上下文对象

* @param uri 图片的Uri

* @return 如果Uri对应的图片存在, 那么返回该图片的绝对路径, 否则返回null

*/

@SuppressLint("NewApi")

private static String getRealPathFromUriAboveApi19(Context context, Uri uri) {

String filePath = null;

if (DocumentsContract.isDocumentUri(context, uri)) {

// 如果是document类型的 uri, 则通过document id来进行处理

String documentId = DocumentsContract.getDocumentId(uri);

if (isMediaDocument(uri)) { // MediaProvider

// 使用':'分割

String id = documentId.split(":")[1];

String selection = MediaStore.Images.Media._ID + "=?";

String[] selectionArgs = {id};

filePath = getDataColumn(context, MediaStore.Images.Media.EXTERNAL_CONTENT_URI, selection, selectionArgs);

} else if (isDownloadsDocument(uri)) { // DownloadsProvider

Uri contentUri = ContentUris.withAppendedId(Uri.parse("content://downloads/public_downloads"), Long.valueOf(documentId));

filePath = getDataColumn(context, contentUri, null, null);

}

} else if ("content".equalsIgnoreCase(uri.getScheme())) {

// 如果是 content 类型的 Uri

filePath = getDataColumn(context, uri, null, null);

} else if ("file".equals(uri.getScheme())) {

// 如果是 file 类型的 Uri,直接获取图片对应的路径

filePath = uri.getPath();

}

return filePath;

}

private static String getDataColumn(Context context, Uri uri, String selection, String[] selectionArgs) {

String path = null;

String[] projection = new String[]{MediaStore.Images.Media.DATA};

Cursor cursor = null;

try {

cursor = context.getContentResolver().query(uri, projection, selection, selectionArgs, null);

if (cursor != null && cursor.moveToFirst()) {

int columnIndex = cursor.getColumnIndexOrThrow(projection[0]);

path = cursor.getString(columnIndex);

}

} catch (Exception e) {

e.printStackTrace();

} finally {

if (cursor != null) {

cursor.close();

}

}

return path;

}

private static boolean isMediaDocument(Uri uri) {

return "com.android.providers.media.documents".equals(uri.getAuthority());

}

private static boolean isDownloadsDocument(Uri uri) {

return "com.android.providers.downloads.documents".equals(uri.getAuthority());

}

//从路径中提取文件名

public static String getFileNameFromPath(String path) {

int start = path.lastIndexOf("/");

int end = path.lastIndexOf(".");

if (start != -1 && end != -1) {

return path.substring(start + 1, end);

} else {

return null;

}

}

}

5.AndroidManifest.xml

<?xml version="1.0" encoding="utf-8"?>

<manifest xmlns:android="http://schemas.android.com/apk/res/android"

package="com.qingshan.note">

<!-- 因为拍照需要写入文件 所以需要申请读取内存的权限 -->

<uses-permission android:name="android.permission.CAMERA" />

<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />

<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />

<uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>

<uses-permission android:name="android.permission.INTERNET"/>

<application

android:networkSecurityConfig="@xml/network_security_config"

android:allowBackup="true"

android:icon="@mipmap/ic_launcher"

android:label="@string/app_name"

android:usesCleartextTraffic="true"

android:roundIcon="@mipmap/ic_launcher_round"

android:supportsRtl="true"

android:theme="@style/AppTheme">

<activity android:name=".MainActivity">

<intent-filter>

<action android:name="android.intent.action.MAIN" />

<category android:name="android.intent.category.LAUNCHER" />

</intent-filter>

</activity>

</application>

</manifest>

6.\res\xml\network_security_config.xml

<?xml version="1.0" encoding="utf-8"?>

<network-security-config>

<base-config cleartextTrafficPermitted="true" />

<domain-config cleartextTrafficPermitted="true" >

<domain includeSubdomains="true">127.0.0.1</domain>

<domain includeSubdomains="true">192.168.100.192</domain>

<domain includeSubdomains="true">localhost</domain>

<domain includeSubdomains="true">qingshanboke.com</domain>

</domain-config>

</network-security-config>

7.服务器端接收(asp.net mvc 接收)

       

public ActionResult AndoridUploadFile()

{

var userName = Request.Params["username"];

var password = Request.Params["password"];

if (string.IsNullOrEmpty(userName) || string.IsNullOrEmpty(password))

{

return Content("抱歉,用户名和密码错误!");

}

//todo:身份验证

var dir = PathHelper.GetMapPath("~/Uploadfiles/" + DateTime.Now.ToString("yyyy-MM"));

if (!Directory.Exists(dir))

{

Directory.CreateDirectory(dir);

}

for (int i = 0; i < Request.Files.Count; i++)

{

var path = Path.Combine(dir, DateTime.Now.ToString("yyyyMMddHHmmss") + ".jpg");

if (Request.Files[i] != null)

{

Request.Files[i].SaveAs(path);

}

}

return Content("{\"isSuccess\":true}");

}

三、注意事项

1.Android发起http请求时,默认请求地址需https,需要增加 network-security-config 配置来允许使用http。(详见上面6.\res\xml\network_security_config.xml)

2.发起post提交时,往往需要做接口身份识别,需要将文本字段和图片字段一起提交,构造表单时,需要 "Content-Type", "multipart/form-data; boundary..."。

3.拍照时,默认只能取到缩略图,不够清晰,若要取到原图,需要在拍照时,传入指定保存位置,在回调函数中只需读取就可以了。

总结

以上所述是小编给大家介绍的Android 拍照选择图片并上传功能的实现思路(包含权限动态获取),希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对网站的支持!

如果你觉得本文对你有帮助,欢迎转载,烦请注明出处,谢谢!

以上是 Android 拍照选择图片并上传功能的实现思路(包含权限动态获取) 的全部内容, 来源链接: utcz.com/p/242271.html

回到顶部