1. 前言

之前折腾了了一下MINE模拟器,发现SDL全是在JNI层fopen操作的,而安卓的SAF则是JAVA层通过DocumentFile和docUri来实现写入的。一种方法是通过去的File Descriptor然后传给JNI层,通过fdopen实现写入[1]。于是成功在MINE模拟器添加外置sd卡写入功能,详见我在贴吧布的 mine模拟器外置SD卡写入修复版。完整版源码我已经封装好了发布到github上,理论上通用。

2. JAVA层SAF核心代码

通过DocumentFile来实现写入,Intent(Intent.ACTION_OPEN_DOCUMENT_TREE)发送请求docUri,然后onActivityResult来得到并储存docUri,通过SharedPreference来实现共享。已经封装在静态类SafFile.java中。

获取外置sd卡根目录DocumentFilepublic static DocumentFile getBaseDocumentFile(final Context context, final SharedPreferences share) {

if(context==null) {

Log.e(LOGTAG, "SafFile.getBaseDocumentFile context is null!");

return null;

}

if(share==null){

Log.e(LOGTAG, "SafFile.getBaseDocumentFile share is null!");

return null;

}

DocumentFile base = null;

Uri docUri = null;

final String p = share.getString("docUri", null);

if (p != null)

docUri = Uri.parse(p);

base = DocumentFile.fromTreeUri(context, docUri);

return base;

}

注意DocumentFile CreateFile好像不只能在本目录下创建,要一层一层往里面走public static DocumentFile getTargetDirDocumentFile(final DocumentFile base, String path) {

DocumentFile target = null;

if (base == null) {

Log.e(LOGTAG, "SafFile.getTargetDirDocumentFile base is null!");

return null;

}

if(path==null) path="";

path = path.replace("\\", "/");

final String paths[] = path.split("/");

int i;

final int end = paths[paths.length - 1].length() > 0 ? paths.length - 1 : paths.length - 2;

for (i = 0; i < end; i++) {

// Log.i(LOGTAG, "getTar... path["+String.valueOf(i)+"], "+paths[i]);

if (paths[i].equals(base.getName())) {

if (i >= end - 1) {

// Log.i(LOGTAG, "getTar... "+path+" end="+paths[paths.length-1]+" "+ paths[end]);

return base;

}

i++;

break;

}

}

// Log.i(LOGTAG, "getTarget... "+base.getName()+" "+path);

target = base.findFile(paths[i++]);

// Log.i(LOGTAG, "target, "+ target.getName());

for (; i < end; i++) {

if (target == null)

break;

// Log.i(LOGTAG, "getTar..., "+path+" "+ target.getName());

target = target.findFile(paths[i]);

}

return target;

}

获得OutputStreampublic static OutputStream getOutputStreamSaf(final Context context, final DocumentFile base, final String path,

final boolean append) {

if(context==null) {

Log.e(LOGTAG, "SafFile.getOutputStreamSaf context is null!");

return null;

}

if(base==null){

Log.e(LOGTAG, "SafFile.getOutputStreamSaf base is null!");

return null;

}

OutputStream out = null;

final String mode = append ? "wa" : "w";

// Log.i(LOGTAG, "getOut.. "+ path +" "+mode);

final DocumentFile df2 = createFileSaf(base, path, append);

if (df2 == null) {

return null;

}

try {

out = context.getContentResolver().openOutputStream(df2.getUri(), mode);

} catch (final Exception e) {

Log.e(LOGTAG, "SafFile.getOutputStreamSaf " + e.getClass().getName());

}

return out;

}

获取文件描述符public static int getFdSaf(final Context context, final DocumentFile base, final String path, final String mode) {

if(context==null) {

Log.e(LOGTAG, "SafFile.getFdSaf context is null!");

return 0;

}

if(base==null){

Log.e(LOGTAG, "SafFile.getFdSaf base is null!");

return 0;

}

ParcelFileDescriptor pfd = null;

boolean append = false;

DocumentFile df2 = null;

if (mode.indexOf('+') != -1 || mode.indexOf('a') != -1)

append = true;

if (mode.indexOf('w') == -1)

append = true;

df2 = createFileSaf(base, path, append);

if (df2 == null) {

Log.e(LOGTAG, "SafFile.getFdSaf, " + path + " error!");

return 0;

}

try {

pfd = context.getContentResolver().openFileDescriptor(df2.getUri(), mode);

} catch (final Exception e) {

Log.e(LOGTAG, "SafFile.getFdSaf " + e.getClass().getName());

}

if (pfd == null)

return 0;

return pfd.detachFd();

}

3.JNI层hook核心代码

这里用到了xhook架构,原理上是运行的时候来替换目标动态库的.got表到自己编译的函数地址,通过JNI来调用JAVA层我们写好通过SAF机制得到的文件描述符。

初始化要JNI中要调用的JAVA方法,class为"com/yurisizuku/utils/SafFile"void nativeInitSafJavaCallbacks(JNIEnv* env, jclass clazz)

{

LOGI("In nativeInitSafJavaCallbacks start!");

g_javaGetFD=(*env)->GetStaticMethodID(env, clazz, "getFD", "(Ljava/lang/String;Ljava/lang/String;I)I");

g_javaMkdir=(*env)->GetStaticMethodID(env, clazz, "mkdir", "(Ljava/lang/String;Ljava/lang/String;I)I");

g_javaRemove = (*env)->GetStaticMethodID(env, clazz, "remove", "(Ljava/lang/String;Ljava/lang/String;)I");

LOGI("In nativeInitSafJavaCallbacks finished!");

}

xhook架构的hook fopen等函数void nativeHookFile(JNIEnv* env, jclass clazz, jstring hooksoStr, jstring soPath)

{

char buf[100];

char *cstr_hooksoStr = jstr2cstr(env, hooksoStr);

LOGI("nativeHookFile, %s \n", cstr_hooksoStr);

char *cstr_soPath = jstr2cstr(env, soPath);

if(cstr_soPath && strlen(cstr_soPath))

{

if (!dlopen(cstr_soPath, RTLD_LAZY)) //dlopen in advance

LOGE("dlopen(%s,%d) error!\n", cstr_soPath,RTLD_LAZY);

else LOGI("dlopen(%s,%d) success !\n", cstr_soPath,RTLD_LAZY);

}

if (xhook_register(cstr_hooksoStr, "fopen", fopen_saf, NULL))

LOGE("xhook fopen register failed!");

else LOGI("xhook fopen register successed!");

if (xhook_register(cstr_hooksoStr, "mkdir", mkdir_saf, NULL))

LOGE("xhook mkdir register failed!\n");

else LOGI("xhook mkdir register successed!");

if (xhook_register(cstr_hooksoStr, "remove", remove_saf, NULL))

LOGE("xhook remove register failed!\n");

else LOGI("xhook remove register successed!");

xhook_refresh(0);

free(cstr_hooksoStr);

LOGI("nativeHookFile xhook finished!");

if(cstr_soPath) free(cstr_soPath);

}

fopen的hook, 调用java层我们写好的getFD再用fdopen文件可写FILE *fopen_saf(const char *pathname, const char *mode)

{

FILE* fp=NULL;

JNIEnv* env = NULL;

(*g_vm)->AttachCurrentThread(g_vm, &env, NULL);

if(!env)

{

LOGE("fopen_asf, env AttachCurrentThread failed!\n");

return fopen(pathname, mode);

}

int mode2=0;

if(mode[0] == 'w') mode2=1;

fp = fopen(pathname, mode);

if(!(fp || mode2 == 0 || errno != EACCES))

{

char buf[PATH_MAX_LEN];

getcwd(buf, PATH_MAX_LEN);

//LOGI("before fopen(%s, %s), cwd=%s\n", pathname, mode, buf);

jstring s_pathname = (*env)->NewStringUTF(env, pathname);

jstring s_curdir = (*env)->NewStringUTF(env, buf);

int fd = (*env)->CallStaticIntMethod(env, g_javaClass, g_javaGetFD, s_curdir, s_pathname, mode2 );

(*env)->DeleteLocalRef(env, s_curdir);

(*env)->DeleteLocalRef(env, s_pathname);

fp = fdopen(fd, mode);

//LOGI("after fopen_saf(%s, %s),fp=%x, cwd=%s\n", pathname, mode, (unsigned int)fp,buf);

}

return fp;

}

Reference

[1] https://stackoverflow.com/questions/30593964/how-to-access-android-lollipop-documentfile-files-via-ndk/31677287

[2] https://developer.android.com/guide/topics/providers/document-provider.html?hl=zh-cn

最后于 2020-1-14 22:12

被devseed编辑

,原因:

saf java_[原创]Android Storage Access Framework(SAF)框架实现外置SD卡的写入(JAVA层与JNI层HOOK)...相关推荐

  1. saf java_Android SAF实现外置SD卡的写入JAVA层与JNI层hook

    JAVA层SAF核心代码 通过DocumentFile来实现写入,Intent(Intent.ACTION_OPEN_DOCUMENT_TREE)发送请求docUri,然后onActivityResu ...

  2. Android 进阶——Framework 核心之Android Storage Access Framework(SAF)存储访问框架机制详解(一)

    文章大纲 引言 一.Android Storage Access Framework 二.Storage Access Framework 的主要角色成员 1.Document Provider 文件 ...

  3. Android 进阶——Framework 核心之Android Storage Access Framework(SAF)存储访问框架机制详解(二)

    文章大纲 引言 一.DirectFragment 1.当选中DirectoryFragment中RecyclerView的Item时 2.选中DirectoryFragment中RecyclerVie ...

  4. android 使用SAF框架操作外置sd卡

    android 使用SAF框架操作外置sd卡 在 Android 4.4中,Google 对 SD卡 的访问已经做了严格的限制,在 Android 5.0中,开发者可以使用 新API 要求用户对某个指 ...

  5. saf java_Android SAF实现外置SD卡的写入(JAVA层与JNI层hook)

    1. 前言 之前折腾了了一下MINE模拟器,发现SDL全是在JNI层fopen操作的,而安卓的SAF则是JAVA层通过DocumentFile和docUri来实现写入的.一种方法是通过去的File D ...

  6. Android本地文件存储,机身和外置sd卡

    在安卓的开发中,有时候要用到大文件的存储,这个时候就不能存储在应用内部(data/data),只能借助外部存储.而外部存储又分为手机机身的存储空间(一级sd卡)和外置sd卡存储空间(二级sd卡). 1 ...

  7. Android 外置 SD 卡写入权限问题

    https://busy.im/post/android-sdcard-write/ 最近升级到 Android 9.0 后,发现文件管理器在写入外置 SD 卡时出现了写入失败的问题,定位到 File ...

  8. Android P 外置 SD 卡写入权限问题

    概述 Android 9.0 后,发现文件管理器在写入外置 SD 卡时出现了写入失败的问题,定位到 File.canWrite() 方法,发现返回了 false.经过讨论追踪定位,发现是由于 Goog ...

  9. Android SAF(Storage Access Framework)使用攻略

    漫长的假期,在家整理了一下Android 10的适配内容.因为适配篇的篇幅问题,就将这一部本单独出来,也先放出来. 1.介绍 Android 4.4 就引入了存储访问框架 (SAF).借助 SAF,用 ...

最新文章

  1. could not create connection to database server.] with root cause
  2. 基于eclipse swt做java浏览器内嵌等功能
  3. linux shell之字符串的更具字符分割和删除字符和文本内容的删除以及内容是否匹配成功
  4. matlab控制realsense,RealSense开发学习--1.初识RealSense
  5. 面试中的 10 大排序算法总结
  6. java笔试题(1)
  7. SpringBoot maven项目如何打包进行发布?
  8. 引用当前网站集下的样式文件
  9. 干粉灭火器(泡沫灭火器)工作原理
  10. java多线程上传文件_Java大文件分片上传/多线程上传
  11. 上传到服务器的图片访问的时候提示403 You don't have permission to access
  12. Spring Boot内嵌的tomcat日志
  13. Java程序员从笨鸟到菜鸟之(序言)+全部链接
  14. 有趣的python小程序(附效果和程序)
  15. 桌面计算机最小化,最小化我的电脑窗口打开总是最小化,如何把它 – 手机爱问...
  16. 问题排查-Flink session窗口最后一个不过期问题
  17. 笔记本Ubuntu安装失败的一般原因
  18. 防止电脑辐射的有效方法
  19. 东华大学计算机学院刘国华,东华大学计算机科学与技术学院研究生导师简介-刘国华??(教授)...
  20. 程序员除了看技术类的书你们还看哪些书

热门文章

  1. 【其他】快速注册stackoverflow
  2. 基于Python的超市零售数据分析
  3. Chinese 目录 (2005.03.22/ 整理和使用ShitMp3 修改MP3信息)
  4. DataGrid_单击_双击_editable
  5. Python 函数 | sorted 函数详解
  6. CCF-CSP Python Cheat Sheet
  7. Opencv 图片处理
  8. 陕甘回变——关陕残月(一)
  9. openLayers3(四)电子围栏—使用画图工具绘图
  10. B站怎么下载视频中的音乐(不用辅助工具)