Android中数据存储与文件存储

前言

Android提供了多种选项来保存永久性应用数据。用户可以选择的数据存储选项有:共享首选项、内部存储、外部存储、SQLite数据库、网络存储。

文章开始前用一张图表示共享首选项,内部存储,外部存储

《Android中数据存储与文件存储》 微信截图_20190305114614.png

使用共享首选项

关键类:SharedPreferences类

可以使用ShatedPreferences来保存任何原始数据:布尔值、浮点值、整型值、长整型、字符串。
这些数据将会跨多个用户会话永久保留。

获得SharedPreferences对象

(1)getSharedPreferences(String name,int mode);

通过Context调用,参数一:首选项文件名,如果此名称的首选项文件不存在,那么会在再检索编辑器SharedPreferences.edit()并提交更改(Editor.commit())时创建这个文件。
参数二默认填写0或者是MODE_PRIVATE(创建的文件只能由调用应用程序访问),别的模式MODE_WORLD_READABLE(创建全都可读文件,容易造成安全漏洞,已在17弃用)与MODE_WORLD_WRITEABLE(创建全部课读写文件,容易造成安全漏洞,已在17弃用)与MODE_MULTI_PROCESS(在某些版本的Android中无法可靠运行,不提供任何机制来协调跨进程的并发修改,已在23弃用)

(2)getPreferences(int mode);

在Activity中调用,参数参照上面,一般用0就可以。
调用SharedPreferences.edit()方法并最后提交更改(Editor.commit())时候创建文件,文件名就是以当前Activity的类名,比较简单的基于当前Activity创建其相应的SharedPreferences文件。

介绍SharedPreferences类中提供的方法

其应该是一个interface,放在android.content.SharedPreferences包下。
其嵌套了接口Editor与接口 OnShatedPreferenceChangeListener。
通过SharedPreferences对象可以调用如下方法:

(1) contains(String key)

返回值:boolean
检验当前SharedPreferences文件中是否包含某个key

(2) getAll()

返回值:Map<String,?>
返回当前SharedPreferences文件中所有的key以及相应的value值

        Map<String,?> map = mSharedPreferences.getAll();
        这样此SharedPreferences中所有的值都在Map集合中

(3)getBoolean(String key,boolean defValue)

返回值:boolean
得到此SharedPreferences中boolean类型的Key对应的value,其中参数二表示默认值。
需要说明的是,如果此key 对应的值不是布尔值,那么就会抛出ClassCastException异常,如果不处理,程序会崩溃,所以在使用查询的时候:

 try {
            int i = mSharedPreferences.getInt("age", 1);
            mTextViewa.setText("" + i);
        } catch (ClassCastException e) {
             mTextViewa.setText("出现异常了");
        }

(4)getFloat(String key,float defValue)

返回值:float
与getBoolean方法一样,不同的是得到的是float值,针对的是float类型的数据查询,如果传入的Key对应的值不是vlaue,那么也会抛出ClassCastException异常。

(5)getInt(String key,int defValue)

返回值:int
与getBoolean方法一样,不同的是得到的是int值,如果传入的key查询出来不是int类型的,那么也会抛出ClassCastException异常。

(6)getLong(String key ,long defValue)

返回值:long
与getBoolean方法一样,不同的是得到的是long值,如果传入的key查询出来不是int类型的,那么也会抛出ClassCastException异常。

(7)getString(String key,String defValue)

与getBoolean方法一样,不同的是得到的是String值,如果传入的key查询出来不是int类型的,那么也会抛出ClassCastException异常,defVlue也可以是null

(8)getStringSet(String key,Set<String> defValues)

返回值:Set<String>
defValues可以是null,也会抛出ClassCastException,另外,存入的是Set<String>,也就是一组String数据。

(9)registerOnSharedPreferenceChangeListener(SharedPreferences.OnSharedPreferenceChangeListener listener)

警告:首选项管理器当前不存储对侦听器的强引用。 您必须存储对侦听器的强引用,否则它将容易被垃圾收集。 只要您需要侦听器,我们建议您在对象的实例数据中保留对侦听器的引用。
此监听发生在此SharedPreferences文件内容发生更改的时候。

(10)unregisterOnSharedPreferencesChangeListener(SharedPreferences.OnSharedPreferenceChangeListener listener)

取消监听

(11)edit()

返回值:SharedPreferences.Editor
具体作用参照下面Editor中方法具体介绍。

介绍SharedPreferences中的Editor中方法

操作当前SharedPreferences中文件中的值,只有通过commit或者apply之后,才会建立相应的SharedPreferences文件与完成相应的修改。

(1)putBoolean(String key,boolean value)

返回值:SharedPreferences.Editor
写入到当前SharedPreferences文件中一对key-value或者是修改已经存在key对应的value的值。

(2)putFloat(String key,float value)

返回值:SharedPreferences.Editor
写入到当前SharedPreferences文件中一对key-value或者是修改已经存在key对应的value的值。

(3)putInt(String key,int value)

返回值:SharedPreferences.Editor
写入到当前SharedPreferences文件中一对key-value或者是修改已经存在key对应的value的值。

(4)putLong(String key,long value)

返回值:SharedPreferences.Editor
写入到当前SharedPreferences文件中一对key-value或者是修改已经存在key对应的value的值。

(5)putString(String key,String value)

返回值:SharedPreferences.Editor
写入到当前SharedPreferences文件中一对key-value或者是修改已经存在key对应的value的值。

(6)putStringSet(String key,Set<String> values)

返回值:SharedPreferences.Editor
写入到当前SharedPreferences文件中一对key-value或者是修改已经存在key对应的value的值。
对此参数传递null等价于使用此键调用remove(String)

(7)remove(String key)

返回值:SharedPreferences.Editor
移除当前SharedPreferences中某个Key以及其所代表的值

(8)clear()

返回值:SharedPreferences.Editor
移除所有的key以及value从当前SharedPreferences文件中。

(9)commit()

返回值:boolean
如果新值成功写入当前SharedPreferences,则返回true;
会自动执行请求的参数,替换SharedPreferences中的修改,如果没有的话,就会创建此文件
当两个编辑器同时修改时,最后一个调用commit的成功
如果你不关心返回值,并且你在应用程序的主线程中使用它,请使用apply();

(10)apply()

返回值:void
1、如果先后apply()了几次,那么会以最后一次apply()的为准。
2、commit()是把内容同步提交到硬盘的。而apply()先立即把修改提交到内存,然后开启一个异步的线程提交到硬盘,并且如果提交失败,你不会收到任何通知。
也就是说,commit有返回值,apply没有返回值,commit写入文件操作是在主线程中,会消耗资源,apply写入文件的操作是异步的,会把Runnable放到线程池中执行
3、如果当一个apply()的异步提交还在进行的时候,执行commit()操作,那么commit()是会阻塞的。而如果commit()的时候,前面的commit()还未结束,这个commit()还是会阻塞的。(所以引起commit阻塞会有这两种原因)
4、由于SharePreferences在一个程序中的实例一般都是单例的,所以如果你不是很在意返回值的话,你使用apply()代替commit()是无所谓的。

例子

初始一个应用在data/data/应用包名下:

《Android中数据存储与文件存储》 初始未进行操作时候应用的样子.png

当你使用了SharedPreferences时候

public static final String FILE_NAME = "MySharedPreferences";
 mSharedPreferences = getSharedPreferences(FILE_NAME, 0);
 //执行完上述话,会在应用程序中建立shared_prefs文件夹,里面将要存放sharedPreferences文件,但是目前没有内容

当你完成一些查询操作,但是没有完成使用Editor

mSharedPreferences = getSharedPreferences(FILE_NAME, 0);  
String name = mSharedPreferences.getString("name","xlj");
 //也没有在shared_prefs文件夹下建立相应的FILE_NAME文件,另外这样用也不会报错,返回name = xlj

通过操作Editor

mSharedPreferences = getSharedPreferences(FILE_NAME, 0);  
SharedPreferences.Editor editor = mSharedPreferences.edit();
        editor.putString("name", "xljs");
        editor.commit();

《Android中数据存储与文件存储》 建立相应的SharedPreferences.png

演示第二种得到SharedPerferences方法:

       mSharedPreferences = getPreferences(0);
        SharedPreferences.Editor editor = mSharedPreferences.edit();
        editor.putString("age", "10");
        editor.putBoolean("is", true);
        editor.commit();

        Map<String,?> map = mSharedPreferences.getAll();

        mTextViewa.setText(map.toString());
        try {
            int i = mSharedPreferences.getInt("age", 1);
            mTextViewa.setText("" + i);
        } catch (ClassCastException e) {
             mTextViewa.setText("出现异常了");
        }

当前Activity名字:SharedPActivity

《Android中数据存储与文件存储》 使用第二种方法.png

使用内部存储

直接在设备的内部存储中保存文件。默认情况下,保存到内部存储的文件是应用的私有文件,其他应用(和用户)都不能访问这些文件。当用户卸载应用的时候,这些文件也会被移除。

简单来讲,就是给我们自己的应用,在内部存储分配的空间,默认的有files文件夹、cache文件夹、
(其实共享首选项也是提供的默认的shared_prefs文件夹)
当然了,用户也可以不用这些已经提供的files 与 cache文件夹,自己建立自己的的文件夹,并放文件.

读写files文件夹(根文件夹)中文件

(1)openFileOutput()方法

属于android.content.Context;
返回值:FileOutputStram一个文件输出流
参数一:name,你要打开的文件的名字,这里的name只能是名字,不能使用”/name”此类的。
参数二:MODE_PRIVATE,别的模式处于安全都摒弃了
不同于常见getxxx方法,字面翻译就是:打开files文件夹下某个文件的输出流
简单的方式得到一个文件输出流

例子

初始默认的应用程序内部存储如下图:

《Android中数据存储与文件存储》 初始.png

shared_prefs文件夹是因为操作过SharedPreferences才有的,默认的初始就有一个空的cache文件夹。

                  private String FILE_NAME = "hello_file";
                  private String FILE_NAME_NEW = "test.txt";

                  String string = "怎么都是写不会的啊";
                try {
                    FileOutputStream fileOutputStream = openFileOutput(FILE_NAME, Context.MODE_PRIVATE);

                    fileOutputStream.write(string.getBytes());

                    fileOutputStream.close();
                } catch (FileNotFoundException e) {
                    e.printStackTrace();
                    mTextView2.setText("出现文件找不到异常");
                } catch (IOException e) {
                    e.printStackTrace();
                    mTextView2.setText("出现了IO异常");
                }

结果如图:

《Android中数据存储与文件存储》 结果

出现了files文件夹以及相应的文件。

           try {
                    FileOutputStream fileOutputStream = openFileOutput(FILE_NAME_NEW, Context.MODE_PRIVATE);

                    fileOutputStream.write(string.getBytes());

                    fileOutputStream.close();
                } catch (FileNotFoundException e) {
                    e.printStackTrace();
                    mTextView2.setText("出现文件找不到异常");
                } catch (IOException e) {
                    e.printStackTrace();
                    mTextView2.setText("出现了IO异常");
                }

结果:

《Android中数据存储与文件存储》 结果

如果给文件名字带上具体的文件格式,也没有什么影响。

说明:
自Api17以来,常量MODE_WORLD_READABLE和MODE_WORLD_WRITEABLE已经被弃用。
从Android N开始,使用这些常量会引发securityException。
这意味着,面向Android N 和更高版本的应用无法按名称共享私有文件,尝试共享file://URL将会导致引发FileUriExposedException。
如果应用需要与其他应用共享私有文件,则可以将FileProvider与FLAG_GRANT_READ_URI_PERMISSION配合使用。

openFileInput

属于android.content.Context;
参数一:传入你想读取的文件的名字,这里的name只能是名字,不能使用”/name”此类的。
返回值:返回一个FileInputStream。
不用于常见的getxxx方法,字面翻译:打开文件输入流
方便直接得到一个流

例子
             try {
                    FileInputStream fileInputStream = openFileInput(FILE_NAME);

                    byte[] buf = new byte[1024];

                    int hasRead = 0;

                    StringBuffer sb  = new StringBuffer("");

                    //读取文件部分
                    while((hasRead = fileInputStream.read(buf))>0){
                        sb.append(new String(buf,0,hasRead));
                    }

                    fileInputStream.close();

                    mTextView.setText(sb.toString());

                } catch (FileNotFoundException e) {
                    e.printStackTrace();
                    mTextView.setText("出现文件找不到异常了");
                } catch (IOException e) {
                    e.printStackTrace();
                    mTextView.setText("出现了IO异常了");
                }
            }

《Android中数据存储与文件存储》 GIF.gif

通过动图可以看出来,是可以的读取出来数据。
另外需要注意的是,如果你开始读的文件不存在就会报FileNotFoundException异常,当时会建立files文件夹,如果此文件夹不存在的话。

getFilesDir()

也是在android.content.context中提供的属性
执行此方法的到的是:
如果不想用openFileInput与openFileOutput两个方法,想使用get系列的方法也是可以的.
直接得到的是文件的路径,而不是得到一个流,这样可以自己用.

/data/user/0/example.xlj.savecundemo/files
应用程序内部存储空间中files的目录
获取在其中存储内部文件的文件系统目录的绝对路径。
例子

这只是一种简单的例子,延时了某一个用法

               File file = new File(getFilesDir(),"infos.txt");//返回值是File对象

               try {
                   FileOutputStream outputStream =  new FileOutputStream(file);

                   String s = "这是一个测试的例子,往info里面写入数据";
                   outputStream.write(s.getBytes());

                   outputStream.close();

                   mTextView4.setText("文件写入成功");
               } catch (FileNotFoundException e) {
                   e.printStackTrace();
                   mTextView4.setText("文件未找到异常");
               } catch (IOException e) {
                   e.printStackTrace();
                   mTextView4.setText("出现了IO异常");
               }

结果如图:

《Android中数据存储与文件存储》 结果

也是在files文件夹下创建了infos.txt文件。完成了存储

其他的常用方法,针对于自己应用内部存储中 files (强调的是files)文件夹下的操作

fileList()

返回保存在内部存储空间一系列文件。

      String [] list = fileList();
        for (int i = 0; i < list.length; i++) {
            Log.d("xljxlj",list[i]);
        }

得到的结果如下:
         05-05 03:43:48.829 7396-7396/example.xlj.savecundemo D/xljxlj: hello_file
         05-05 03:43:48.829 7396-7396/example.xlj.savecundemo D/xljxlj: test.txt
         05-05 03:43:48.829 7396-7396/example.xlj.savecundemo D/xljxlj: infos.txt

deleteFile(String name)

删除保存在内部存储的文件,其返回值是一个Boolean,表示是否删除成功。
这里的name只能是名字,不能使用”/name”此类的。

说明

如果在一读一写的操作中,出现了乱码,那么上面的代码需要做如下调整
写入的时候

 try {
                    FileOutputStream fileOutputStream = GudleActivity.this.openFileOutput(ApiTools.MAIN_INFO_FILES, Context.MODE_PRIVATE);
                    OutputStreamWriter outputStreamWriter = new OutputStreamWriter(fileOutputStream,"UTF-8");
                    outputStreamWriter.write(response.toString());
                    outputStreamWriter.flush();
                    outputStreamWriter.close();
                } catch (IOException e) {
                    //出现异常缓存失败
                    e.printStackTrace();
                }

读取的时候

try {
            FileInputStream fileInputStream = openFileInput(ApiTools.MAIN_INFO_FILES);
            InputStreamReader reader = new InputStreamReader(fileInputStream,"UTF-8"); //最后的"GBK"根据文件属性而定,如果不行,改成"UTF-8"试试
            BufferedReader br = new BufferedReader(reader);
            String line;
            while ((line = br.readLine()) != null) {
                LogUtils.debugInfo("maininfo","得到数据:"+line);
            }
            br.close();
            reader.close();

        } catch (IOException e) {
            e.printStackTrace();
        }

读写cache文件夹中文件

如果您想要缓存一些数据,而不是永久的存储这些数据,可以考虑Cache文件夹下存储。
当设备的内部存储空间不足的时候,Android可能会删除这些缓存文件以回收空间。但您不应该依赖系统来为你清理这些文件,而应该是中自行维护缓存文件,使其占用的控件保持在合理的限制范围内例如1MB,当用户卸载您的应用的时候,这些文件也会被移除。

通过getCacheDir()

例子
                File  file = new File(getCacheDir(),"infos.txt"); //返回值是File对象
                try {
                    FileInputStream fileInputStream = new FileInputStream(file);

                    byte[] bytes = new byte[1024];

                    int hascode = 0;

                    StringBuffer stringBuffer = new StringBuffer("");

                    while ((hascode = fileInputStream.read(bytes))>0){
                        stringBuffer.append(new String(bytes,0,hascode));
                    }

                    mTextView5.setText(stringBuffer.toString());

                    fileInputStream.close();
                } catch (FileNotFoundException e) {
                    e.printStackTrace();
                    mTextView5.setText("出现了文件未找到异常");
                } catch (IOException e) {
                    e.printStackTrace();

                    mTextView5.setText("出现了IO异常了");
                }

《Android中数据存储与文件存储》 演示cache.png

忽略info,这是之前做的测试,目前只贴了一个infos.txt的代码

读写内存存储空间中自定义文件夹中文件

如果你不想放在files与cache文件夹下,可以通过getDir()方法,其返回值是:File
如果执行getDir(“test”,MODE_PRIVATE)
这样在执行代码,会先建立这个文件夹.,不过都会自动添加app_前缀,这个不需要自己去考虑

/data/user/0/example.xlj.savecundemo/app_test
如果存在则返回先关文件夹的路径,如果没有的话就创建并且返回相关的FIle。

那么相关的操作就类似了,

       File file = new File(getDir("test",MODE_PRIVATE),"who.txt");
        try {
            FileOutputStream fileOutputStream = new FileOutputStream(file);
            String ss = "自己新建立了一个文件夹";
            fileOutputStream.write(ss.getBytes());
            fileOutputStream.close();
            Toast.makeText(FileActivity.this,"写入成功",Toast.LENGTH_SHORT).show();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }

《Android中数据存储与文件存储》 结果.png

如图中app_test就是通过执行上述代码完成的。这里只演示了文件写入,对于读取文件通上述,不在过多代码演示。

读取资源文件(不属于内部存储内容)这部分是引申

如果在编译时想要保存应用中的静态文件,请在项目的res/raw/目录中保存该文件。可以通过openRawResource()打开该资源文件并传递R.raw.xxx资源ID,此方法返回一个InputStream(注意不是FileInputStream),您可以使用该流传输读取文件(但是不能写入到原始文件,只能读取)
也是得到一个资源文件流,注意不是应用存储内部存储中
下面通过例子进行演示:
准备文件:

《Android中数据存储与文件存储》 WTISQN`{LBKB234VF2$812T.png

演示读取activity.file.xml文件

            try {
                    InputStream fileInputStream =  FileActivity.this.getResources().openRawResource(R.raw.activity_file);

                    byte[] buf = new byte[1024];

                    int hasRead = 0;

                    StringBuffer sb  = new StringBuffer("");

                    //读取文件部分
                    while((hasRead = fileInputStream.read(buf))>0){
                        sb.append(new String(buf,0,hasRead));
                    }

                    fileInputStream.close();

                    mTextView3.setText(sb.toString());

                } catch (Resources.NotFoundException e) {
                    e.printStackTrace();
                    mTextView3.setText("出现文件找不到异常了");
                } catch (IOException e) {
                    e.printStackTrace();
                    mTextView3.setText("出现了IO异常了");
                }

《Android中数据存储与文件存储》 结果

演示如何读取一个图片

              //演示怎么读取一张图片
              InputStream inputStream = getResources().openRawResource(R.raw.my_my_name);


              //方式一
              Bitmap bitmap = BitmapFactory.decodeStream(inputStream);
             mImageView.setImageBitmap(bitmap);

                //方式二
                BitmapDrawable bitmapDrawable = new BitmapDrawable(inputStream);

                Bitmap bitmap = bitmapDrawable.getBitmap();

                mImageView.setImageBitmap(bitmap);

注意:如果出现了乱码,建议首先检查源文件,也就是raw目录下的文件是否是乱码。

使用外部存储

每个兼容Android的设备都支持可用于保存文件的共享“外部存储”。该存储可能是可移除的存储介质(如SD卡,次要卷),也可能是不可移除的存储(主要卷)。现在大部分手机,都自带不可移除的存储.
保存到外部存储的文件,是全局可读的,用户可以通过USB链接上进行修改.

共享,意味着所有的应用都可以使用,并不是前面提到的每个应用中的内部存储.

简单来讲,外部存储其实分为了两种文件类型,一种是公共的存储文件夹,一种是给具体应用的存储文件夹(也分为file 与 cache )

区别:这部分外部存储的就是getExtralxxx开头的了,表明是外部存储.

Ram:手机的运行内存。
Rom:手机内存,表现为内部存储空间,可以理解为电脑本身的硬盘。
SD:外部存储空间,可以理解为电脑的移动硬盘。

其实外部存储系统给开发者使用的地方可以分为公共文件夹与应用私有文件夹区域:

《Android中数据存储与文件存储》 前言图.png

一般来讲就是在Storage/sdcard目录下,如图所示,DCIM、Download、Movies等等都是属于公共文件夹,这部分文件夹对于当前手机上所有的应用都是公开的。Android/data目录下,有一个个以应用名字命名的文件夹,里面可以存放当前应用的私有文件与缓存。

第一步:检查介质可用性

在使用外部存储执行任何工作之前,应始终调用Environment.getExternalStorageState()方法以检查介质是否可用,介质可能处于缺失、只读或者其他状态。

/* Checks if external storage is available for read and write */
public boolean isExternalStorageWritable() {
    String state = Environment.getExternalStorageState();
    if (Environment.MEDIA_MOUNTED.equals(state)) {
        return true;
    }
    return false;
}

/* Checks if external storage is available to at least read */
public boolean isExternalStorageReadable() {
    String state = Environment.getExternalStorageState();
    if (Environment.MEDIA_MOUNTED.equals(state) ||
        Environment.MEDIA_MOUNTED_READ_ONLY.equals(state)) {
        return true;
    }
    return false;
}

关于state可能值介绍:

MEDIA_UNKNOWN:  未知存储状态,例如路径未被已知存储介质支持时。
MEDIA_REMOVED: 存储介质不存在的存储状态。
MEDIA_UNMOUNTED:  存储状态,如果介质存在但未安装。
MEDIA_CHECKING:  如果介质存在并正在进行磁盘检查,则为存储状态。
MEDIA_NOFS:  存储状态,如果介质存在但空白或正在使用不受支持的文件系统。
MEDIA_MOUNTED:  存储状态,如果介质存在并且以读/写访问的方式安装在其安装点。
MEDIA_MOUNTED_READ_ONLY:  存储状态,如果介质存在并且以只读访问权限挂载到它的挂载点。
MEDIA_SHARED:  存储状态,如果介质未安装,并通过USB海量存储共享。
MEDIA_BAD_REMOVAL:  存储状态,如果介质在卸载之前被删除。
MEDIA_UNMOUNTABLE:   存储状态,如果介质存在但无法安装。 通常,如果介质上的文件系统损坏,就会发生这种情况。
MEDIA_EJECTING:  存储状态,如果媒体正在被弹出的过程中。

getExternalStorageState()方法将会返回可能需要检查的其他状态,当应用需要访问介质时,可以使用这些状态向用户通知更多信息。
更多的关于Environment的使用介绍;

第二步:检查权限

<user-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<user-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>

两点说明:
(1)android 6.0提出了运行时权限,需要开发过程中注意,可以参考文章
(2)从Android4.4开始,如果应用仅仅读取或者写入应用的私有文件,则不需要这些权限,只有使用外部存储的公共文件夹时需要。

第三步:在外部存储公共文件位置存储文件。

一般而言,应该将用户可通过您的应用获取的新文件保存到设备上的“公共”位置,以便其他应用能够在其中访问这些文件,并且用户也能轻松地从该设备复制这些文件。 执行此操作时,应使用共享的公共目录之一,例如 Music/、Pictures/ 和 Ringtones/ 等。。

getExternalStoragePublicDirectory()方法,可与其他应用共享

返回值:File
参数:type
type可以有如下几种选择,其表示顶级公共目录,当做是标准目录,却没有强制选择使用相应的:

DIRECTORY_ALARMS:用于放置任何音频文件的标准目录,该文件应位于用户可以选择的闹铃文件列表(而不是普通音乐),的特定音频文件。
DIRECTORY_DCIM:将设备安装为相机时传统照片和视频位置。
DIRECTORY_DOCUMENTS:放置用户创建的文档的标准目录。
DIRECTORY_DOWNLOADS:用于放置用户下载文件的标准目录,这是顶级公共目录的约定。
DIRECTORY_MOVIES:用户放置用户电影的标准目录,但是媒体扫描器会在任何目录中查找和收集电影。
DIRECTORY_MUSIC:用于放置任何音频文件的标准目录。
DIRECTORY_NOTIFICATIONS:放置任何音频文件的标准目录,一般是用户可以选择的通知列表(而不是普通音乐)
DIRECTORY_PICIURES:放置可供用户使用的图片的标准目录。但是媒体扫描器将在任何目录中查找和收集图片。
DIRECTORY_PODCASTS:放置任何音频的标准目录,是可供选择的播客列表中(而不是普通音乐)。
DIRECTORY_RINGTONES:用于放置任何音频的标准目录,该文件可供用户选择铃声列表。

Google官方给出的例子:

void createExternalStoragePublicPicture() {
    //创建了一个公共的放置图片的路径。
    // Context.getExternalMediaDir() ,下面会进行解释
   //上述方法的路径:/storage/sdcard/Android/media/example.xlj.savecundemo
    File path = Environment.getExternalStoragePublicDirectory(
            Environment.DIRECTORY_PICTURES);
  //在5.1上测试:/storage/sdcard/Pictures
  //在7.0上测试:/storage/emulated/0/Pictures
    File file = new File(path, "DemoPicture.jpg");
  //在5.1上测试: /storage/sdcard/Pictures/DemoPicture.jpg
  //在7.0上测试;/storage/emulated/0/Pictures/DemoPicture.jpg

    try {
        // Make sure the Pictures directory exists.确保文件夹存在,下面的方法表示创建相应的文件夹。具体的文件就要靠流的操作来完成了。
        path.mkdirs();

        // Very simple code to copy a picture from the application's
        // resource into the external file.  Note that this code does
        // no error checking, and assumes the picture is small (does not
        // try to copy it in chunks).  Note that if external storage is
        // not currently mounted this will silently fail.
        InputStream is = getResources().openRawResource(R.drawable.balloons);
        OutputStream os = new FileOutputStream(file);
        byte[] data = new byte[is.available()];
        is.read(data);
        os.write(data);
        is.close();
        os.close();

        // Tell the media scanner about the new file so that it is
        // immediately available to the user.
        //告诉媒体扫描器有关新文件的信息,立即告诉用户。
       //下面代码表示演示了手机多媒体扫描的流程。
        MediaScannerConnection.scanFile(this,
                new String[] { file.toString() }, null,
                new MediaScannerConnection.OnScanCompletedListener() {
            public void onScanCompleted(String path, Uri uri) {
                Log.i("ExternalStorage", "Scanned " + path + ":");
                Log.i("ExternalStorage", "-> uri=" + uri);
            }
        });
    } catch (IOException e) {
        // Unable to create file, likely because external storage is
        // not currently mounted.
        Log.w("ExternalStorage", "Error writing " + file, e);
    }
}

void deleteExternalStoragePublicPicture() {
    // Create a path where we will place our picture in the user's
    // public pictures directory and delete the file.  If external
    // storage is not currently mounted this will fail.
    File path = Environment.getExternalStoragePublicDirectory(
            Environment.DIRECTORY_PICTURES);
    File file = new File(path, "DemoPicture.jpg");
    file.delete();
}

boolean hasExternalStoragePublicPicture() {
    // Create a path where we will place our picture in the user's
    // public pictures directory and check if the file exists.  If
    // external storage is not currently mounted this will think the
    // picture doesn't exist.
    File path = Environment.getExternalStoragePublicDirectory(
            Environment.DIRECTORY_PICTURES);
    File file = new File(path, "DemoPicture.jpg");
    return file.exists();
}

例子

    //检查设备介质是否可以使用
    public boolean isExternalStorageWritable() {
        String state = Environment.getExternalStorageState();

        if (Environment.MEDIA_MOUNTED.equals(state)) {
            return true;
        }

        return false;
    }

先检查设备介质是否可用:

                if (isExternalStorageWritable()) {
                  //这是使用的运行时权限一个注解方式动态申请权限
                    SDTestActivityPermissionsDispatcher.saveFileOneWithCheck(SDTestActivity.this);
                } else {
                    mTextView1.setText("设备不可以用");
                }

设备可用,执行相应的方法:

     //保存可以与其他应用共享的文件
    @NeedsPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE)
    public void saveFileOne() {
        //保在下载文件夹中
        File file = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS), "testwenjian");

     
        /**
         * File file = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS),"testwenjian");
         * 5.1:/storage/sdcard/Download/testwenjian
         * 7.0:/storage/emulated/0/Download/testwenjian
         * 不管是file.mkdir还是file.mkdirs都是创建目录的,不是创建新的文件
         * 创建了一次新的之后,表示已经存在了,那么就不会在创建了,会返回一个false,表示文件夹未创建成功,但是实际上文件已经存在了。
          *具体效果如下图一
         */
        if (file.mkdir()) {
            //表示文件创建成功了
            mTextView1.setText("文件夹创建成功了");
            //开始写入文件
            File file1 = new File(file, "xlj.txt");
            Log.d("newtest", "hah: " + file1.toString());


            /****
             * File file1 = new File(file,"xlj.txt");
             * 5.0:/storage/sdcard/Download/testwenjian/xlj.txt
             * 7.0: /storage/emulated/0/Download/testwenjian/xlj.txt
             * 通过测试不难发现,使用具体的文件通过具体的 流操作就可以完成了, 是文件输出流,其本身就具有再具体的file路径下创建文件的功能。
             *具体效果见图二:
             */

            String info = "恭喜您,数据写入成功了";
            try {
                FileOutputStream fileOutputStream = new FileOutputStream(file1);

                fileOutputStream.write(info.getBytes());

                fileOutputStream.close();

                mTextView1.setText("创建新的文件成功了");

            } catch (FileNotFoundException e) {
                e.printStackTrace();
                mTextView1.setText("创建新的文件失败了");
            } catch (IOException e) {
                e.printStackTrace();

                mTextView1.setText("创建新的文件出现了IO异常了");
            }
        } else {
            mTextView1.setText("文件夹未创建成功");
            file.delete();
        }
    }

建立文件夹,如图一:

《Android中数据存储与文件存储》 图一

最后建立相应的文件,如图二:
在5.0系统下:

《Android中数据存储与文件存储》 图二

在7.0系统下:

《Android中数据存储与文件存储》 7.0测试一外部存贮.png

另外,保存到外部存储的公共区域上如果你想在媒体扫描程序中隐藏您的文件

在您的外部文件目录中包含名为 `.nomedia` 的空文件(注意文件名中的点前缀)。 这将阻止媒体扫描程序读取您的媒体文件,
并通过 MediaStore 内容提供程序将其提供给其他应用。

第四步:将数据保存在外部存储上的应用私有位置上,这样这些文件只能本应用使用,别的应用程序无法访问.

简单来讲就是五个方法,五个方法得到想要操作的文件夹路径,接下来就是流的操作,往你想用的文件夹路径中创建文件或者读取文件了,所以这里只介绍这五个方法,对于流的操作不在写。

当用户卸载您的应用时,此目录及其内容将被删除。此外,系统媒体扫描程序不回读取这些目录中的文件,因此不能从MediaStore内容提供程序访问这些文件。同样,不应将这些目录用于最终属于用户的媒体,例如使用您的应用拍摄或编辑照片或者您的应用购买的音乐等,这些文件应该放在公共的目录中。

当应用卸载时候,这部分内容也会删除,另外,尽管MediaStore提供的程序不能访问此部分内容,但是别的应用程序具有READ_XXX权限的仍可以获取全部文件时,仍可以得到,所以并不是完全的保密.

(1)getExternalFilesDir(String type)

如果正在处理的文件不适合其他应用使用(比如应用使用的图形纹理或者音效),则可以考虑使用外部存储上的私有存储目录。此方法需要传入type参数指定子目录的类型。如果不需要特定的目录,也可以传递null获得应用私有目录的根目录。也可以指定具体的比如DIRECTORY_MOVIES

从Android 4.4开始,读取或者写入应用私有目录中的文件不在需要READ_EXTERNAL_STORAGE或者WRITE_EXTERNAL_STORAGE权限,可以通过添加maxSdkVersion属性来声明,只在较低版本的Android中请求该权限:

<manifest...>
            <users-permission android:name = "android.permission.WRITE_EXTERNAL_STORAGE"
                                               andorid:maxSdkVersion = "18"/>
</manifest>

下面是一些打印的Log:进行方法的展示

(1)getExternalFilesDir(null)
    5.0:
    /storage/sdcard/Android/data/example.xlj.savecundemo/files
   7.0:
    /storage/emulated/0/Android/data/example.xlj.savecundemo/files

(2)getExternalFilesDir(Environment.DIRECTORY_MOVIES)
     5.0:
     /storage/sdcard/Android/data/example.xlj.savecundemo/files/Movies
    7.0:
    /storage/emulated/0/Android/data/example.xlj.savecundemo/files/Movies

(3)getExternalFilesDir("xlj")
    /storage/sdcard/Android/data/example.xlj.savecundemo/files/xlj

下面是测试例子:

 File file = new File(getExternalFilesDir(null), "DemoFile.jpg");

        try {
            // Very simple code to copy a picture from the application's
            // resource into the external file.  Note that this code does
            // no error checking, and assumes the picture is small (does not
            // try to copy it in chunks).  Note that if external storage is
            // not currently mounted this will silently fail.
            InputStream is = getResources().openRawResource(R.raw.my_my_name);
            OutputStream os = new FileOutputStream(file);
            byte[] data = new byte[is.available()];
            is.read(data);
            os.write(data);
            is.close();
            os.close();
        } catch (IOException e) {
            // Unable to create file, likely because external storage is
            // not currently mounted.
            Log.w("ExternalStorage", "Error writing " + file, e);
        }

在5.0系统下;

《Android中数据存储与文件存储》 5.0系统之下.png

在7.0系统下:

《Android中数据存储与文件存储》 7.0测试隐私文件存储.png

(2)getExternalFilesDirs(String type)

与前面getExternalFilesDir方法一样,不同的是此方法得到的是一个file[],一般取file[0]使用即可

有时,已分配某个内部存储器分区用作外部存储的设备可能提供了SD卡槽。在使用运行 Android 4.3和更低版本的这类设备时,getExternalFilesDir()方法将仅提供内部分区的访问权限,而您的应用无法读取或者写入SD卡。

从Android 4.4开始,可以通过调用getEx ternalFilesDirs()来同时访问这两个位置,该方法将会返回包含各个位置条目的File数组。数组中的第一个条目被视为外部主存储,除非该位置已满或者不可用,否则应该使用该位置。

如果希望支持Android 4.3和更低版本的同时访问两个可能的位置,请使用支持库中的静态方法
ContextCompat.getExternalFilesDirs();在Android 4.3或者更低的版本中,此方法也会返回一个File数组,但其中始终包含一个条目,只能在使用的时候使用File[0];

尽管MediaStore内容提供程序不能访问getExternalFilesDir()和getExternalFilesDirs()所提供的目录,但其他具有 READ_EXTERNAL_STORAGE权限的用用仍可访问外部存储上的所有文件,如上述文件,如果您想要完全限制您的文件访问权限,则应该讲您的文件写入到内部存储。

(3)getExternalCacherDir();
         5.0
         /storage/sdcard/Android/data/example.xlj.savecundemo/cache
         7.0
         /storage/emulated/0/Android/data/example.xlj.savecundemo/cache
    

与前面叙述的ContextCompat.getExternalFilesDirs()相似,您也可以通过调用ContextCompat.getExternalCacheDirs()来访问外部存储上的缓存目录。

为了节省文件空间并保持应用性能,您应该在应用的整个生命周期内仔细管理您的缓存文件并移除其中不再需要的文件。

调用此方法,得到的文件路径如图所示:
5.0系统之下:

《Android中数据存储与文件存储》 测试存储缓存隐私文件.png

7.0系统之下;

《Android中数据存储与文件存储》 7.0测试缓存存储.png

(4)getExternalCacherDirs();

此方法与前面提到的getExternalCacherDirs()一样。其返回值也是Files[];

(5)getExternalMediaDir()

执行此方法:

/storage/emulated/0/Android/media/example.xlj.savecundemo

7.0系统之下:

《Android中数据存储与文件存储》 结果

发现此方法是在Android文件夹下建立了media文件夹,在其中建立了你自己应用包名的文件夹,你可以放数据。前面提到的四个方法都是放在了Android/data/应用包名/下

数据库存储

Android 提供了对于SQlite数据库的完全支持,应用中的任何类(不包括应用外部的类)均可按名称访问您所创建的任何数据库。

数据库存储涉及到两个关键类:

SQLiteDatabase

此类SQLiteDatabase公开了管理SQLite数据库的方法。SQLiteDatabase具有创建、删除、执行SQL命令以及执行其他常见数据库管理任务的方法。

SQLiteOpenHelper

实际操作是写一个类继承此类
SQLiteOpenHelper是管理数据库创建和版本管理的助手类。

数据库名称在应用中必须是唯一的,而不是跨所有应用程序。

比如自己写的一个例子:

public class MySQLiteOpenHelper extends SQLiteOpenHelper {

    //构造方法一
    public MySQLiteOpenHelper(Context context, String name, SQLiteDatabase.CursorFactory factory, int version, DatabaseErrorHandler errorHandler) {
        super(context, name, factory, version, errorHandler);
    }

    //构造方法二
    public MySQLiteOpenHelper(Context context, String name, SQLiteDatabase.CursorFactory factory, int version) {
//        super(context, name, factory, version);
        //可以通过外界参数传入创建,这里直接赋值了
        super(context,"db_openhelper",null,1);

    }

    //重写的onCreate方法
    @Override
    public void onCreate(SQLiteDatabase sqLiteDatabase) {
             sqLiteDatabase.execSQL("create table if not exists tb_my(_id integer primary key autoincrement,title text)");
    }

    //用于更新数据库版本
    @Override
    public void onUpgrade(SQLiteDatabase sqLiteDatabase, int i, int i1) {
        //参数一表示以前的版本,oldVersion
        //参数二表示新的版本,newVersion
        if (i1 > i){
            sqLiteDatabase.execSQL("drop table if exists tb_my");
            onCreate(sqLiteDatabase);
        }
    }
}

在应用中使用:

       //使用自定义的SQLiteOpenHelper
        MySQLiteOpenHelper mySQLiteOpenHelper = new MySQLiteOpenHelper(this,"",null,1);
        //执行完上局话得到。。什么反应也没有
        SQLiteDatabase sqLiteDatabase = mySQLiteOpenHelper.getReadableDatabase();
        //通过帮助类得到管理数据库的方法。便会得到在内部存储中建立相应的数据库

        sqLiteDatabase.insert("tb_my","title",null);

结果:

《Android中数据存储与文件存储》 O5{@J1246`E4CL(S5%7NRWC.png

写给自己的备注:

关于数据库这边简单记录一下,上面涉及到的两个链接都是Google官方提供的资料,计划在整理新的文档,因为其涉及的还是比较多的。整理完成后,将Google提供的链接替换成自己文章的链接。

网络存储

所谓的网络存储简单来讲,就是将数据资料放在网络服务器上。
由于市面上的网络框架比较多,就不做详细介绍了。下面提供两个Google推荐的两个参考类、

java.net.*
android.net.*

    原文作者:难得糊涂与君勉
    原文地址: https://www.jianshu.com/p/f31a013824ce
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞