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自定义字母导航栏
下一篇: Android 简单实现倒计时功能