赞
踩
进行Android JNI C++读写本地文件,取得了想要的效果。
对于Android的本地文件的操作,由于涉及到安全问题,并不是十分直接。
具体创建Andriod JNI应用,可以参考CSDN: android studio 3.2 使用jni 和 Add C and C++ code to your project
要使用Android JNI读写本地文件,首先需要解决权限和授权的问题。否则会出现各种各样的错误。我碰到的有errno = 2
(No such file or directory) 和 errno = 13
(Permission denied),具体错误码如下:
errno错误码: errno0 : Success errno1 : Operation not permitted errno2 : No such file or directory errno3 : No such process errno4 : Interrupted system call errno5 : Input/output error errno6 : No such device or address errno7 : Argument list too long errno8 : Exec format error errno9 : Bad file descriptor errno10 : No child processes errno11 : Resource temporarily unavailable errno12 : Cannot allocate memory errno13 : Permission denied errno14 : Bad address errno15 : Block device required errno16 : Device or resource busy errno17 : File exists errno18 : Invalid cross-device link errno19 : No such device errno20 : Not a directory errno21 : Is a directory errno22 : Invalid argument errno23 : Too many open files in system errno24 : Too many open files errno25 : Inappropriate ioctl for device errno26 : Text file busy errno27 : File too large errno28 : No space left on device errno29 : Illegal seek errno30 : Read-only file system errno31 : Too many links errno32 : Broken pipe errno33 : Numerical argument out of domain errno34 : Numerical result out of range errno35 : Resource deadlock avoided errno36 : File name too long errno37 : No locks available errno38 : Function not implemented errno39 : Directory not empty errno40 : Too many levels of symbolic links errno41 : Unknown error 41 errno42 : No message of desired type errno43 : Identifier removed errno44 : Channel number out of range errno45 : Level 2 not synchronized errno46 : Level 3 halted errno47 : Level 3 reset errno48 : Link number out of range errno49 : Protocol driver not attached errno50 : No CSI structure available errno51 : Level 2 halted errno52 : Invalid exchange errno53 : Invalid request descriptor errno54 : Exchange full errno55 : No anode errno56 : Invalid request code errno57 : Invalid slot errno58 : Unknown error 58 errno59 : Bad font file format errno60 : Device not a stream errno61 : No data available errno62 : Timer expired errno63 : Out of streams resources errno64 : Machine is not on the network errno65 : Package not installed errno66 : Object is remote errno67 : Link has been severed errno68 : Advertise error errno69 : Srmount error errno70 : Communication error on send errno71 : Protocol error errno72 : Multihop attempted errno73 : RFS specific error errno74 : Bad message errno75 : Value too large for defined datatype errno76 : Name not unique on network errno77 : File descriptor in bad state errno78 : Remote address changed errno79 : Can not access a needed sharedlibrary errno80 : Accessing a corrupted sharedlibrary errno81 : .lib section in a.out corrupted errno82 : Attempting to link in too manyshared libraries errno83 : Cannot exec a shared librarydirectly errno84 : Invalid or incomplete multibyte orwide character errno85 : Interrupted system call should berestarted errno86 : Streams pipe error errno87 : Too many users errno88 : Socket operation on non-socket errno89 : Destinationaddress required errno90 : Message too long errno91 : Protocol wrong type for socket errno92 : Protocol not available errno93 : Protocol not supported errno94 : Socket type not supported errno95 : Operation not supported errno96 : Protocol family not supported errno97 : Address family not supported byprotocol errno98 : Address already in use errno99 : Cannot assign requested address errno100 : Network is down errno101 : Network is unreachable errno102 : Network dropped connection onreset errno103 : Software caused connection abort errno104 : Connection reset by peer errno105 : No buffer space available errno106 : Transport endpoint is alreadyconnected errno107 : Transport endpoint is notconnected errno108 : Cannot send after transportendpoint shutdown errno109 : Too many references: cannot splice errno110 : Connection timed out errno111 : Connection refused errno112 : Host is down errno113 : No route to host errno114 : Operation already in progress errno115 : Operation now in progress errno116 : Stale NFS file handle errno117 : Structure needs cleaning errno118 : Not a XENIX named type file errno119 : No XENIX semaphores available errno120 : Is a named type file errno121 : Remote I/O error errno122 : Disk quota exceeded errno123 : No medium found errno124 : Wrong medium type errno125 : Operation canceled errno126 : Required key not available errno127 : Key has expired errno128 : Key has been revoked errno129 : Key was rejected by service errno130 : Owner died errno131 : State not recoverable errno132 : Operation not possible due toRF-kill errno133 : Unknown error 133 errno134 : Unknown error 134 errno135 : Unknown error 135 errno136 : Unknown error 136 errno137 : Unknown error 137 errno138 : Unknown error 138 errno139 : Unknown error 139
有关权限的问题的具体解决办法可能参考此篇文章:CSDN: Android文件读写权限 fopen errno=13
默认设置是App可以访问本应用所在的目录, 例如/data/data/example.jniwritefile/
,这里example.jniwritefile
是应用名字。但是要访问其它存储,需要考虑到权限问题。
权限的问题的解决办法是在AndroidManifest.xml
加了几个文件操作权限,并在application
中加入了android:requestLegacyExternalStorage="true"
,文件具体如下:
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools"> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/> <uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS" tools:ignore="ProtectedPermissions" /> <uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" tools:ignore="ScopedStorage" /> <application android:allowBackup="true" android:dataExtractionRules="@xml/data_extraction_rules" android:fullBackupContent="@xml/backup_rules" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:roundIcon="@mipmap/ic_launcher_round" android:supportsRtl="true" android:theme="@style/Theme.JNIWriteFile" android:requestLegacyExternalStorage="true" tools:targetApi="31"> <activity android:name=".MainActivity" android:exported="true"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> <meta-data android:name="android.app.lib_name" android:value="" /> </activity> </application> </manifest>
以下Java代码是在应用启动时开启弹窗让用户确认开启权限。
@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); verifyStoragePermission(this); ... 此处省略 ... } private static final int REQUEST_EXTERNAL_STORAGE = 1; private static String[] PERMISSIONS_STORAGE = { "android.permission.READ_EXTERNAL_STORAGE", "android.permission.WRITE_EXTERNAL_STORAGE" }; public void verifyStoragePermission(Activity activity){ try{ int permission = ActivityCompat.checkSelfPermission(activity,"android.permission.WRITE_EXTERNAL_STORAGE"); if(permission!= PackageManager.PERMISSION_GRANTED){ ActivityCompat.requestPermissions(activity,PERMISSIONS_STORAGE, REQUEST_EXTERNAL_STORAGE); } }catch (Exception e){ e.printStackTrace(); e.printStackTrace(); } }
mkdir
函数需要包括头文件:#include <sys/stat.h>
,参考CSDN: 文件编程:创建目录mkdir()函数,具体C代码如下:
//方便用日志查看 #define LOG_D(...) __android_log_print(ANDROID_LOG_DEBUG, "jni", __VA_ARGS__) ... 省略 ... int errNum = 0; if(0 == access(WriteFileFolder,0)) {//目录存在 } else{ if(0 == mkdir(WriteFileFolder,777)) { } else { LOG_D("open fail errno = %d, reason = %s", errNum, strerror(errNum)); } } ...
/storage/emulated/0/
是可以通过Android手机文件应用/文件浏览器进行访问的。
首先,需要获取获取手机内部存储卡的根目录,Java代码获取比较方便,这里使用Android ndk来获取。
参考CSDN: Android ndk获取手机内部存储卡的根目录 和 CSDN: Android Native APP开发笔记:文件存储与访问 ,代码如下:
//get the external file directory jclass envcls = env->FindClass("android/os/Environment"); //获得类引用 if (envcls == nullptr) return 0; //找到对应的类,该类是静态的返回值是File jmethodID id = env->GetStaticMethodID(envcls, "getExternalStorageDirectory", "()Ljava/io/File;"); //调用上述id获得的方法,返回对象即File file=Enviroment.getExternalStorageDirectory() //其实就是通过Enviroment调用 getExternalStorageDirectory() jobject fileObj = env->CallStaticObjectMethod(envcls,id); //通过上述方法返回的对象创建一个引用即File对象 jclass flieClass = env->GetObjectClass(fileObj); //或得类引用 //在调用File对象的getPath()方法获取该方法的ID,返回值为String 参数为空 jmethodID getpathId = env->GetMethodID(flieClass, "getPath", "()Ljava/lang/String;"); //调用该方法及最终获得存储卡的根目录 jstring pathStr = (jstring)env->CallObjectMethod(fileObj,getpathId); const char* pathStrC = env->GetStringUTFChars(pathStr,NULL); char WriteFileFolder[100]; sprintf(WriteFileFolder, "%s/DocumentTest", pathStrC);
以下代码实现了在前面创建的 /storage/emulated/0/DocumentTest
目录下创建5个文件,目录是放在以上所获取的变量WriteFileFolder
里的,文件名以JohnTest
开头,以时间戳来命名的TXT文件,创建文件后写入This is test to write to file! Timestamp:
并加上时间戳。
FILE *dumpFile = NULL; for (int j = 0; j < 5; j++) { time_t currentTime; struct tm *sCurrentTime; time(¤tTime); /*获取time_t类型当前时间*/ LOG_D("Current time = %s", ctime(¤tTime)); putenv("TZ=Asia/Singapore"); //sCurrentTime = gmtime(¤tTime); sCurrentTime = localtime(¤tTime); char dumpfileName[100]; sprintf(dumpfileName, "%s/JohnTest%04d%02d%02d%02d%02d%02d.txt", WriteFileFolder, sCurrentTime->tm_year + 1900, sCurrentTime->tm_mon + 1, sCurrentTime->tm_mday, sCurrentTime->tm_hour, sCurrentTime->tm_min, sCurrentTime->tm_sec); dumpFile = fopen(dumpfileName, "w+"); char rpucData[100]; sprintf(rpucData, "This is test to write to file! Timestamp: %s", ctime(¤tTime)); if (dumpFile == NULL) { int errNum = 0; errNum = errno; LOG_D("open fail errno = %d, reason = %s", errNum, strerror(errNum)); } else { fwrite(rpucData, sizeof(char), (unsigned) strlen(rpucData), dumpFile); fclose(dumpFile); } }
程序运行后会在相应的目录里写入5个TXT文件,并写入相应的内容。
访问/data/data/example.jniwritefile/
并不需要申请权限,类似以上程序,只需要进行以下修改:
...
省略
...
sprintf(dumpfileName, "/data/data/example.jniwritefile/JohnTest%04d%02d%02d%02d%02d%02d.txt",
sCurrentTime->tm_year + 1900,
sCurrentTime->tm_mon + 1,
sCurrentTime->tm_mday,
sCurrentTime->tm_hour,
sCurrentTime->tm_min,
sCurrentTime->tm_sec);
dumpFile = fopen(dumpfileName, "w+");
有关时间戳的问题可以参考CSDN: C语言应用(1)——Unix时间戳和北京时间的相互转换 , cppreference.com: gmtime, gmtime_r, gmtime_s 和 CSDN: c++ 时间类型详解 time_t
这里使用了localtime
函数,注意localtime
与gmtime
的时差,例如新加坡/北京时间与GMT时间隔了8个小时。
putenv("TZ=Asia/Singapore");
//sCurrentTime = gmtime(¤tTime);
sCurrentTime = localtime(¤tTime);
参考Can’t determine type for tag macro name=“m3_comp_assist_chip_container_shape”>?attr/shapeAppearanceCornerSmall
这个问题在build.gradle(:app)
里通过修改几个版本号解决,我使用了以下版本:
dependencies {
implementation 'androidx.appcompat:appcompat:1.4.0'
implementation 'com.google.android.material:material:1.6.0'
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
testImplementation 'junit:junit:4.13.2'
androidTestImplementation 'androidx.test.ext:junit:1.1.3'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
}
C library function - fopen()
CSDN: android通过JNI用C/C++创建本地文件
CSDN: android studio 3.2 使用jni
CSDN Android JNI读取本地文件和读取文件并且写入其他文件
Stackoverflow: Android NDK fopen returns error 2 “No such file or directory” on a file I know exits
Stackoverlfow: Write file to location other than SDcard using Android NDK?
Stackoverlfow: File Operations in Android NDK
CSDN: Android Native APP开发笔记:文件存储与访问
Add C and C++ code to your project
Can’t determine type for tag macro name=“m3_comp_assist_chip_container_shape”>?attr/shapeAppearanceCornerSmall
CSDN: c++ 时间类型详解 time_t
cppreference.com: gmtime, gmtime_r, gmtime_s
CSDN: Android文件读写权限 fopen errno=13
CSDN: C语言应用(1)——Unix时间戳和北京时间的相互转换
CSDN: Android ndk获取手机内部存储卡的根目录
CSDN: 文件编程:创建目录mkdir()函数
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。