赞
踩
作为一个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.拍照时,默认只能取到缩略图,不够清晰,若要取到原图,需要在拍照时,传入指定保存位置,在回调函数中只需读取就可以了。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。