【Android9.0】【ftell】相机拍照保存到sdcard中的图片无法显示

2024-03-27 15:48

本文主要是介绍【Android9.0】【ftell】相机拍照保存到sdcard中的图片无法显示,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

【现象】

  • 相机设置存储为外部存
  • 相机拍完照之后,相机相册无法显示图片
  • 重启手机或者重新mount sdcard就可以显示

【背景】

  • 由于Android 9.0,apk如果需要向sdcard中保存数据只有apk本身sdcard路径下才有权限操作,sdcard其他路径下无法操作。于是camera apk那边采用了DocumentFile方式绕过此处的权限管控。
  • 采用此方法之后,相册 apk采用底层C语言fseek(filp, 0L, SEEK_END)和ftell(filp)方式获取文件size为0才无法显示图片

【分析】

  • 怀疑camera产生图片文件之后没有close掉
    • 进入adb mode,使用“lsof 文件”查看无其他进程在使用,说明图片文件已经被close了
  • 写一个C测试程序方便采用ftell测试图片文件
    • /external/test/Android.mk
      LOCAL_PATH := $(call my-dir)
      include $(CLEAR_VARS)LOCAL_MODULE := testLOCAL_CFLAGS := \-Wall -Werror \-Wno-sometimes-uninitialized \-Wno-unused-parameter \-Wno-unused-function \LOCAL_SRC_FILES := \test.c \$(NULL)include $(BUILD_EXECUTABLE)
    • /external/test/test.c
      #include <stdio.h>
      #include <stdlib.h>
      #include <string.h>
      #include <fcntl.h>
      #include <linux/fs.h>
      #include <sys/ioctl.h>
      #include <unistd.h> // for close
      #include <linux/dm-ioctl.h>
      #include <libgen.h>
      #include <sys/param.h>
      #include <sys/mount.h>
      #include <errno.h>
      #include <linux/kdev_t.h>int main(int argc, char** argv) {FILE *pFile = NULL;long fSize_cur = 0;long fSize_stop = 0;int ret = -1;//long acount = 0;char buf[2];printf("[wxl]: Starting ftell........\n");pFile = fopen(argv[1], "rb+");if (pFile == NULL) {printf("[wxl]: Error opening file\n");} else {//while (!feof(pFile)) {memset(buf, 0 , sizeof(buf));fread(buf, sizeof(buf), 1, pFile);printf("[wxl]: fread buf[0] %x\n",buf[0]);printf("[wxl]: fread buf[1] %x\n",buf[1]);fSize_cur = ftell(pFile);printf("[wxl]: ftell fSize_cur %ld\n",fSize_cur);ret = fseek(pFile, 0L, SEEK_CUR);printf("[wxl]: fseek SEEK_CUR return %d\n",ret);memset(buf, 0 , sizeof(buf));fread(buf, sizeof(buf), 1, pFile);printf("[wxl]: fread buf[0] %x\n",buf[0]);printf("[wxl]: fread buf[1] %x\n",buf[1]);fSize_cur = ftell(pFile);printf("[wxl]: ftell fSize_cur %ld\n",fSize_cur);ret = fseek(pFile, 0L, SEEK_END);printf("[wxl]: fseek SEEK_END return %d\n",ret);fSize_stop = ftell(pFile);printf("[wxl]: ftell fSize_stop %ld\n",fSize_stop);//acount ++;//}}//printf("[wxl]: acount = %ld\n",acount);printf("[wxl]: %d %s\n",argc,argv[1]);	fclose(pFile);pFile = NULL;}
    • mmm /external/test编译test可执行程序
    • adb push到手机,进入adb mode,执行test测试程序发现:
      • 获取/storage/76EE-1CE6/DCIM/100MEDIA/IMAG0001.jpg文件size为0(实际是fseek(filp, 0L, SEEK_END)定位到文件头)
      • 获取/mnt/media_rw/76EE-1CE6/DCIM/100MEDIA/IMAG0001.jpg文件size正常(与ls -l查看文件size一致)
      • 修改test测试程序源代码,采用fread从头读到尾(用!feof(pFile)判断到文件头)来计算文件size,可以正常获取文件size
      • 将/storage/76EE-1CE6/DCIM/100MEDIA/IMAG0001.jpg文件在adb mode下采用“cp”命令拷贝到/storage/76EE-1CE6/路径,用test测试程序可以正常获取/storage/76EE-1CE6/路径下文件size
      • 通过手机设置->存储->sdcard->找到IMAG0001.jpg文件->拷贝到/storage/76EE-1CE6/路径下(apk本身拷贝方法),再用test测试程序获取/storage/76EE-1CE6/路径下文件size为0
  • 说明图片文件本身是没问题的,只是fseek(filp, 0L, SEEK_END)不能定位到文件末尾
  • 在adb mode下,使用“mount”命令查看mount信息
  • /dev/block/vold/public:179,1 on /mnt/media_rw/76EE-1CE6 type vfat (rw,dirsync,nosuid,nodev,noexec,noatime,uid=1023,gid=1023,fmask=0007,dmask=0007,allow_utime=0020,codepage=437,iocharset=iso8859-1,shortname=mixed,utf8,errors=remount-ro)
    /mnt/media_rw/76EE-1CE6 on /mnt/runtime/default/76EE-1CE6 type sdcardfs (rw,nosuid,nodev,noexec,noatime,fsuid=1023,fsgid=1023,gid=1015,mask=6)
    /mnt/media_rw/76EE-1CE6 on /storage/76EE-1CE6 type sdcardfs (rw,nosuid,nodev,noexec,noatime,fsuid=1023,fsgid=1023,gid=1015,mask=6)
    /mnt/media_rw/76EE-1CE6 on /mnt/runtime/read/76EE-1CE6 type sdcardfs (rw,nosuid,nodev,noexec,noatime,fsuid=1023,fsgid=1023,gid=9997,mask=18)
    /mnt/media_rw/76EE-1CE6 on /mnt/runtime/write/76EE-1CE6 type sdcardfs (rw,nosuid,nodev,noexec,noatime,fsuid=1023,fsgid=1023,gid=9997,mask=18)
    • /mnt/media_rw/76EE-1CE6/是底层设备直接mount点,为vfat文件系统
    • /storage/76EE-1CE6是/mnt/media_rw/76EE-1CE6/以sdcard虚拟文件系统的mount点,为sdcard虚拟文件系统
  • vfat文件系统可以,sdcard文件系统不行,很可能是sdcard文件系统有问题
  • 写一个ko驱动程序,打印图片文件kernel中的inode部分信息
    • c文件
      /** Copyright (C) 2010 HTC, Inc.** This software is licensed under the terms of the GNU General Public* License version 2, as published by the Free Software Foundation, and* may be copied, distributed, and modified under those terms.** This program is distributed in the hope that it will be useful,* but WITHOUT ANY WARRANTY; without even the implied warranty of* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the* GNU General Public License for more details.**/#include <linux/kernel.h>
      #include <linux/init.h>
      #include <linux/module.h>
      #include <linux/fs.h>
      #include <linux/proc_fs.h>
      #include <asm/uaccess.h>
      #include <linux/seq_file.h>static int __init print_fail_file_struct_init(void)
      {struct file *pFile = NULL;char fileName[126] = "/storage/76EE-1CE6/DCIM/100MEDIA/IMAG0001.jpg";//char fileName[126] = "/storage/45AE-1AE4/111.jpg";printk("<wxl>:\n");printk("<wxl>: Starting print fail file.\n");pFile = filp_open(fileName,  O_RDWR | O_SYNC, 0);if (IS_ERR(pFile)) {printk("<wxl>: unable to open file: %s\n", fileName);return 0;}printk("<wxl>: file name:%s                   \n", (pFile->f_path).dentry->d_iname);printk("<wxl>: file inode magic:%li           \n", pFile->f_inode->i_sb->s_magic);printk("<wxl>: file inode uid:%d              \n", (pFile->f_inode->i_uid).val);printk("<wxl>: file inode gid:%d              \n", (pFile->f_inode->i_gid).val);printk("<wxl>: file inode count:%d            \n", (pFile->f_inode->i_count).counter);printk("<wxl>: file inode write count:%d      \n", (pFile->f_inode->i_writecount).counter);printk("<wxl>: file inode flags:%d            \n", pFile->f_inode->i_flags);printk("<wxl>: file inode bytes:%d            \n", pFile->f_inode->i_bytes);printk("<wxl>: file inode blocks:%ld          \n", pFile->f_inode->i_blocks);printk("<wxl>: file inode state:%ld           \n", pFile->f_inode->i_state);printk("<wxl>: file inode size:%lld           \n", pFile->f_inode->i_size);printk("<wxl>: file inode i_size_read:%lld    \n", i_size_read(pFile->f_inode));printk("<wxl>: file mode:%d                   \n", pFile->f_mode);printk("<wxl>: file count:%ld                 \n", (pFile->f_count).counter);printk("<wxl>: file flags:%d                  \n", pFile->f_flags);printk("<wxl>: file loff_t:%lld               \n", pFile->f_pos);filp_close(pFile, NULL);return 0;}static void __exit print_fail_file_struct_exit(void)
      {
      }module_init(print_fail_file_struct_init);
      module_exit(print_fail_file_struct_exit);
      MODULE_DESCRIPTION("WXL Print File Struct Interface");
      MODULE_VERSION("1.0");
      MODULE_LICENSE("GPL v2");

      以上code主要获取文件属性

    • Makefile添加“obj-m += print_fail_file_struct.o”
    • 编译生成的ko文件,adb push到/data路径下,insmod安装驱动,打印信息发现sdcard文件系统路径下的图片文件inode中的i_size和i_block为0,而vfat文件系统路径下的图片文件inode中的i_size和i_block正常
  • 查看fseek通过vfs调用sdcard文件系统中的llseek函数的实现(应该首要check sdcard文件系统llseek函数),通过kernel/fs/sdcard/file.c文件可知llseek调用通用的llseek函数(kernel/fs/read_write.c文件中generic_file_llseek函数),发现fseek传入whence为“SEEK_END”时计算文件size,是通过文件inode中的i_size来计算偏移量的。
  • 说明就是文件inode中的i_size不对导致的。vfat文件系统i_size正常,而sdcard文件系统(基于vfat文件系统再次mount出来的一个虚拟文件系统)i_size却为0,是不是两个文件系统文件inode系统没有同步,什么时候同步inode信息?
  • 查看kernel/fs/sdcard/file.c,发现sdcard的write函数中,write完数据会同步底层vfat文件系统中的inode信息
    static ssize_t sdcardfs_write(struct file *file, const char __user *buf,size_t count, loff_t *ppos)
    {int err;struct file *lower_file;struct dentry *dentry = file->f_path.dentry;struct inode *inode = d_inode(dentry);/* check disk space */if (!check_min_free_space(dentry, count, 0)) {pr_err("No minimum free space.\n");return -ENOSPC;}lower_file = sdcardfs_lower_file(file);err = vfs_write(lower_file, buf, count, ppos);/* update our inode times+sizes upon a successful lower write */if (err >= 0) {if (sizeof(loff_t) > sizeof(long))inode_lock(inode);fsstack_copy_inode_size(inode, file_inode(lower_file));fsstack_copy_attr_times(inode, file_inode(lower_file));if (sizeof(loff_t) > sizeof(long))inode_unlock(inode);}return err;
    }

    code中lower_file是底层文件指针,此函数直接是对lower_file进行vfs_write

  • 既然有同步动作,为什么却没有同步呢?除非此code没有被调用
  • 添加debug log,发现确实没有被调用。那数据是采用什么方式写入呢?(测试发现没有走sdcard文件中的write、mmap等可以写入的方式)
  • 与负责camera同事沟通了解DocumentFile只是为了获取sdcard的权限,写数据仍然采用OutputStream类进行写操作。看来apk侧没什么突破口了。
  • 向kernel/fs/路径下的相关文件中添加debug log,分别抓取apk采用DocumentFile类向/mnt/media_rw/76EE-1CE6/DCIM/100MEDIA/路径创建文件并写入时dmesg log,以及抓取apk采用正常File类向sdcard apk路径(/storage/76EE-1CE6/Android/data/com.xxx)创建文件并写入时dmesg log
  • 对比发现正常方式会走sdcard中的write函数,而采用DocumentFile方式走vfat中的write(通用write)对数据进行写入,所以sdcard虚拟文件系统中文件inode没能同步底层vfat文件系统中文件inode。
  • 最终得到如规律:
    • /storage/76EE-1CE6/(sdcard文件系统)路径下创建一个空文件a.txt
    • mnt/media_rw/76EE-1CE6/(vfat文件系统)路径下对a.txt文件进行写入数据
    • 此时/storage/76EE-1CE6/a.txt文件i_size就为0
    • mnt/media_rw/76EE-1CE6/(vfat文件系统)路径下创建空文件b.txt,并直接对b.txt进行写入数据,此时不管/storage/76EE-1CE6/b.txt还是mnt/media_rw/76EE-1CE6/b.txt都正常
  • 这有可能是Google设计sdcard文件系统是未能考虑到一种场景
  • 自己在sdcard_flush函数中主动同步底层lower_file数据,在sdcard文件系统中的文件可以正常获取文件size,至于能不能正常做,还不清楚。

【总结】

根据以上分析数据,猜测采用DocumentFile流程应该是:

  • 先在sdcard文件系统创建文件a,此时并没有写入数据
  • 然后对vfat文件系统中文件a进行写入数据
  • 而此时另外一个进程(相册中的进程)直接读取sdcard文件系统中文件a的size,这时获取文件a的size为0.(相册那边是否可以把文件路径改为vfat文件系统路径,不知道是否有权限问题?)

以下是我对Linux文件系统操作流程的理解,如有不对的地方,还请帮忙提出多谢!

 

这篇关于【Android9.0】【ftell】相机拍照保存到sdcard中的图片无法显示的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/852631

相关文章

电脑win32spl.dll文件丢失咋办? win32spl.dll丢失无法连接打印机修复技巧

《电脑win32spl.dll文件丢失咋办?win32spl.dll丢失无法连接打印机修复技巧》电脑突然提示win32spl.dll文件丢失,打印机死活连不上,今天就来给大家详细讲解一下这个问题的解... 不知道大家在使用电脑的时候是否遇到过关于win32spl.dll文件丢失的问题,win32spl.dl

pip无法安装osgeo失败的问题解决

《pip无法安装osgeo失败的问题解决》本文主要介绍了pip无法安装osgeo失败的问题解决,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一... 进入官方提供的扩展包下载网站寻找版本适配的whl文件注意:要选择cp(python版本)和你py

SpringBoot项目启动报错"找不到或无法加载主类"的解决方法

《SpringBoot项目启动报错找不到或无法加载主类的解决方法》在使用IntelliJIDEA开发基于SpringBoot框架的Java程序时,可能会出现找不到或无法加载主类com.example.... 目录一、问题描述二、排查过程三、解决方案一、问题描述在使用 IntelliJ IDEA 开发基于

基于Python开发批量提取Excel图片的小工具

《基于Python开发批量提取Excel图片的小工具》这篇文章主要为大家详细介绍了如何使用Python中的openpyxl库开发一个小工具,可以实现批量提取Excel图片,有需要的小伙伴可以参考一下... 目前有一个需求,就是批量读取当前目录下所有文件夹里的Excel文件,去获取出Excel文件中的图片,并

Java实现数据库图片上传与存储功能

《Java实现数据库图片上传与存储功能》在现代的Web开发中,上传图片并将其存储在数据库中是常见的需求之一,本文将介绍如何通过Java实现图片上传,存储到数据库的完整过程,希望对大家有所帮助... 目录1. 项目结构2. 数据库表设计3. 实现图片上传功能3.1 文件上传控制器3.2 图片上传服务4. 实现

Java实现数据库图片上传功能详解

《Java实现数据库图片上传功能详解》这篇文章主要为大家详细介绍了如何使用Java实现数据库图片上传功能,包含从数据库拿图片传递前端渲染,感兴趣的小伙伴可以跟随小编一起学习一下... 目录1、前言2、数据库搭建&nbsChina编程p; 3、后端实现将图片存储进数据库4、后端实现从数据库取出图片给前端5、前端拿到

Linux虚拟机不显示IP地址的解决方法(亲测有效)

《Linux虚拟机不显示IP地址的解决方法(亲测有效)》本文主要介绍了通过VMware新装的Linux系统没有IP地址的解决方法,主要步骤包括:关闭虚拟机、打开VM虚拟网络编辑器、还原VMnet8或修... 目录前言步骤0.问题情况1.关闭虚拟机2.China编程打开VM虚拟网络编辑器3.1 方法一:点击还原VM

Flask解决指定端口无法生效问题

《Flask解决指定端口无法生效问题》文章讲述了在使用PyCharm开发Flask应用时,启动地址与手动指定的IP端口不一致的问题,通过修改PyCharm的运行配置,将Flask项目的运行模式从Fla... 目录android问题重现解决方案问题重现手动指定的IP端口是app.run(host='0.0.

Android WebView无法加载H5页面的常见问题和解决方法

《AndroidWebView无法加载H5页面的常见问题和解决方法》AndroidWebView是一种视图组件,使得Android应用能够显示网页内容,它基于Chromium,具备现代浏览器的许多功... 目录1. WebView 简介2. 常见问题3. 网络权限设置4. 启用 JavaScript5. D

SpringBoot项目启动错误:找不到或无法加载主类的几种解决方法

《SpringBoot项目启动错误:找不到或无法加载主类的几种解决方法》本文主要介绍了SpringBoot项目启动错误:找不到或无法加载主类的几种解决方法,具有一定的参考价值,感兴趣的可以了解一下... 目录方法1:更改IDE配置方法2:在Eclipse中清理项目方法3:使用Maven命令行在开发Sprin