Android文件存储(一)内部存储

开个头

数据存储在我们android开发中是不可避免的,而且,我们也都知道数据存储的方式,文件存储,SharedPreference,数据库存储等。但是应该也有一部分人, 只是知道这些存储方式,或者说只知道怎么用,但是不知道具体被保存在什么地方。本篇文章将详细分析这三种存储方式。
算了不卖关子了,其实,在我看来数据存储,或者说数据的持久化,就分为两类。内部存储,外部存储。然后接下来开始我们的表演。

内部存储空间

路径:data/data/包名

1 、getFileDir()

路径:data/data/包名/file

    /**
     * Returns the absolute path to the directory on the filesystem where files
     * created with {@link #openFileOutput} are stored.
     * <p>
     * The returned path may change over time if the calling app is moved to an
     * adopted storage device, so only relative paths should be persisted.
     * <p>
     * No additional permissions are required for the calling app to read or
     * write files under the returned path.
     *
     * @return The path of the directory holding application files.
     * @see #openFileOutput
     * @see #getFileStreamPath
     * @see #getDir
     */
    public abstract File getFilesDir();

通过官方注释我们可以看到,这个方法返回一个绝对路径,这个绝对路径是有openFilePutput()方法创建的文件。重要的是最后一句,当在这个路径下调用程序来进行读写操作的时候,是不需要任何额外的权限的。也就是说,我们在使用内部存储的方法存储数据的时候,不需要用在manifest文件中声明权限,也不需要考虑android6.0的运行时权限的。想想还是很happy滴

1.1 openFileOutput()

根据上面的介绍我们知道了,这个方法是用来创建一个内部存储的文件的。

    /**
     * Open a private file associated with this Context's application package
     * for writing. Creates the file if it doesn't already exist.
     * <p>
     * No additional permissions are required for the calling app to read or
     * write the returned file.
     *
     * @param name The name of the file to open; can not contain path
     *            separators.
     * @param mode Operating mode.
     * @return The resulting {@link FileOutputStream}.
     * @see #MODE_APPEND
     * @see #MODE_PRIVATE
     * @see #openFileInput
     * @see #fileList
     * @see #deleteFile
     * @see java.io.FileOutputStream#FileOutputStream(String)
     */
    public abstract FileOutputStream openFileOutput(String name, @FileMode int mode)
        throws FileNotFoundException;

再看官方的注释,打开一个和应用程序包名相关联的私有文件来写入。如果文件不存在就会创建这个文件。并且又说明了进行读写操作的时候是不用任何权限的。

这个方法需要传两个参数,第一个参数就是你想要创建文件的文件名,注意不能包含”/”斜杠符号。
第二个参数是操作模式。官方给我们了两种模式选择:

    /**
     * File creation mode: for use with {@link #openFileOutput}, if the file
     * already exists then write data to the end of the existing file
     * instead of erasing it.
     * @see #openFileOutput
     * 文件创建模式,在openFileOuput方法中使用,如果文件存在,那么会在已存在的文件后面接着写入数据,而不是删除已存在的数据。
     */
    public static final int MODE_APPEND = 0x8000;
   /**
     * File creation mode: the default mode, where the created file can only
     * be accessed by the calling application (or all applications sharing the
     * same user ID).
     * 文件创建模式,默认的模式,用这个模式创建的文件只能被当前调用的应用程序访问。(或者所有拥有相同UID的应用,
     * 这个UID其实就是每个进程的UID,也就是说同一进程访问,这里涉及到多进程的知识,在此不详细展开了)
     */
    public static final int MODE_PRIVATE = 0x0000;
小试一下 写入数据
    private void createInternalPathHxy() {
        try {
            //用MODE_PRIVAT模式,创建一个hxy.txt文件
            FileOutputStream outputStream = openFileOutput("hxy.txt", MODE_PRIVATE);
            //创建一个Usr对象(实现了Serializable接口,来让User对象可以通过流写入)
            User user = new User("hxy", 23);
            //下面的就没么好说的了,写入方法和冲刷方法
            outputStream.write(user.toString().getBytes());
            outputStream.flush();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

而且,我并没有在Mainfest文件中声明任何权限。由于内部存储空间必须有root的android机才对用户可见,所以我们用as的虚拟机运行程序,通过DeviceFileExplorer看下写入的结果。
这里写图片描述
可以看到,基本和我们预想的都一样。既然写入成功了,那我们看下读取的方法吧。

1.2 openFileInput()
    /**
     * Open a private file associated with this Context's application package
     * for reading.
     *
     * @param name The name of the file to open; can not contain path
     *             separators.
     *
     * @return The resulting {@link FileInputStream}.
     *
     * @see #openFileOutput
     * @see #fileList
     * @see #deleteFile
     * @see java.io.FileInputStream#FileInputStream(String)
     */
    public abstract FileInputStream openFileInput(String name)
        throws FileNotFoundException;

官方注释,打开一个和程序包名相关联的私有文件来读取。这个方法只有一个参数,就是要打开的文件名,这个文件名也不能包含“/”斜杠符号。

小试一下 读取
    在读取数据之前,我们修改了写入的代码,来看看是否会覆盖之前的内容而不是在后面添加
    // User user = new User("hxy", 23);
     User user = new User("xavier", 111);

    private void getDataFromInternalPath() {
        try {
            //用StringBuilder来接收数据,而不是用String+=的方法。
            StringBuilder sb = new StringBuilder();
            //每次读取1024个byte的数据
            byte[] bytes = new byte[1024];
            FileInputStream inputStream = openFileInput("hxy.txt");
            int len = 0;
            while ((len = inputStream.read(bytes)) != -1) {
                sb.append(new String(bytes, 0, len));
            }
            Log.e("读取到的数据", sb.toString());
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    //打印结果如下,确实是覆盖了之前的内容。
    08-24 07:52:49.190 15360-15360/com.example.hxytest E/读取到的数据: User{name='xavier', age=111}
1.3 其他API

下面连个方法就不详细介绍了,一个是删除内部存储文件,一个是获取内部存储文件的列表方法。(只能操作file文件夹下的文件)

    /**
     * Delete the given private file associated with this Context's
     * application package.
     *
     * @param name The name of the file to delete; can not contain path
     *             separators.
     *
     * @return {@code true} if the file was successfully deleted; else
     *         {@code false}.
     *
     * @see #openFileInput
     * @see #openFileOutput
     * @see #fileList
     * @see java.io.File#delete()
     */
    public abstract boolean deleteFile(String name);
   /**
     * Returns an array of strings naming the private files associated with
     * this Context's application package.
     *
     * @return Array of strings naming the private files.
     *
     * @see #openFileInput
     * @see #openFileOutput
     * @see #deleteFile
     */
    public abstract String[] fileList();

2、getCacheDir()

路径:data/data/包名/cache
获取内部存储空间的缓存路径。他并没有类似openFileOutput和openFileInput的方法也没偶遇delete和fileList的方法。所以如果要往这个文件夹下写入文件就要用一般用到的File file = new File(); 先创建一个文件,然后再利用FileOutputStream往文件里写入数据。这个好像用的挺少的,可能是因为当系统内存不足时,会把他整个目录删掉吧。

3、getDir()

    /**
     * Retrieve, creating if needed, a new directory in which the application
     * can place its own custom data files.  You can use the returned File
     * object to create and access files in this directory.  Note that files
     * created through a File object will only be accessible by your own
     * application; you can only set the mode of the entire directory, not
     * of individual files.
     * <p>
     * The returned path may change over time if the calling app is moved to an
     * adopted storage device, so only relative paths should be persisted.
     * <p>
     * Apps require no extra permissions to read or write to the returned path,
     * since this path lives in their private storage.
     *
     * @param name Name of the directory to retrieve.  This is a directory
     * that is created as part of your application data.
     * @param mode Operating mode.
     *
     * @return A {@link File} object for the requested directory.  The directory
     * will have been created if it does not already exist.
     *
     * @see #openFileOutput(String, int)
     */
    public abstract File getDir(String name, @FileMode int mode);

这个方法其实是直接在内部存储空间创建文件夹的方法。

File dir = getDir("xavier", MODE_PRIVATE);

这里写图片描述
这个文件夹是和上面所说的file和cache是同级的。而且我们发现他会自动往我们的“xavier”参数前,加一个app_,这个需要注意一下,但是并不影响我们还是通过”xavier”来访问”app_xavier”。
往这个自定义文件夹里面写入文件的方法如下。

    private void writeToFile() {
        try {
            File dir = getDir("xavier", MODE_PRIVATE);
            File file = new File(dir, "hxy2.txt");
            FileOutputStream outputStream = new FileOutputStream(file);
            User user = new User("xavier", 111);
            outputStream.write(user.toString().getBytes());
            outputStream.flush();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

可以看到确实吧hxy2.txt写入倒了内存中。
这里写图片描述
小结
1、内部存储空间的路径为data/data/包名
2、内部存储空间只有file文件夹下的读,写,删,操作系统给我们提供了。
3、内部存储空间的文件都是只能本程序访问,其他程序没有权限访问。
4、内部存储空间的文件 在应用被卸载的时候会被一并删除,更新的时候不会。
5、访问内部存储空间,并不需要任何的权限。
6、内部存储空间其实就是手机的内存,所以不能往这里面存入太大的文件,不然手机没有内存就无法正常使用了。
7、cache与files的差别在于,如果手机的内部存储空间不够了,会自行选择cache目录进行删除,因此,不要把重要的文件放在cache文件里面,可以放置在files里面

4、SharedPreference

相信很多开发者对于这种存储方式都不陌生。但是,你知道sharedPreference是以什么样的方式保存的吗?你知道有几种获取SharedPreference对象的方法吗?你知道。。。
用的最多的应该就是,直接getSharedPrederence(name,mode)获取sp对象,然后进行存储或者读取操作。其实这个方法是调用的Context类的方法。

Context.java
    /**
     * Retrieve and hold the contents of the preferences file 'name', returning
     * a SharedPreferences through which you can retrieve and modify its
     * values.  Only one instance of the SharedPreferences object is returned
     * to any callers for the same name, meaning they will see each other's
     * edits as soon as they are made.
     * 检索和保存这个偏好文件"name"的内容,返回一个SharedPreferences的对象通过这个对象,
     * 可以检索和修改他的值。每次调用这个方法,如果name一样,那么返回的SharedPreferences对象也一样。
     *
     * @param name Desired preferences file. If a preferences file by this name
     * does not exist, it will be created when you retrieve an
     * editor (SharedPreferences.edit()) and then commit changes (Editor.commit()).
     * 要请求的偏好文件。如果当前name命名的偏好文件不存在,那么当通过获取一个editor对象,并且调用commit()方法
     * 那么这个name命名的文件将会被创建。
     * @param mode Operating mode.
     *
     * @return The single {@link SharedPreferences} instance that can be used
     *         to retrieve and modify the preference values.
     *
     * @see #MODE_PRIVATE
     */
    public abstract SharedPreferences getSharedPreferences(String name, @PreferencesMode int mode);

官方注释,有这么几个点需要我们注意

1、SharedPreferences对于每个name来说是单例模式。
2、偏好文件,在editor.commit()方法之后才会被创建,getSharedPreferences方法是不会创建 名字为name的偏好文件的。
3、关于mode参数。我们一般都取MODE_PRIVATE,这里的MODE_PRIVATE 和上面openFileOutput方法里的MODE_PRIVATE 是一个意思。而且,官方已经废除了MODE_WORLD_READABLEMODE_WORLD_WRITEABLE 这两个参数意思分别是允许其他应用读取和写入我们的sharedPreferences,官方也是为用户的安全着想,废除了危险的操作模式。

private void sharedPreferences() {
    SharedPreferences sp1 = getSharedPreferences("hxy", MODE_PRIVATE);
    SharedPreferences sp2 = getSharedPreferences("hxy", MODE_PRIVATE);
    SharedPreferences sp3 = getSharedPreferences("hxy", MODE_PRIVATE);
    Log.e("sp", "sp1" + sp1.toString());
    Log.e("sp", "sp2" + sp2.toString());
    Log.e("sp", "sp3" + sp3.toString());
}

08-29 05:52:28.494 9647-9647/com.example.hxytest E/sp: sp1android.app.SharedPreferencesImpl@dfe8a50
08-29 05:52:29.886 9647-9647/com.example.hxytest E/sp: sp2android.app.SharedPreferencesImpl@dfe8a50
08-29 05:52:31.645 9647-9647/com.example.hxytest E/sp: sp3android.app.SharedPreferencesImpl@dfe8a50

根据代码和打印的Log日志我们可以证明第一条。
下图我们可以看到,我们的内部存储空间多了一个文件夹shared_prefs,这个文件夹其实就是所有通过SharedPreferences方法存储数据的文件夹,就是说通过SharedPreferences保存的数据都在shared_prefs文件夹里面。但是该文件夹是个空的,里面并没有我们想要创建的”hxy”这个文件,所以也证明了第二条。
这里写图片描述

4.1 存储sp和读取sp

这些都是大家经常用的操作,就简单看下代码和结果。

    private void putSharedPreferences() {
        SharedPreferences sp = getSharedPreferences("hxy", MODE_PRIVATE);
        SharedPreferences.Editor edit = sp.edit();
        edit.putString("userName", "xavier");
        edit.putInt("age", 23);
        edit.apply();
//        boolean commit = edit.commit();
    }

    private void getSp() {
        SharedPreferences sp = getSharedPreferences("hxy", MODE_PRIVATE);
        String userName = sp.getString("userName", "");
        int age = sp.getInt("age", 0);
        Log.e("sp", "userName == " + userName + " , age == " + age);
    }

    Log日志:
    08-29 06:32:41.663 29764-29764/com.example.hxytest E/sp: userName == xavier , age == 23

Log日志表示我们成功读取到了数据,这里需要注意的是,edit.apply()和edit.commit(),代码中也可以看到commit()方法是有个boolean类型的返回值的,用来表示使用正常写入了数据。这两个方法的区别是,apply()是异步的,commit()是同步的,写入文件毕竟是个IO操作,所以及时写入的数据量很少,为了不阻塞我们的UI线程,一般还是推荐使用apply()方法来提交。
这里写图片描述
可以看到,系统是通过xml文件的方式存储sp的,而且也成功吧hxy.xml写入到了shared_prefs文件夹里。

4.2 其他方法获取SharedPreferences

上面的方法是通过Context类里面的Context.getSharedPreferences(name,mode)方法来获取的。其实还可以通过Activity的getPreferences(mode)方法和PreferenceManager.getDefaultSharedPreferences(context)来获取。

private void putSharedPreferencesByPreferenceManager() {
        SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(this);
        SharedPreferences.Editor edit = sp.edit();
        edit.putString("saveType", "preferenceManager");
        edit.apply();
    }

    private void putSharedPreferencesByActivity() {
        SharedPreferences sp = getPreferences(MODE_PRIVATE);
        SharedPreferences.Editor edit = sp.edit();
        edit.putString("saveType", "activity");
        edit.apply();
    }

这里写图片描述
这个结果也是显而易见的。Activity.getPreferences(mode)方法会以当前类名为文件名,来存储sp。
PreferenceManager.getDefaultSharedPreferences(context)会以当前包名+”_preferences”为文件名,来存储sp。

5 数据库存储

目前android数据库存储的方式,也是被广泛应用了,但是应该已经没有人还通过SQLiteOpenHelper 调用getWritableDatabase()或者getReadableDatabase()方法获取到SQLiteDatabase 然后再 增删改查。。。都开始使用第三口方框架 比如 greenDAORealmActiveAndroid 等。

但是,通过原生的数据库操作sqlite的方法,其数据库.db文件,默认是保存在内部存储空间的 databases 文件夹里。在此也不进行演示了。

总结

通过以上的分析,我想大家应该对以上的数据存储方式有了更清晰的认识。而且上面的存储方式其最终文件都保存在了系统内存内部,所以,我在开头把他们都归结为内部存储方式就是这样来的。

如有错误,欢迎指正~

猜你喜欢

转载自blog.csdn.net/xy4_android/article/details/80985890