当前位置:   article > 正文

Android JNI C++读写本地文件_android jni 读取文件

android jni 读取文件

小结

进行Android JNI C++读写本地文件,取得了想要的效果。

Android JNI使用C++

对于Android的本地文件的操作,由于涉及到安全问题,并不是十分直接。
具体创建Andriod JNI应用,可以参考CSDN: android studio 3.2 使用jniAdd 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
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 152
  • 153
  • 154
  • 155
  • 156
  • 157
  • 158
  • 159
  • 160
  • 161
  • 162
  • 163
  • 164
  • 165
  • 166
  • 167
  • 168
  • 169
  • 170
  • 171
  • 172
  • 173
  • 174
  • 175
  • 176
  • 177
  • 178
  • 179
  • 180
  • 181
  • 182
  • 183
  • 184
  • 185
  • 186
  • 187
  • 188
  • 189
  • 190
  • 191
  • 192
  • 193
  • 194
  • 195
  • 196
  • 197
  • 198
  • 199
  • 200
  • 201
  • 202
  • 203
  • 204
  • 205
  • 206
  • 207
  • 208
  • 209
  • 210
  • 211
  • 212
  • 213
  • 214
  • 215
  • 216
  • 217
  • 218
  • 219
  • 220
  • 221
  • 222
  • 223
  • 224
  • 225
  • 226
  • 227
  • 228
  • 229
  • 230
  • 231
  • 232
  • 233
  • 234
  • 235
  • 236
  • 237
  • 238
  • 239
  • 240
  • 241
  • 242
  • 243
  • 244
  • 245
  • 246
  • 247
  • 248
  • 249
  • 250
  • 251
  • 252
  • 253
  • 254
  • 255
  • 256
  • 257
  • 258
  • 259
  • 260
  • 261
  • 262
  • 263
  • 264
  • 265
  • 266
  • 267
  • 268
  • 269
  • 270
  • 271
  • 272
  • 273
  • 274
  • 275
  • 276
  • 277
  • 278
  • 279
  • 280
  • 281

有关权限的问题的具体解决办法可能参考此篇文章: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>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39

以下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();
        }
    }

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31

创建文件夹

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));
        }
    }
...

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20

访问 /storage/emulated/0/

/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);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24

以下代码实现了在前面创建的 /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(&currentTime); /*获取time_t类型当前时间*/

        LOG_D("Current time = %s", ctime(&currentTime));

        putenv("TZ=Asia/Singapore");
        //sCurrentTime = gmtime(&currentTime);
        sCurrentTime = localtime(&currentTime);


        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(&currentTime));

        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);
        }
    }

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42

程序运行后会在相应的目录里写入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+");
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

时间戳

有关时间戳的问题可以参考CSDN: C语言应用(1)——Unix时间戳和北京时间的相互转换cppreference.com: gmtime, gmtime_r, gmtime_s CSDN: c++ 时间类型详解 time_t

这里使用了localtime函数,注意localtimegmtime的时差,例如新加坡/北京时间与GMT时间隔了8个小时。

        putenv("TZ=Asia/Singapore");
        //sCurrentTime = gmtime(&currentTime);
        sCurrentTime = localtime(&currentTime);
  • 1
  • 2
  • 3

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'
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

参考

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()函数

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/繁依Fanyi0/article/detail/411005
推荐阅读
相关标签
  

闽ICP备14008679号