欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  移动技术

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

程序员文章站 2022-04-20 13:32:14
作为一个android新手,想实现手机拍照并上传的功能,经过查找资料,已实现此功能。在此记录备忘。老鸟请忽略。 一、实现思路: 1.android手机客户端,拍照(或选择图片),然...

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

一、实现思路:

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

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

二、实现步骤:

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

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

项目结构:

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

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 拍照选择图片并上传功能的实现思路(包含权限动态获取),希望对大家有所帮助