1. 程式人生 > 程式設計 >Android 拍照選擇圖片並上傳功能的實現思路(包含許可權動態獲取)

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,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("取消",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,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,width,height,matrix,false);
 return newBM;
 }
 //POST 表單提交
 @RequiresApi(api = Build.VERSION_CODES.O)
 public static String formUpload(String posturl,Map<String,String> textMap,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,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及以上,否則返回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);
 }
 } else if ("content".equalsIgnoreCase(uri.getScheme())) {
 // 如果是 content 型別的 Uri
 filePath = getDataColumn(context,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,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 拍照選擇圖片並上傳功能的實現思路(包含許可權動態獲取),希望對大家有所幫助,如果大家有任何疑問請給我留言,小編會及時回覆大家的。在此也非常感謝大家對我們網站的支援!
如果你覺得本文對你有幫助,歡迎轉載,煩請註明出處,謝謝!