Android 開発 8.0 以降 カメラ/アルバムを呼び出し、Uri に従って画像の絶対パスを取得し、ファイルをアップロードします

1.許可の問題

  1. 考えられる問題

requires android.permission.READ_EXTERNAL_STORAGE, or grantUriPermission()
Permission Denial: reading com.android.providers.media.MediaProvider
  1. 権限を追加する

まず、AndroidManifest.xml ルート ノードの下に次の権限を追加します。これは、主にネットワーク、カメラ、および読み取りと書き込みの権限にアクセスするためのものです。

<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.CAMERA"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
  1. 許可を動的に要求する

新バージョンのandoridでは、上記のパーミッション申請後も動的にパーミッション申請を行う必要があるため、申請に必要なインターフェースのonCreateメソッドに以下のコードを追加してください。

 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
            if (checkSelfPermission(Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) {
                requestPermissions(new String[] {Manifest.permission.CAMERA}, 1);
            }
        }

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
            if (checkSelfPermission(Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
                requestPermissions(new String[] {Manifest.permission.READ_EXTERNAL_STORAGE}, 1);
            }
        }

2. カメラを呼び出す

  1. プロバイダーを宣言する

まず、アクティビティと同じレベルで、メイン構成ファイルでプロバイダーを宣言する必要があります。プロバイダーを使用する理由は、Android 7.0 以降、file:// メソッドを使用してアプリ間でファイルを渡すことが許可されていないためです。それ以外の場合は例外がスローされ、プロバイダーの役割はコンテンツを使用することです。 :// のパターンは file:// を置き換えます。これは単なるプレフィックスの変更のように見えますが、実際には実際のパスが仮想パスに変換されます。

<provider
            android:authorities="com.example.yourpackage.provider"
            android:name="androidx.core.content.FileProvider"
            android:grantUriPermissions="true">
            <meta-data
                android:name="android.support.FILE_PROVIDER_PATHS"
                android:resource="@xml/file_paths"/>
        </provider>

file_paths の内容

<paths>
    <external-path
        name = "photo"
        path = "/"/>
</paths>
  1. カメラを呼び出す

最初にカメラ画像を保存するファイルを作成し、さまざまなシステム バージョンに従って Uri を取得し、それを Intent に渡し、カメラをアクティブにします (outputImage と imageUri をグローバル変数として設定することを検討できます)。

int REQUEST_CODE = 1;  //事件请求CODE为1
outputImage = new File(getExternalFilesDir(Environment.DIRECTORY_PICTURES),"last.jpg");
if (outputImage.exists())
    outputImage.delete();
    try {
        outputImage.createNewFile();
    } catch (IOException e) {
        e.printStackTrace();
    }
//注意com.example.yourpackage.provider要和provider声明中的一致
imageUri = (Build.VERSION.SDK_INT>=Build.VERSION_CODES.N) ? FileProvider.getUriForFile(context,"com.example.yourpackage.provider",outputImage) : Uri.fromFile(outputImage);
Intent intent1 = new Intent("android.media.action.IMAGE_CAPTURE");
intent1.putExtra(MediaStore.EXTRA_OUTPUT, imageUri);
startActivityForResult(intent1,REQUEST_CODE);
  1. コールバックを処理する

BitmapFactory を使用して imageUri を読み取り、ビットマップを取得し、圧縮してから表示します。

if (resultCode == Activity.RESULT_OK) {
    ContentResolver contentResolver = getContentResolver();
    Bitmap bitmap = null;
    try {
        bitmap = BitmapFactory.decodeStream(contentResolver.openInputStream(imageUri));
        Log.i("TAG", "从相册回传bitmap:"+bitmap.getWidth());
        Bitmap bitmap2 = Bitmap.createScaledBitmap(bitmap,  bitmap.getWidth()/10 ,bitmap.getHeight()/10, true);
        img_imgview.setImageBitmap(bitmap2);
        this.flag = 1;
    } catch (FileNotFoundException e) {
        e.printStackTrace();
    }
}
  1. 画像の絶対パスを取得する

outputImage を使用して、アップロードまたはその他の操作の絶対パスを取得します。

outputImage.getAbsolutePath()

3. アルバムを呼び出す

  1. 通話アルバム

nt REQUEST_CODE = 2;
Intent intent1 = new Intent(Intent.ACTION_OPEN_DOCUMENT);
intent1.addCategory(Intent.CATEGORY_OPENABLE);
intent1.setType("image/*");
startActivityForResult(intent1,REQUEST_CODE);
  1. コールバックを処理する

if (resultCode == Activity.RESULT_OK && data != null) {
    if (data.getData()!=null) {
        ContentResolver contentResolver = getContentResolver();
        Bitmap bitmap = null;
        try {
            imageUri = data.getData();
            bitmap = BitmapFactory.decodeStream(contentResolver.openInputStream(data.getData()));
            Log.i("TAG", "从相册回传bitmap:"+bitmap.getWidth());
            Bitmap bitmap2 = Bitmap.createScaledBitmap(bitmap,  bitmap.getWidth()/10 ,bitmap.getHeight()/10, true);
            img_imgview.setImageBitmap(bitmap2);
            this.flag = 2;
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        }
    }
}
  1. 画像の絶対パスを取得する

ここでは多くの問題が発生します。詳細な方法については 4 を参照してください。

FileHelper.getFileAbsolutePath(context, imageUri)

4.Uriからファイルの絶対パスを取得する

これは、Uri に基づいて絶対パスを取得するための完全なヘルパー クラスです。

public class FileHelper {

    /**
     * 根据Uri获取文件绝对路径,解决Android4.4以上版本Uri转换
     *
     * @param context
     * @param imageUri
     */
    public static String getFileAbsolutePath(Context context, Uri imageUri) {
        if (context == null || imageUri == null) {
            return null;
        }

        if (android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.KITKAT) {
            return getRealFilePath(context, imageUri);
        }

        if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.KITKAT && android.os.Build.VERSION.SDK_INT < Build.VERSION_CODES.Q && DocumentsContract.isDocumentUri(context, imageUri)) {
            if (isExternalStorageDocument(imageUri)) {
                String docId = DocumentsContract.getDocumentId(imageUri);
                String[] split = docId.split(":");
                String type = split[0];
                if ("primary".equalsIgnoreCase(type)) {
                    return Environment.getExternalStorageDirectory() + "/" + split[1];
                }
            } else if (isDownloadsDocument(imageUri)) {
                String id = DocumentsContract.getDocumentId(imageUri);
                Uri contentUri = ContentUris.withAppendedId(Uri.parse("content://downloads/public_downloads"), Long.valueOf(id));
                return getDataColumn(context, contentUri, null, null);
            } else if (isMediaDocument(imageUri)) {
                String docId = DocumentsContract.getDocumentId(imageUri);
                String[] split = docId.split(":");
                String type = split[0];
                Uri contentUri = null;
                if ("image".equals(type)) {
                    contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
                } else if ("video".equals(type)) {
                    contentUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI;
                } else if ("audio".equals(type)) {
                    contentUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;
                }
                String selection = MediaStore.Images.Media._ID + "=?";
                String[] selectionArgs = new String[]{split[1]};
                return getDataColumn(context, contentUri, selection, selectionArgs);
            }
        } // MediaStore (and general)
        if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q){
            return uriToFileApiQ(context,imageUri);
        }
        else if ("content".equalsIgnoreCase(imageUri.getScheme())) {
            // Return the remote address
            if (isGooglePhotosUri(imageUri)) {
                return imageUri.getLastPathSegment();
            }
            return getDataColumn(context, imageUri, null, null);
        }
        // File
        else if ("file".equalsIgnoreCase(imageUri.getScheme())) {
            return imageUri.getPath();
        }
        return null;
    }

    //此方法 只能用于4.4以下的版本
    private static String getRealFilePath(final Context context, final Uri uri) {
        if (null == uri) {
            return null;
        }
        final String scheme = uri.getScheme();
        String data = null;
        if (scheme == null) {
            data = uri.getPath();
        } else if (ContentResolver.SCHEME_FILE.equals(scheme)) {
            data = uri.getPath();
        } else if (ContentResolver.SCHEME_CONTENT.equals(scheme)) {
            String[] projection = {MediaStore.Images.ImageColumns.DATA};
            Cursor cursor = context.getContentResolver().query(uri, projection, null, null, null);

//            Cursor cursor = context.getContentResolver().query(uri, new String[]{MediaStore.Images.ImageColumns.DATA}, null, null, null);
            if (null != cursor) {
                if (cursor.moveToFirst()) {
                    int index = cursor.getColumnIndex(MediaStore.Images.ImageColumns.DATA);
                    if (index > -1) {
                        data = cursor.getString(index);
                    }
                }
                cursor.close();
            }
        }
        return data;
    }


    /**
     * @param uri The Uri to check.
     * @return Whether the Uri authority is ExternalStorageProvider.
     */
    private static boolean isExternalStorageDocument(Uri uri) {
        return "com.android.externalstorage.documents".equals(uri.getAuthority());
    }

    /**
     * @param uri The Uri to check.
     * @return Whether the Uri authority is DownloadsProvider.
     */
    private static boolean isDownloadsDocument(Uri uri) {
        return "com.android.providers.downloads.documents".equals(uri.getAuthority());
    }

    private static String getDataColumn(Context context, Uri uri, String selection, String[] selectionArgs) {
        Cursor cursor = null;
        String column = MediaStore.Images.Media.DATA;
        String[] projection = {column};
        try {
            cursor = context.getContentResolver().query(uri, projection, selection, selectionArgs, null);
            if (cursor != null && cursor.moveToFirst()) {
                int index = cursor.getColumnIndexOrThrow(column);
                return cursor.getString(index);
            }
        } finally {
            if (cursor != null) {
                cursor.close();
            }
        }
        return null;
    }

    /**
     * @param uri The Uri to check.
     * @return Whether the Uri authority is MediaProvider.
     */
    private static boolean isMediaDocument(Uri uri) {
        return "com.android.providers.media.documents".equals(uri.getAuthority());
    }

    /**
     * @param uri The Uri to check.
     * @return Whether the Uri authority is Google Photos.
     */
    private static boolean isGooglePhotosUri(Uri uri) {
        return "com.google.android.apps.photos.content".equals(uri.getAuthority());
    }


    /**
     * Android 10 以上适配 另一种写法
     * @param context
     * @param uri
     * @return
     */
    public static String getFileFromContentUri(Context context, Uri uri) {
        if (uri == null) {
            return null;
        }
        String filePath;
        String[] filePathColumn = {MediaStore.MediaColumns.DATA, MediaStore.MediaColumns.DISPLAY_NAME};
        ContentResolver contentResolver = context.getContentResolver();
        Cursor cursor = contentResolver.query(uri, filePathColumn, null,
                null, null);
        if (cursor != null) {
            cursor.moveToFirst();
            try {
                filePath = cursor.getString(cursor.getColumnIndex(filePathColumn[0]));
                return filePath;
            } catch (Exception e) {
            } finally {
                cursor.close();
            }
        }
        return "";
    }

    /**
     * Android 10 以上适配
     * @param context
     * @param uri
     * @return
     */
    @RequiresApi(api = Build.VERSION_CODES.Q)
    private static String uriToFileApiQ(Context context, Uri uri) {
        File file = null;
        //android10以上转换
        if (uri.getScheme().equals(ContentResolver.SCHEME_FILE)) {
            file = new File(uri.getPath());
        } else if (uri.getScheme().equals(ContentResolver.SCHEME_CONTENT)) {
            //把文件复制到沙盒目录
            ContentResolver contentResolver = context.getContentResolver();
            Cursor cursor = contentResolver.query(uri, null, null, null, null);
            if (cursor.moveToFirst()) {
                String displayName = cursor.getString(cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME));
                try {
                    InputStream is = contentResolver.openInputStream(uri);
                    File cache = new File(context.getExternalCacheDir().getAbsolutePath(), Math.round((Math.random() + 1) * 1000) + displayName);
                    FileOutputStream fos = new FileOutputStream(cache);
                    FileUtils.copy(is, fos);
                    file = cache;
                    fos.close();
                    is.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        return file.getAbsolutePath();
    }
}

五、httpリクエストとアップロード

  1. okhttp を介してインターフェイスを呼び出す

モジュールの build.gradle を変更し、依存関係の構成に次の行を追加します。

implementation 'com.squareup.okhttp3:okhttp:4.9.1
  1. リクエストを取得

OkHttpClient client = new OkHttpClient(); // 创建一个okhttp客户端对象
// 创建一个GET方式的请求结构
xRequest request = new Request.Builder()
    //.get() // 因为OkHttp默认采用get方式,所以这里可以不调get方法
    .header("Accept-Language", "zh-CN") // 给http请求添加头部信息
    .url("http://192.168.1.104:5291/myApi/GetList") // 指定http请求的调用地址
    .build();
Call call = client.newCall(request); // 根据请求结构创建调用对象
// 加入HTTP请求队列。异步调用,并设置接口应答的回调方法
call.enqueue(new Callback() {
    @Override
    public void onFailure(Call call, IOException e) { // 请求失败
        // 回到主线程操纵界面
        runOnUiThread(() -> {
            Toast toast= Toast.makeText(context, "默认失败的Toast" + e.getMessage().toString(), Toast.LENGTH_SHORT);
            toast.show();
        });
    }

    @Override
    public void onResponse(Call call, final Response response) throws IOException { // 请求成功
        String resp = response.body().string();
        // 回到主线程操纵界面
        runOnUiThread(() -> {
            Toast toast= Toast.makeText(context, "默认成功的的Toast" + resp, Toast.LENGTH_SHORT);
            toast.show();
        });
    }
});
  1. フォームから投稿してファイルとデータをアップロードする

//文件
File file = new File(picUrl);
//请求体
MultipartBody.Builder builder = new MultipartBody.Builder();
builder.setType(MultipartBody.FORM);
//数据1
builder.addFormDataPart("date", date);
//数据2
builder.addFormDataPart("banzu", banzu);
//文件,注意名称,这里是files,后台需要用这个名字接数据
builder.addFormDataPart("files", file.getName(), RequestBody.create(MediaType.parse("image/jpeg"), file));
MultipartBody body = builder.build();

// 创建一个okhttp客户端对象,设置超时
OkHttpClient client = new OkHttpClient().newBuilder()
        .connectTimeout(30, TimeUnit.SECONDS)
        .readTimeout(60, TimeUnit.SECONDS).build();

// 创建一个POST方式的请求结构
Request request = new Request.Builder().post(body).url("http://192.168.1.100:5290/upLoadFile").build();
Call call = client.newCall(request); // 根据请求结构创建调用对象
// 加入HTTP请求队列。异步调用,并设置接口应答的回调方法
call.enqueue(new Callback() {
    @Override
    public void onFailure(Call call, IOException e) { // 请求失败
        // 回到主线程操纵界面
        runOnUiThread(() -> {
            Toast toast= Toast.makeText(context, "默认失败的Toast" + e.getMessage().toString(), Toast.LENGTH_SHORT);
            toast.show();
            //关闭loading
            dialog.dismiss();
        });
    }

    @Override
    public void onResponse(Call call, final Response response) throws IOException { // 请求成功
        String resp = response.body().string();
        // 回到主线程操纵界面
        runOnUiThread(() -> {
            Toast toast= Toast.makeText(context, "默认成功的Toast", Toast.LENGTH_SHORT);
            toast.show();
            //关闭loading
            dialog.dismiss();
        });
    }
});
  1. JSON データを投稿する

private void postJson() {
        String username = et_username.getText().toString();
        String password = et_password.getText().toString();
        String jsonString = "";
        try {
            JSONObject jsonObject = new JSONObject();
            jsonObject.put("username", username);
            jsonObject.put("password", password);
            jsonString = jsonObject.toString();
        } catch (Exception e) {
            e.printStackTrace();
        }
        // 创建一个POST方式的请求结构
        RequestBody body = RequestBody.create(jsonString, MediaType.parse("text/plain;charset=utf-8"));
        OkHttpClient client = new OkHttpClient(); // 创建一个okhttp客户端对象
        Request request = new Request.Builder().post(body).url(URL_LOGIN).build();
        Call call = client.newCall(request); // 根据请求结构创建调用对象
        // 加入HTTP请求队列。异步调用,并设置接口应答的回调方法
        call.enqueue(new Callback() {
            @Override
            public void onFailure(Call call, IOException e) { // 请求失败
                // 回到主线程操纵界面
                runOnUiThread(() -> tv_result.setText("调用登录接口报错:"+e.getMessage()));
            }
 
            @Override
            public void onResponse(Call call, final Response response) throws IOException { // 请求成功.setText("调用登录接口返回:\n"+resp));
            }
        });
    }

おすすめ

転載: blog.csdn.net/bashendixie5/article/details/129745348
おすすめ