Android 拍照選擇圖片并上傳功能的實(shí)現(xiàn)思路(包含權(quán)限動(dòng)態(tài)獲取)
作為一個(gè)Android新手,想實(shí)現(xiàn)手機(jī)拍照并上傳的功能,經(jīng)過(guò)查找資料,已實(shí)現(xiàn)此功能。在此記錄備忘。老鳥(niǎo)請(qǐng)忽略。
一、實(shí)現(xiàn)思路:
1.Android手機(jī)客戶(hù)端,拍照(或選擇圖片),然后上傳到服務(wù)器。
2.服務(wù)器端接收手機(jī)端上傳上來(lái)的圖片。
二、實(shí)現(xiàn)步驟:
1.按慣例,先放效果圖:
項(xiàng)目結(jié)構(gòu):
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="圖片預(yù)覽" /> <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;//權(quán)限請(qǐng)求碼
//權(quán)限集合,對(duì)應(yīng)在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才用動(dòng)態(tài)權(quán)限
if (Build.VERSION.SDK_INT >= 23) {
checkPermission();
}
btnPhoto = findViewById(R.id.btnPhoto);
btnSelect = findViewById(R.id.btnSelect);
btnPhoto.setOnClickListener(this);
btnSelect.setOnClickListener(this);
}
//檢查權(quán)限
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) {
//說(shuō)明權(quán)限都已經(jīng)通過(guò),可以做你想做的事情去
} else {
//存在未允許的權(quán)限
ActivityCompat.requestPermissions(this, permissions, permissionCode);
}
}
//授權(quán)后回調(diào)函數(shù)
@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) {
//跳轉(zhuǎn)到系統(tǒng)設(shè)置權(quán)限頁(yè)面,或者直接關(guān)閉頁(yè)面,不讓他繼續(xù)訪問(wèn)
permissionDialog();
} else {
//全部權(quán)限通過(guò),可以進(jìn)行下一步操作
}
}
}
//打開(kāi)手動(dòng)設(shè)置應(yīng)用權(quán)限
private void permissionDialog() {
if (alertDialog == null) {
alertDialog = new AlertDialog.Builder(this)
.setTitle("提示信息")
.setMessage("當(dāng)前應(yīng)用缺少必要權(quán)限,該功能暫時(shí)無(wú)法使用。如若需要,請(qǐng)單擊【確定】按鈕前往設(shè)置中心進(jìn)行權(quán)限授權(quán)。")
.setPositiveButton("設(shè)置", 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();
}
//用戶(hù)取消授權(quán)
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); //指定拍照后的存儲(chǔ)路徑,保存原圖
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) {
// 調(diào)用照相機(jī)拍照
case CAMERA:
if (resultCode == RESULT_OK) {
//對(duì)應(yīng)方法一:圖片未保存,需保存文件到本地
// 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();
// }
//對(duì)應(yīng)方法二:圖片已保存,只需讀取就行了
try {
FileInputStream stream = new FileInputStream(photoPath);
Bitmap bitmap = BitmapFactory.decodeStream(stream);
//預(yù)覽圖片
ImageView image = findViewById(R.id.imageView);
image.setImageBitmap(bitmap);
//上傳圖片(Android 4.0 之后不能在主線程中請(qǐng)求HTTP請(qǐng)求)
File file = new File(photoPath);
if (file.exists()) {
new Thread(new Runnable() {
@Override
public void run() {
//文本字段(用于驗(yàn)證用戶(hù)身份)
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;
// 選擇圖片庫(kù)的圖片
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);
//預(yù)覽圖片
ImageView image = findViewById(R.id.imageView);
image.setImageBitmap(bitmap);
//上傳圖片(Android 4.0 之后不能在主線程中請(qǐng)求HTTP請(qǐng)求)
File file = new File(photoPath);
if (file.exists()) {
new Thread(new Runnable() {
@Override
public void run() {
//文本字段(用于驗(yàn)證用戶(hù)身份)
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頭和上傳文件內(nèi)容的分隔符
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();
// 讀取返回?cái)?shù)據(jù)
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("發(fā)送POST請(qǐng)求出錯(cuò)。" + posturl);
e.printStackTrace();
} finally {
if (conn != null) {
conn.disconnect();
conn = null;
}
}
return res;
}
}
4.輔助類(lèi) 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 路徑輔助類(lèi)
public class PathHelper {
//適配api19以下(不包括api19),根據(jù)uri獲取圖片的絕對(duì)路徑
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),根據(jù)uri獲取圖片的絕對(duì)路徑
*
* @param context 上下文對(duì)象
* @param uri 圖片的Uri
* @return 如果Uri對(duì)應(yīng)的圖片存在, 那么返回該圖片的絕對(duì)路徑, 否則返回null
*/
private static String getRealPathFromUriBelowAPI19(Context context, Uri uri) {
return getDataColumn(context, uri, null, null);
}
/**
* 適配api19及以上,根據(jù)uri獲取圖片的絕對(duì)路徑
*
* @param context 上下文對(duì)象
* @param uri 圖片的Uri
* @return 如果Uri對(duì)應(yīng)的圖片存在, 那么返回該圖片的絕對(duì)路徑, 否則返回null
*/
@SuppressLint("NewApi")
private static String getRealPathFromUriAboveApi19(Context context, Uri uri) {
String filePath = null;
if (DocumentsContract.isDocumentUri(context, uri)) {
// 如果是document類(lèi)型的 uri, 則通過(guò)document id來(lái)進(jìn)行處理
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 類(lèi)型的 Uri
filePath = getDataColumn(context, uri, null, null);
} else if ("file".equals(uri.getScheme())) {
// 如果是 file 類(lèi)型的 Uri,直接獲取圖片對(duì)應(yīng)的路徑
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"> <!-- 因?yàn)榕恼招枰獙?xiě)入文件 所以需要申請(qǐng)讀取內(nèi)存的權(quán)限 --> <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.服務(wù)器端接收(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("抱歉,用戶(hù)名和密碼錯(cuò)誤!");
}
//todo:身份驗(yàn)證
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}");
}
三、注意事項(xiàng)
1.Android發(fā)起http請(qǐng)求時(shí),默認(rèn)請(qǐng)求地址需https,需要增加 network-security-config 配置來(lái)允許使用http。(詳見(jiàn)上面6.\res\xml\network_security_config.xml)
2.發(fā)起post提交時(shí),往往需要做接口身份識(shí)別,需要將文本字段和圖片字段一起提交,構(gòu)造表單時(shí),需要 "Content-Type", "multipart/form-data; boundary..."。
3.拍照時(shí),默認(rèn)只能取到縮略圖,不夠清晰,若要取到原圖,需要在拍照時(shí),傳入指定保存位置,在回調(diào)函數(shù)中只需讀取就可以了。
總結(jié)
以上所述是小編給大家介紹的Android 拍照選擇圖片并上傳功能的實(shí)現(xiàn)思路(包含權(quán)限動(dòng)態(tài)獲取),希望對(duì)大家有所幫助,如果大家有任何疑問(wèn)請(qǐng)給我留言,小編會(huì)及時(shí)回復(fù)大家的。在此也非常感謝大家對(duì)我們網(wǎng)站的支持!
如果你覺(jué)得本文對(duì)你有幫助,歡迎轉(zhuǎn)載,煩請(qǐng)注明出處,謝謝!
上一篇:android studio 的下拉菜單Spinner使用詳解
欄 目:Android
下一篇:Android利用碎片fragment實(shí)現(xiàn)底部標(biāo)題欄(Github模板開(kāi)源)
本文標(biāo)題:Android 拍照選擇圖片并上傳功能的實(shí)現(xiàn)思路(包含權(quán)限動(dòng)態(tài)獲取)
本文地址:http://www.jygsgssxh.com/a1/Android/9010.html
您可能感興趣的文章
- 01-10Android自定義View之繪制圓形頭像功能
- 01-10Android實(shí)現(xiàn)雙擊返回鍵退出應(yīng)用實(shí)現(xiàn)方法詳解
- 01-10android實(shí)現(xiàn)記住用戶(hù)名和密碼以及自動(dòng)登錄
- 01-10android實(shí)現(xiàn)簡(jiǎn)單計(jì)算器功能
- 01-10Android 友盟第三方登錄與分享的實(shí)現(xiàn)代碼
- 01-10android實(shí)現(xiàn)指紋識(shí)別功能
- 01-10Emoji表情在Android JNI中的兼容性問(wèn)題詳解
- 01-10Android實(shí)現(xiàn)圓形漸變加載進(jìn)度條
- 01-10android開(kāi)發(fā)環(huán)境中SDK文件夾下的所需內(nèi)容詳解
- 01-10android異步消息機(jī)制 源碼層面徹底解析(1)


閱讀排行
- 1C語(yǔ)言 while語(yǔ)句的用法詳解
- 2java 實(shí)現(xiàn)簡(jiǎn)單圣誕樹(shù)的示例代碼(圣誕
- 3利用C語(yǔ)言實(shí)現(xiàn)“百馬百擔(dān)”問(wèn)題方法
- 4C語(yǔ)言中計(jì)算正弦的相關(guān)函數(shù)總結(jié)
- 5c語(yǔ)言計(jì)算三角形面積代碼
- 6什么是 WSH(腳本宿主)的詳細(xì)解釋
- 7C++ 中隨機(jī)函數(shù)random函數(shù)的使用方法
- 8正則表達(dá)式匹配各種特殊字符
- 9C語(yǔ)言十進(jìn)制轉(zhuǎn)二進(jìn)制代碼實(shí)例
- 10C語(yǔ)言查找數(shù)組里數(shù)字重復(fù)次數(shù)的方法
本欄相關(guān)
- 01-10Android自定義View之繪制圓形頭像功能
- 01-10Android實(shí)現(xiàn)雙擊返回鍵退出應(yīng)用實(shí)現(xiàn)方
- 01-10android實(shí)現(xiàn)簡(jiǎn)單計(jì)算器功能
- 01-10android實(shí)現(xiàn)記住用戶(hù)名和密碼以及自動(dòng)
- 01-10C++自定義API函數(shù)實(shí)現(xiàn)大數(shù)相乘算法
- 01-10Android 友盟第三方登錄與分享的實(shí)現(xiàn)代
- 01-10android實(shí)現(xiàn)指紋識(shí)別功能
- 01-10如何給Flutter界面切換實(shí)現(xiàn)點(diǎn)特效
- 01-10Android實(shí)現(xiàn)圓形漸變加載進(jìn)度條
- 01-10Emoji表情在Android JNI中的兼容性問(wèn)題詳
隨機(jī)閱讀
- 08-05dedecms(織夢(mèng))副欄目數(shù)量限制代碼修改
- 08-05DEDE織夢(mèng)data目錄下的sessions文件夾有什
- 01-10使用C語(yǔ)言求解撲克牌的順子及n個(gè)骰子
- 01-10C#中split用法實(shí)例總結(jié)
- 01-10SublimeText編譯C開(kāi)發(fā)環(huán)境設(shè)置
- 01-11ajax實(shí)現(xiàn)頁(yè)面的局部加載
- 08-05織夢(mèng)dedecms什么時(shí)候用欄目交叉功能?
- 01-10delphi制作wav文件的方法
- 01-11Mac OSX 打開(kāi)原生自帶讀寫(xiě)NTFS功能(圖文
- 04-02jquery與jsp,用jquery


