Android 外部存储App私有目录下照片和视频显示到相册

本文介绍了一种使Android应用私有目录下的图片和视频在系统相册中显示的方法,通过自定义工具类实现扫描和插入MediaStore操作,确保不同Android版本下(包括Android10分区存储)的兼容性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

    对于外部存储中,APP私有目录下的图片或者视频,一般情况下,系统自带相册中是不会显示的,如果想要将其显示出来,可以通过下面的方法,对于适配Android 10的分区存储也是很方便的一种选择。

    Android中的存储简单展示如下:这里说的APP私有目录,也就是指最下面部分。

下面是具体的方法

一、辅助工具类

package com.jlpay.partner.utils;

import android.content.ContentValues;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.os.Build;
import android.provider.MediaStore;
import android.util.Log;

import java.io.File;

public class AlbumNotifyHelper {

    public static final String TAG = AlbumNotifyHelper.class.getSimpleName();


    ///////////////////////////////////////////////////////////////////////////
    // 下面是对外公开的重载的方法
    ///////////////////////////////////////////////////////////////////////////

    public static void notifyScanDcim(Context context, String filePath) {
        scanFile(context, filePath);
    }

    public static void insertVideoToMediaStore(Context context, String filePath, long dateTaken, long duration) {
        insertVideoToMediaStore(context, filePath, dateTaken, 0, 0, duration);
    }

    public static void insertImageToMediaStore(Context context, String filePath, long createTime) {
        insertImageToMediaStore(context, filePath, createTime, 0, 0);
    }


    ///////////////////////////////////////////////////////////////////////////
    // 扫描系统相册核心方法
    ///////////////////////////////////////////////////////////////////////////

    /**
     * 针对系统文件夹只需要扫描,不用插入内容提供者,不然会重复
     *
     * @param context  上下文
     * @param filePath 文件路径
     */
    public static void scanFile(Context context, String filePath) {
        if (!checkFile(filePath)) {
            return;
        }
        Intent intent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE);
        intent.setData(Uri.fromFile(new File(filePath)));
        context.sendBroadcast(intent);
    }


    ///////////////////////////////////////////////////////////////////////////
    // 非系统相册向MediaContent中插入数据,核心方法
    ///////////////////////////////////////////////////////////////////////////

    /**
     * 针对非系统文件夹下的文件,使用该方法
     * 插入时初始化公共字段
     *
     * @param filePath 文件
     * @param time     ms
     * @return ContentValues
     */
    private static ContentValues initCommonContentValues(String filePath, long time) {
        ContentValues values = new ContentValues();
        File saveFile = new File(filePath);
        long timeMillis = getTimeWrap(time);
        values.put(MediaStore.MediaColumns.TITLE, saveFile.getName());
        values.put(MediaStore.MediaColumns.DISPLAY_NAME, saveFile.getName());
        values.put(MediaStore.MediaColumns.DATE_MODIFIED, timeMillis);
        values.put(MediaStore.MediaColumns.DATE_ADDED, timeMillis);
        values.put(MediaStore.MediaColumns.DATA, saveFile.getAbsolutePath());
        values.put(MediaStore.MediaColumns.SIZE, saveFile.length());
        return values;
    }

    /**
     * 保存到照片到本地,并插入MediaStore以保证相册可以查看到,这是更优化的方法,防止读取的照片获取不到宽高
     *
     * @param context    上下文
     * @param filePath   文件路径
     * @param createTime 创建时间 <=0时为当前时间 ms
     * @param width      宽度
     * @param height     高度
     */
    public static void insertImageToMediaStore(Context context, String filePath, long createTime, int width, int height) {
        if (!checkFile(filePath))
            return;
        createTime = getTimeWrap(createTime);
        ContentValues values = initCommonContentValues(filePath, createTime);
        values.put(MediaStore.Images.ImageColumns.DATE_TAKEN, createTime);
        values.put(MediaStore.Images.ImageColumns.ORIENTATION, 0);
        values.put(MediaStore.Images.ImageColumns.ORIENTATION, 0);
        if (Build.VERSION.SDK_INT > Build.VERSION_CODES.JELLY_BEAN) {
            if (width > 0) values.put(MediaStore.Images.ImageColumns.WIDTH, 0);
            if (height > 0) values.put(MediaStore.Images.ImageColumns.HEIGHT, 0);
        }
        values.put(MediaStore.MediaColumns.MIME_TYPE, getPhotoMimeType(filePath));
        context.getApplicationContext().getContentResolver().insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values);
    }

    /**
     * 保存到视频到本地,并插入MediaStore以保证相册可以查看到,这是更优化的方法,防止读取的视频获取不到宽高
     *
     * @param context    上下文
     * @param filePath   文件路径
     * @param createTime 创建时间 <=0时为当前时间 ms
     * @param duration   视频长度 ms
     * @param width      宽度
     * @param height     高度
     */
    public static void insertVideoToMediaStore(Context context, String filePath, long createTime, int width, int height, long duration) {
        if (!checkFile(filePath))
            return;
        createTime = getTimeWrap(createTime);
        ContentValues values = initCommonContentValues(filePath, createTime);
        values.put(MediaStore.Video.VideoColumns.DATE_TAKEN, createTime);
        if (duration > 0)
            values.put(MediaStore.Video.VideoColumns.DURATION, duration);
        if (Build.VERSION.SDK_INT > Build.VERSION_CODES.JELLY_BEAN) {
            if (width > 0) values.put(MediaStore.Video.VideoColumns.WIDTH, width);
            if (height > 0) values.put(MediaStore.Video.VideoColumns.HEIGHT, height);
        }
        values.put(MediaStore.MediaColumns.MIME_TYPE, getVideoMimeType(filePath));
        context.getContentResolver().insert(MediaStore.Video.Media.EXTERNAL_CONTENT_URI, values);
    }


    /**
     * 是不是系统相册
     *
     * @param path
     * @return
     */
    private static boolean isSystemDcim(String path) {
        return path.toLowerCase().contains("dcim") || path.toLowerCase().contains("camera");
    }

    /**
     * 获取照片的mine_type
     *
     * @param path
     * @return
     */
    private static String getPhotoMimeType(String path) {
        String lowerPath = path.toLowerCase();
        if (lowerPath.endsWith("jpg") || lowerPath.endsWith("jpeg")) {
            return "image/jpeg";
        } else if (lowerPath.endsWith("png")) {
            return "image/png";
        } else if (lowerPath.endsWith("gif")) {
            return "image/gif";
        }
        return "image/jpeg";
    }

    /**
     * 获取video的mine_type,暂时只支持mp4,3gp
     *
     * @param path
     * @return
     */
    private static String getVideoMimeType(String path) {
        String lowerPath = path.toLowerCase();
        if (lowerPath.endsWith("mp4") || lowerPath.endsWith("mpeg4")) {
            return "video/mp4";
        } else if (lowerPath.endsWith("3gp")) {
            return "video/3gp";
        }
        return "video/mp4";
    }

    /**
     * 获得转化后的时间
     *
     * @param time
     * @return
     */
    private static long getTimeWrap(long time) {
        if (time <= 0) {
            return System.currentTimeMillis();
        }
        return time;
    }

    /**
     * 检测文件存在
     *
     * @param filePath
     * @return
     */
    private static boolean checkFile(String filePath) {
        //boolean result = FileUtil.fileIsExist(filePath);
        boolean result = false;
        File mFile = new File(filePath);
        if (mFile.exists()) {
            result = true;
        }
        Log.e(TAG, "文件不存在 path = " + filePath);
        return result;
    }
}

二、测试用例

private void demo() {
        ArrayList<String> mListPathSysImg = new ArrayList<>();
        if (mListPathSysImg.size() != 0) {
            //下载成功,刷新系统图库  视频部分手机不支持显示
            for (int i = 0; i < mListPathSysImg.size(); i++) {
                String nDownLoadPath = mListPathSysImg.get(i);
                String updateImgPath = FileUtil.getDownLoad() + nDownLoadPath.substring(nDownLoadPath.lastIndexOf("/"));
                if (updateImgPath.contains(".LRV")) {
                    updateImgPath = updateImgPath.replace(".LRV", ".MP4");
                    AlbumNotifyHelper.insertVideoToMediaStore(this, updateImgPath, 0, 5000);
                } else {
                    AlbumNotifyHelper.insertImageToMediaStore(this, updateImgPath, 0);
                }
                //Log.e("nDownLoadPath----more-->","nDownLoadPath="+nDownLoadPath+",updateImgPath="+updateImgPath);
            }
            mListPathSysImg.clear();
        }
    }

 

<think>好的,我现在需要帮助用户了解如何在Python中读取Android外部存储私有目录文件。首先,我得确认用户的具体需求是什么。外部存储私有目录通常指的是应用专属目录,比如在Android上的context.getExternalFilesDir()返回的路径。用户可能是在开发一个Android应用,想要用Python访问这个目录下的文件。 不过,Python在Android上运行的情况比较特殊,除非用户是在使用像Kivy、BeeWare这样的框架,或者通过Termux等环境。通常,Android应用主要用Java或Kotlin编写,但用户可能是在使用Python-for-Android工具,或者SL4A(Scripting Layer for Android)这样的解决方案。 接下来,我需要考虑不同的情况。如果用户是在Termux环境中运行Python,那么访问外部存储可能需要请求存储权限,并使用特定的路径。例如,Termux的外部存储路径通常是/storage/emulated/0,但访问可能需要使用Android的Storage Access Framework或者通过权限授予。 另外,如果是使用Kivy等框架,可能需要使用Android的API来获取外部私有目录的路径,这通常需要用到pyjnius这样的库来调用Java方法。例如,通过调用getExternalFilesDir()方法获取路径,然后使用Python的文件操作来读取。 还需要注意Android的权限问题。从Android 10(API 29)开始,作用域存储(Scoped Storage)限制了应用对外部存储的访问。应用私有目录不需要额外权限,但访问其他公共目录可能需要请求权限,如READ_EXTERNAL_STORAGE。 用户可能遇到的常见问题包括路径不正确、权限不足或者文件不存在。我需要指导用户如何正确获取路径,处理权限,并进行错误处理。例如,在Termux中,用户可能需要运行termux-setup-storage来获取存储访问权限,然后访问~/storage/shared路径。 最后,要提供一个简单的示例代码,展示如何读取文件,并提醒用户替换实际路径处理异常。同时,提醒用户不同环境下的差异,确保他们根据自己的具体情况调整代码。</think>在Python中读取Android外部存储私有目录文件,通常需要结合Android API文件路径操作。以下是关键步骤说明: --- ### 一、理解私有目录位置 Android应用的**外部私有目录**路径格式为: ``` /storage/emulated/0/Android/data/<package_name>/files/ ``` 这是应用专属目录,卸载应用时会被自动删除,无需额外权限即可访问。 --- ### 二、Python实现方式 #### 情况1:使用Android API(推荐) 需要借助`pyjnius`库调用Java方法: ```python from jnius import autoclass # 获取上下文环境 Context = autoclass('android.content.Context') context = autoclass('org.kivy.android.PythonActivity').mActivity # 获取外部私有目录路径 ext_dir = context.getExternalFilesDir(None).getAbsolutePath() file_path = f"{ext_dir}/your_file.txt" # 读取文件 with open(file_path, 'r') as f: content = f.read() print(content) ``` #### 情况2:直接使用Termux环境 ```python import os # Termux默认共享存储路径 ext_dir = "/storage/emulated/0" file_path = os.path.join(ext_dir, "Android/data/your.package.name/files/your_file.txt") # 检查权限 if not os.access(file_path, os.R_OK): raise PermissionError("需要先执行 termux-setup-storage 授权") with open(file_path, 'r') as f: content = f.read() ``` --- ### 三、注意事项 1. **权限要求** - 私有目录:不需要额外权限 - 公共目录Android 10+需要`READ_EXTERNAL_STORAGE`权限 2. **Android版本差异** - Android 11+强制使用Scoped Storage - 建议使用`Context.getExternalFilesDir()`获取路径 3. **错误处理** ```python try: with open(file_path, 'r') as f: print(f.read()) except FileNotFoundError: print("文件不存在") except PermissionError: print("无访问权限") ``` --- ### 四、扩展说明 - 写入文件时需使用`w`模式: ```python with open(file_path, 'w') as f: f.write("新内容") ``` - 查看所有存储路径: ```python from android.storage import app_storage_path print("内部存储路径:", app_storage_path()) ``` 建议根据实际开发环境(Kivy/QPython/Termux等)选择对应方法,路径获取方式可能有所不同。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值