Android9上,调用系统相机拍照后,相册中不显示,第三方软件也读取不到

一、问题:我的app调用系统拍照功能,然后使用知乎的Matisse框架加载自己定义的文件夹中的图片,发现怎么也不显示。

二、原因:Android给图片创建了一个数据库,我们需要手动去刷新这个数据库,把刚刚拍摄的照片信息放到数据库中,其它的软件才可以获取到图片。否则只有自己找到那个路径去拿图片了。

三、先看实现:

package com.ysl.photo;

import android.Manifest;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.os.Environment;
import android.provider.MediaStore;
import android.text.TextUtils;

import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.content.FileProvider;

import com.tbruyelle.rxpermissions2.RxPermissions;
import com.ysl.myandroidbase.R;

import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.List;

import io.reactivex.disposables.Disposable;

public class TakePhotoActivity extends AppCompatActivity {
    private String path;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_takephoto);
        Disposable subscribe = new RxPermissions(this).request(Manifest.permission.CAMERA, Manifest.permission.WRITE_EXTERNAL_STORAGE)
                .subscribe(aBoolean -> {
                    if (aBoolean) {
                        findViewById(R.id.take_photo).setOnClickListener(v -> takePhoto());
                    } else {
                    }
                });
    }

    private void takePhoto() {
        Intent openCameraIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
        File file = new File(getPictureDirPath().getAbsolutePath(),
                System.currentTimeMillis() + ".jpg");
        path = file.getAbsolutePath();
        Uri imageUri = FileProvider.getUriForFile(getApplicationContext(),
                "com.ysl.myandroidbase.fileprovider", file);
        System.out.println("-------->"+imageUri);
        //-------->content://com.ysl.myandroidbase.fileprovider/m/MyAndroidBase/mybase/1574394370534.jpg
        System.out.println("-------->"+path);
        //-------->/storage/emulated/0/MyAndroidBase/mybase/1574394370534.jpg
        openCameraIntent.putExtra(MediaStore.EXTRA_OUTPUT, imageUri);
        openCameraIntent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
            List<ResolveInfo> resInfoList = getApplicationContext().getPackageManager()
                    .queryIntentActivities(openCameraIntent, PackageManager.MATCH_DEFAULT_ONLY);
            for (ResolveInfo resolveInfo : resInfoList) {
                String packageName = resolveInfo.activityInfo.packageName;
                getApplicationContext().grantUriPermission(packageName, imageUri,
                        Intent.FLAG_GRANT_WRITE_URI_PERMISSION
                                | Intent.FLAG_GRANT_READ_URI_PERMISSION);
            }
        }
        TakePhotoActivity.this.startActivityForResult(openCameraIntent, 0);
    }

    @Override
    public void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        switch (requestCode) {
            case 0:
                if (!TextUtils.isEmpty(path) && resultCode == -1) {
                    Bitmap mBitmap = BitmapFactory.decodeFile(path);
                    saveBitmapFile(mBitmap);
                }
                break;
            default:
                break;
        }
    }

    /**
     * 保存图片并发送广播通知数据库刷新,只有这样才能在相册里面看到这张照片
     * @param bitmap
     */
    public void saveBitmapFile(Bitmap bitmap){
        //将要保存图片的路径
        File file = new File(path);
        try {
            BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(file));
            bitmap.compress(Bitmap.CompressFormat.JPEG, 100, bos);
            bos.flush();
            bos.close();
        } catch (IOException e) {
            e.printStackTrace();
        }

        //发送广播给系统,刷新数据库
        Uri uri = Uri.fromFile(file);
        System.out.println("imageUri------>"+uri);
        //imageUri------>file:///storage/emulated/0/MyAndroidBase/mybase/1574394370534.jpg
        Intent localIntent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, uri);
        sendBroadcast(localIntent);


        //下面这种扫描的方法也可以刷新数据库
        new SingleMediaScanner(this.getApplicationContext(),
                getPictureDirPath().getAbsolutePath(),
                new SingleMediaScanner.ScanListener() {
                    @Override public void onScanFinish() {
                        Log.i("SingleMediaScanner", "scan finish!");
                    }
                });
    }
    public static File getPictureDirPath() {
        File mIVMSFolder = null;
        try {
            String path = Environment.getExternalStorageDirectory().getAbsolutePath()
                    + File.separator + "MyAndroidBase" + File.separator + "mybase";
            mIVMSFolder = new File(path);
            if (!mIVMSFolder.exists()) {
                mIVMSFolder.mkdirs();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return mIVMSFolder;
    }
}

以上就是核心代码:

调用相机时:

Uri imageUri = FileProvider.getUriForFile(getApplicationContext(),
        "com.ysl.myandroidbase.fileprovider", file);

使用这种方式来获取uri,因为使用文件的方法在高版本Android下报错;其中的第二个参数怎么来的:

在清单文件中:

<provider
    android:name="androidx.core.content.FileProvider"
    android:authorities="com.ysl.myandroidbase.fileprovider"
    android:exported="false"<!--exported:必须是false-->
    android:grantUriPermissions="true"><!--grantUriPermissions:必须是true,表示授予 URI 临时访问权限-->   
 <meta-data
        android:name="android.support.FILE_PROVIDER_PATHS"
        android:resource="@xml/file_paths_public"></meta-data>
</provider>
<!--resource:中的@xml/file_paths是我们接下来要添加的文件-->
<?xml version="1.0" encoding="utf-8"?>
<paths>
    <external-path
        name="m" <!--会被应用在uri中,下面打印的uri可以看到-->
        path="."/><!--会被应用在图片的保存路径中, 这里只用一个点,表示不改变之前自定义的路径-->
</paths>

 

看看打印的uri:

content://com.ysl.myandroidbase.fileprovider/m/MyAndroidBase/mybase/1574394370534.jpg

方便和后面对比;

在activity跳转回调中:

要保存图片并发送更新系统数据库的广播:

//发送广播给系统,刷新数据库
Uri uri = Uri.fromFile(file);
System.out.println("imageUri------>"+uri);
//imageUri------>file:///storage/emulated/0/MyAndroidBase/mybase/1574394370534.jpg
Intent localIntent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, uri);
sendBroadcast(localIntent);

这段代码就可以让相册发现刚刚拍摄的照片了;

注意这里一个坑:看这里获取的uri方法:Uri uri = Uri.fromFile(file);  再看打印的uri,是以file://+路径的方式。

file:///storage/emulated/0/MyAndroidBase/mybase/1574394370534.jpg

我们再看上面的uri:content://com.ysl.myandroidbase.fileprovider/m/MyAndroidBase/mybase/1574394370534.jpg

这两个是不一样的;我之前就是因为发送广播时使用了上面的uri,导致怎么都不起作用;后来才发现,这里已经存好文件了,就使用Uri.fromFile(file)来获取uri。

再往下看:

//下面这种扫描的方法也可以刷新数据库
new SingleMediaScanner(this.getApplicationContext(),
        getPictureDirPath().getAbsolutePath(),
        new SingleMediaScanner.ScanListener() {
            @Override public void onScanFinish() {
                Log.i("SingleMediaScanner", "scan finish!");
            }
        });

这段代码也能实现刷新数据库的功能,主要是引入一个实现MediaScannerConnection.MediaScannerConnectionClient的类:

package com.ysl.photo;

import android.content.Context;
import android.media.MediaScannerConnection;
import android.net.Uri;

/**
 * description:媒体扫描
 */
public class SingleMediaScanner implements MediaScannerConnection.MediaScannerConnectionClient {

    private MediaScannerConnection mMsc;
    private String mPath;
    private ScanListener mListener;

    public interface ScanListener {

        /**
         * scan finish
         */
        void onScanFinish();
    }

    public SingleMediaScanner(Context context, String mPath, ScanListener mListener) {
        this.mPath = mPath;
        this.mListener = mListener;
        this.mMsc = new MediaScannerConnection(context, this);
        this.mMsc.connect();
    }

    @Override public void onMediaScannerConnected() {
        mMsc.scanFile(mPath, null);
    }

    @Override public void onScanCompleted(String mPath, Uri mUri) {
        mMsc.disconnect();
        if (mListener != null) {
            mListener.onScanFinish();
        }
    }
}

可以看到,他是Android的media库中的一个接口,然后在连接上之后,调用扫描文件的方法,扫描指定的路径下的文件。这样图片也就被扫描出来了。

 

 

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
首先需要在AndroidManifest.xml文件添加相机和存储权限: ```xml <uses-permission android:name="android.permission.CAMERA" /> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> ``` 然后创建一个布局文件activity_main.xml,包含一个按钮和一个RecyclerView: ```xml <?xml version="1.0" encoding="utf-8"?> <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent" android:layout_height="match_parent"> <Button android:id="@+id/btn_take_photo" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Take photo" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" /> <androidx.recyclerview.widget.RecyclerView android:id="@+id/rv_photos" android:layout_width="0dp" android:layout_height="0dp" app:layout_constraintBottom_toTopOf="@+id/btn_take_photo" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" /> </androidx.constraintlayout.widget.ConstraintLayout> ``` 接下来创建一个Photo实体类,用于存储照片信息: ```java public class Photo { private int id; private String imagePath; public Photo(int id, String imagePath) { this.id = id; this.imagePath = imagePath; } public int getId() { return id; } public String getImagePath() { return imagePath; } } ``` 然后创建一个PhotosAdapter类,用于RecyclerView的显示: ```java public class PhotosAdapter extends RecyclerView.Adapter<PhotosAdapter.ViewHolder> { private List<Photo> photos; public PhotosAdapter(List<Photo> photos) { this.photos = photos; } public void setPhotos(List<Photo> photos) { this.photos = photos; notifyDataSetChanged(); } @NonNull @Override public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_photo, parent, false); return new ViewHolder(view); } @Override public void onBindViewHolder(@NonNull ViewHolder holder, int position) { Photo photo = photos.get(position); Glide.with(holder.itemView.getContext()) .load(photo.getImagePath()) .into(holder.ivPhoto); } @Override public int getItemCount() { return photos.size(); } static class ViewHolder extends RecyclerView.ViewHolder { ImageView ivPhoto; ViewHolder(@NonNull View itemView) { super(itemView); ivPhoto = itemView.findViewById(R.id.iv_photo); } } } ``` 接下来创建一个MainActivity类,实现拍照、保存到数据库和显示相册的功能: ```java public class MainActivity extends AppCompatActivity { private static final int REQUEST_CODE_TAKE_PHOTO = 1; private SQLiteDatabase db; private PhotosAdapter adapter; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); db = new DBHelper(this).getWritableDatabase(); adapter = new PhotosAdapter(new ArrayList<>()); RecyclerView rvPhotos = findViewById(R.id.rv_photos); rvPhotos.setLayoutManager(new GridLayoutManager(this, 3)); rvPhotos.setAdapter(adapter); Button btnTakePhoto = findViewById(R.id.btn_take_photo); btnTakePhoto.setOnClickListener(v -> { Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE); if (intent.resolveActivity(getPackageManager()) != null) { startActivityForResult(intent, REQUEST_CODE_TAKE_PHOTO); } }); showPhotos(); } @Override protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) { super.onActivityResult(requestCode, resultCode, data); if (requestCode == REQUEST_CODE_TAKE_PHOTO && resultCode == RESULT_OK && data != null) { Bitmap bitmap = (Bitmap) data.getExtras().get("data"); if (bitmap != null) { String imagePath = saveImage(bitmap); if (imagePath != null) { addPhoto(imagePath); showPhotos(); } else { Toast.makeText(this, "Failed to save image", Toast.LENGTH_SHORT).show(); } } else { Toast.makeText(this, "Failed to get image", Toast.LENGTH_SHORT).show(); } } } private String saveImage(Bitmap bitmap) { String fileName = "IMG_" + new SimpleDateFormat("yyyyMMdd_HHmmss", Locale.US).format(new Date()) + ".jpg"; File file = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES), fileName); try (FileOutputStream fos = new FileOutputStream(file)) { bitmap.compress(Bitmap.CompressFormat.JPEG, 100, fos); return file.getAbsolutePath(); } catch (IOException e) { e.printStackTrace(); return null; } } private void addPhoto(String imagePath) { ContentValues contentValues = new ContentValues(); contentValues.put(DBHelper.COLUMN_IMAGE_PATH, imagePath); db.insert(DBHelper.TABLE_NAME, null, contentValues); } private void showPhotos() { List<Photo> photos = new ArrayList<>(); Cursor cursor = db.query(DBHelper.TABLE_NAME, null, null, null, null, null, null); if (cursor.moveToFirst()) { do { int id = cursor.getInt(cursor.getColumnIndex(DBHelper.COLUMN_ID)); String imagePath = cursor.getString(cursor.getColumnIndex(DBHelper.COLUMN_IMAGE_PATH)); photos.add(new Photo(id, imagePath)); } while (cursor.moveToNext()); } cursor.close(); adapter.setPhotos(photos); } } ``` 最后在res文件夹下创建一个布局文件item_photo.xml,用于显示每个照片: ```xml <?xml version="1.0" encoding="utf-8"?> <androidx.cardview.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="100dp" android:layout_height="100dp" android:layout_margin="4dp" android:clickable="true" android:focusable="true" android:foreground="?android:attr/selectableItemBackground"> <ImageView android:id="@+id/iv_photo" android:layout_width="match_parent" android:layout_height="match_parent" android:scaleType="centerCrop" /> </androidx.cardview.widget.CardView> ``` 这样就完成了调用系统相机拍照,照片储存到数据库之,并且获取相册路径,在recyclerview显示出来的功能。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值