当前位置: 移动技术网 > 移动技术>移动开发>Android > AndroidQ 沙箱适配多媒体文件(小结)

AndroidQ 沙箱适配多媒体文件(小结)

2020年03月09日  | 移动技术网移动技术  | 我要评论
综述 所有内容的访问变化见下图: 外部媒体文件的扫描,读取和写入 最容易被踩坑的应该是,对外部媒体文件,照片,视频,图片的读取或写入。 扫描 首先是扫描。扫描依然是使用

综述

所有内容的访问变化见下图:

外部媒体文件的扫描,读取和写入

最容易被踩坑的应该是,对外部媒体文件,照片,视频,图片的读取或写入。

扫描

首先是扫描。扫描依然是使用 query mediastore 的方式。一句话介绍 mediastore,mediastore 就是android系统中的一个多媒体数据库。代码如下图所示,以搜索本地视频为例子:

protected list<videoinfo> doinbackground(void... params) {
  mcontentresolver = context.getcontentresolver();

  string[] mediacolumns = { mediastore.video.media._id, mediastore.video.media.data,
      mediastore.video.media.title, mediastore.video.media.mime_type,
      mediastore.video.media.display_name, mediastore.video.media.size,
      mediastore.video.media.date_added, mediastore.video.media.duration,
      mediastore.video.media.width, mediastore.video.media.height };

  cursor mcursor = mcontentresolver.query(mediastore.video.media.external_content_uri, mediacolumns,
      null, null, mediastore.video.media.date_added);


  if (mcursor == null) {
    return null;
  }

  // 注意,data 数据在 android q 以前代表了文件的路径,但在 android q上该路径无法被访问,因此没有意义。
  ixdata = mcursor.getcolumnindexorthrow(mediastore.video.media.data);
  ixmime = mcursor.getcolumnindexorthrow(mediastore.video.media.mime_type);
  // id 是在 android q 上读取文件的关键字段
  ixid = mcursor.getcolumnindexorthrow(mediastore.video.media._id);
  ixsize = mcursor.getcolumnindexorthrow(mediastore.video.media.size);
  ixtitle = mcursor.getcolumnindexorthrow(mediastore.video.media.title);

  allimages = new arraylist<videoinfo>();
  mtotalvideocount = 0;

  mcursor.movetolast();
  
  while (mcursor.movetoprevious()) {
    if (addvideo(mcursor) == 0) {
      continue;
    } else if (addvideo(mcursor) == 1) {
      break;
    }
  }

  mcursor.close();
  
  return allimages;
}

既然 data 不可用,就需要知晓 id 的使用方式,首先是使用 id 拼装出 content uri ,如下所示:

public getrealpath(string id) {
  return mediastore.video.media.external_content_uri.buildupon().appendpath(string.valueof(id)).build().tostring();
}

image 同理换成 mediastore.images。

读取和写入

其次,是读取 content uri。这里需要注意 file file = new file(contenturi); 是无法获取到文件的。file.exist() 为 false。

那么就产生两个问题:1. 如何确定 contenturi 形式的文件存在 2. 如何读取或写入文件。

首先,对于 content uri 的读取,必须借助于 contentresolver。

其次,对于 1,没有找到 google 文档中提供比较容易的api,只能采用打开 filedescriptor 是否成功的形式,代码如下所示:

public boolean iscontenturiexists(context context, uri uri) {
  if (null == context) {
    return false;
  }
  contentresolver cr = context.getcontentresolver();
  try {
    assetfiledescriptor afd = cr.openassetfiledescriptor(uri, "r");
    if (null == afd) {
      iterator.remove();
    } else {
      try {
        afd.close();
      } catch (ioexception e) {
      }
    }
  } catch (filenotfoundexception e) {
    return false;
  }

  return true;
}

这种方法最大的问题即是,对应于一个同步 i/o 调用,易造成线程等待。因此,目前对于 mediastore 中扫描出来的文件可能不存在的情况,没有直接的好方法可以解决过滤。

对于问题 2,如 1 所示,可以借助 content uri 从 contentresolver 里面拿到 assetfiledescriptor,然后就可以拿到 inputsteam 或 outputstream,那么接下来的读取和写入就非常自然,如下所示:

public static void copy(file src, parcelfiledescriptor parcelfiledescriptor) throws ioexception {
  fileinputstream istream = new fileinputstream(src);
  try {
    fileoutputstream ostream = new fileoutputstream(parcelfiledescriptor.getfiledescriptor());
    try {
      ioutil.copy(istream, ostream);
    } finally {
      ostream.close();
    }
  } finally {
    istream.close();
  }
}

public static void copy(parcelfiledescriptor parcelfiledescriptor, file dst) throws ioexception {
  fileinputstream istream = new fileinputstream(parcelfiledescriptor.getfiledescriptor());
  try {
    fileoutputstream ostream = new fileoutputstream(dst);
    try {
      ioutil.copy(istream, ostream);
    } finally {
      ostream.close();
    }
  } finally {
    istream.close();
  }
}
  
  
public static void copy(inputstream ist, outputstream ost) throws ioexception {
  byte[] buffer = new byte[4096];
  int bytecount = 0;
  while ((bytecount = ist.read(buffer)) != -1) { // 循环从输入流读取 buffer字节
    ost.write(buffer, 0, bytecount);    // 将读取的输入流写入到输出流
  }
}

保存媒体文件到公共区域

这里仅以 video 示例,image、downloads 基本类似:

public static uri insertvideointomediastore(context context, string filename) {
  contentvalues contentvalues = new contentvalues();
  contentvalues.put(mediastore.video.media.display_name, filename);
  contentvalues.put(mediastore.video.media.date_taken, system.currenttimemillis());
  contentvalues.put(mediastore.video.media.mime_type, "video/mp4");

  uri uri = context.getcontentresolver().insert(mediastore.video.media.external_content_uri, contentvalues);
  return uri;
}

这里所做的,只是往 mediastore 里面插入一条新的记录,mediastore 会返回给我们一个空的 content uri,接下来问题就转化为往这个 content uri 里面写入,那么应用上一节所述的代码即可实现。

video 的 thumbnail 问题

在 android q 上已经拿不到 video 的 thumbnail 路径了,又由于没有暴露 video 的 thumbnail 的 id ,导致了 video 的 thumbnail 只能使用实时获取 bitmap 的方法,如下所示:

private bitmap getthumbnail(contentresolver cr, long videoid) throws throwable {
  return mediastore.video.thumbnails.getthumbnail(cr, videoid, mediastore.video.thumbnails.mini_kind,
      null);
}

可以进去看 android sdk 的实现,其中最关键的部分是:

string column = isvideo ? "video_id=" : "image_id=";
c = cr.query(baseuri, projection, column + origid, null, null);
if (c != null && c.movetofirst()) {
  bitmap = getminithumbfromfile(c, baseuri, cr, options);
  if (bitmap != null) {
    return bitmap;
  }
}

进一步再进去看,可以发现直接就把 video/image 文件打开计算 thumbnail。

private static bitmap getminithumbfromfile(
    cursor c, uri baseuri, contentresolver cr, bitmapfactory.options options) {
  bitmap bitmap = null;
  uri thumburi = null;
  try {
    long thumbid = c.getlong(0);
    string filepath = c.getstring(1);
    thumburi = contenturis.withappendedid(baseuri, thumbid);
    parcelfiledescriptor pfdinput = cr.openfiledescriptor(thumburi, "r");
    bitmap = bitmapfactory.decodefiledescriptor(
        pfdinput.getfiledescriptor(), null, options);
    pfdinput.close();
  } catch (filenotfoundexception ex) {
    log.e(tag, "couldn't open thumbnail " + thumburi + "; " + ex);
  } catch (ioexception ex) {
    log.e(tag, "couldn't open thumbnail " + thumburi + "; " + ex);
  } catch (outofmemoryerror ex) {
    log.e(tag, "failed to allocate memory for thumbnail "
        + thumburi + "; " + ex);
  }
  return bitmap;
}

这个 api 毫无疑问设计的非常不合理,没有暴露 thumbnail 的系统缓存给开发者,造成了每次都要重新i/o 计算的极大耗时。强烈呼吁 android q 的正式版能修正这个 api 设计缺陷。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持移动技术网。

如您对本文有疑问或者有任何想说的,请点击进行留言回复,万千网友为您解惑!

相关文章:

验证码:
移动技术网