文章目录
- 小结
- Android JNI使用C++
- Android JNI读写本地文件
- 有关权限
- 创建文件夹
- 访问 /storage/emulated/0/
- 访问/data/data/example.jniwritefile/
- 时间戳
- Can't determine type for tag
- 参考
小结
进行Android JNI C++读写本地文件,取得了想要的效果。
Android JNI使用C++
对于Android的本地文件的操作,由于涉及到安全问题,并不是十分直接。
 具体创建Andriod JNI应用,可以参考 android studio 3.2 使用jni 和 Add C and C++ code to your project
Android JNI读写本地文件
有关权限
要使用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有关权限的问题的具体解决办法可能参考此篇文章: 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>,参考文件编程:创建目录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/
 /storage/emulated/0/是可以通过Android手机文件应用/文件浏览器进行访问的。
 首先,需要获取获取手机内部存储卡的根目录,Java代码获取比较方便,这里使用Android ndk来获取。
 参考 Android ndk获取手机内部存储卡的根目录 和 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/
访问/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+");时间戳
有关时间戳的问题可以参考C语言应用(1)——Unix时间戳和北京时间的相互转换 , cppreference.com: gmtime, gmtime_r, gmtime_s 和 c++ 时间类型详解 time_t
这里使用了localtime函数,注意localtime与gmtime的时差,例如新加坡/北京时间与GMT时间隔了8个小时。
putenv("TZ=Asia/Singapore");
        //sCurrentTime = gmtime(¤tTime);
        sCurrentTime = localtime(¤tTime);Can’t determine type for tag
参考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()
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
Add C and C++ code to your project
Can’t determine type for tag macro name=“m3_comp_assist_chip_container_shape”>?attr/shapeAppearanceCornerSmalcppreference.com: gmtime, gmtime_r, gmtime_s










