【Android的从零单排开发日记】——Android数据存储(上)

在讲解Android的数据源组件——ContentProvider之前我觉得很有必要先弄清楚Android的数据结构。
数据和程序是应用构成的两个核心要素,数据存储永远是应用开发中最重要的主题之一,也是开发平台必须提供的基础功能。不光是在Android平台上,在其他的平台上,数据的存储永远是不可缺少的一块。Android的数据存储是构建在Linux的文件系统上,它充分利用Linux的账号系统来限定应用对数据的访问,部署了一套安全和灵活并重的数据存储解决方案。Android的文件框架,以及各种数据存储手段,具体包括:Android的文件系统操作,设置文件的使用,数据库的使用,数据源组件的使用以及云端数据的存储

一、Android的文件系统

  1. Android系统文件目录

    目录 内容
    system 系统目录,放置在Android运行所需的核心库
    data 应用目录,放置着运行在Android上的应用及其数据
    sdcard 扩展存储卡目录,用来存放共享的数据
    mnt 记录Android挂载的外部存储信息
  2. Android的应用数据存储机制
    在Android中,第三方应用及其数据,都存放在data目录下。其中,应用安装包会被存放到/data/app/目录下,每个安装包的文件名都形如:应用包名.apk,以避免重复。
    比如包名为com.test.sample的应用,其应用数据的目录为/data/data/com.test.sample/。对应的数据库文件存储在/data/data/com.test.sample/database/目录下,设置文件存储在/data/data/com.test.sample/shared_prefs/,自定义的应用数据文件存储在目录/data/data/com.test.sample/files/下,等等。
    不仅如此,Android还会为每个应用创建一个账号,只有通过本应用的账号才有权限去运行该应用的安装包文件,读写应用数据目录下的文件(当然root权限除外啊~),从而保证了该应用数据不会再被其他应用获取或破坏。
  3. Android的文件操作
    从应用数据目录下可以看出,数据文件可以分成两类,一类是放置在扩展存储器中的文件,即/sdcard/目录下的文件,它们可以被各个应用共享;而另一类则是放在该应用数据目录下文件,它们仅能被各个应用独享,不能被其他应用读写。
    (1)扩展存储器中的文件读写方式跟标准的java文件处理无异。
           我们可以新建一个FileUtil的工具类来帮助我们处理文件的I/O操作,首先我们先判断SD卡的状态,看看SD卡是否可用,还有多少可用容量等。新建一个FileUtil的Class,加入方法

     1 // =================get SDCard information===================
     2     public static boolean isSdcardAvailable() {
     3         String status = Environment.getExternalStorageState();
     4         //Environment.MEDIA_MOUNTED表示SD卡正常挂载
     5         if (status.equals(Environment.MEDIA_MOUNTED)) {
     6             return true;
     7         }
     8         return false;
     9     }
    10
    11     public static long getSDAllSizeKB() {
    12         //sd卡的位置
    13         File path = Environment.getExternalStorageDirectory();
    14         //StatFs获取的都是以block为单位的
    15         StatFs sf = new StatFs(path.getPath());
    16         // 得到单个block的大小
    17         long blockSize = sf.getBlockSize();
    18         // 获取所有数据块数
    19         long allBlocks = sf.getBlockCount();
    20         // 返回SD卡大小
    21         return (allBlocks * blockSize) / 1024; // KB
    22     }
    23
    24     /**
    25      * free size for normal application
    26      * @return
    27      */
    28     public static long getSDAvalibleSizeKB() {
    29         File path = Environment.getExternalStorageDirectory();
    30         StatFs sf = new StatFs(path.getPath());
    31         long blockSize = sf.getBlockSize();
    32         long avaliableSize = sf.getAvailableBlocks();
    33         return (avaliableSize * blockSize) / 1024;// KB
    34     }

    Environment.getExternalStorageDirectory()表示获取扩展存储器的目录。(建议使用此方法动态获取,因为sdcard这个目录路径是可配置的)
    StatFs.getBlockSize在API18后变为StatFs.getBlockSizeLong,其他类似的getBlock方法也一样,关于StatFs,详情可以看这篇博文
    然后在activity中的button1加入事件

    case R.id.button1: {
                Log.d("TEST", "sdcard?"+FileUtil.isSdcardAvailable());
                Log.d("TEST", "全部容量"+(float)FileUtil.getSDAllSizeKB()/1024/1024);
                Log.d("TEST", "可用容量"+(float)FileUtil.getSDAvalibleSizeKB()/1024/1024);
                Toast.makeText(this, "status", Toast.LENGTH_SHORT).show();
                break;
            }

    运行结果如下

    接下来我们来判断某个文件夹是否存在在SD卡中以及创建一个文件夹

    /**
         * @param director 文件夹名称
         * @return
         */
        public static boolean isFileExist(String director) {
            File file = new File(Environment.getExternalStorageDirectory()
                    + File.separator + director);
            return file.exists();
        }
    
        /**
         * create multiple director
         * @param path
         * @return
         */
        public static boolean createFile(String director) {
            if (isFileExist(director)) {
                return true;
            } else {
                File file = new File(Environment.getExternalStorageDirectory()
                        + File.separator + director);
                if (!file.mkdirs()) {
                    return false;
                }
                return true;
            }
        }

    其中File.separator是表示分隔符,在不同操作系统下是不同的,如windows就是代表"/",而在Linux下却是代表"\"。所以介意使用File.separator来代替分隔符。File.mkdirs()表示创建一个文件夹,且可附带创建父目录,而mkdir()不行,详情的File大家可以查看官方文档,或者看看这篇博文
    然后在activity中的button2加入响应事件

    case R.id.button2: {
                Log.d("TEST", "example文件夹存在?"+FileUtil.isFileExist("example"));
                Log.d("TEST", "创建forexample文件夹"+FileUtil.createFile("forexample"));
                Toast.makeText(this, "IsFile", Toast.LENGTH_SHORT).show();
                break;
            }  

    运行后可以看到

    我们会发现在手机的sdcard目录下新建了一个forexample的文件夹。
    最后我们来实现文件的读和写
    写:

    /**
         *
         * @param director
         *            (you don‘t need to begin with
         *            Environment.getExternalStorageDirectory()+File.separator)
         * @param fileName
         * @param content
         * @param encoding
         *            (UTF-8...)
         * @param isAppend
         *            : Context.MODE_APPEND
         * @return
         */
        public static File writeToSDCardFile(String directory, String fileName,
                String content, String encoding, boolean isAppend) {
            // mobile SD card path +path
            File file = null;
            OutputStream os = null;
            try {
                if (!createFile(directory)) {
                    return file;
                }
                file = new File(Environment.getExternalStorageDirectory()
                        + File.separator + directory + File.separator + fileName);
                os = new FileOutputStream(file, isAppend);
                if (encoding.equals("")) {
                    os.write(content.getBytes());
                } else {
                    os.write(content.getBytes(encoding));
                }
                os.flush();
            } catch (IOException e) {
                Log.e("FileUtil", "writeToSDCardFile:" + e.getMessage());
            } finally {
                try {
                    if (os != null) {
                        os.close();
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            return file;
        }
    
        /**
         * write data from inputstream to SDCard
         */
        public File writeToSDCardFromInput(String directory, String fileName,
                InputStream input) {
            File file = null;
            OutputStream os = null;
            try {
                if (createFile(directory)) {
                    return file;
                }
                file = new File(Environment.getExternalStorageDirectory()
                        + File.separator + directory + File.separator + fileName);
                os = new FileOutputStream(file);
                byte[] data = new byte[bufferd];
                int length = -1;
                while ((length = input.read(data)) != -1) {
                    os.write(data, 0, length);
                }
                // clear cache
                os.flush();
            } catch (Exception e) {
                Log.e("FileUtil", "" + e.getMessage());
                e.printStackTrace();
            } finally {
                try {
                    os.close();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
            return file;
        }

    从上面可以看到有两种写入的方法,一种是将字符串直接写入,另一种是将数据流写到文件中。还有一点要提的是file的默认目录就是sdcard的目录,所以开头不必每次都要加sdcard的目录路径。
    FileOutputStream(file, isAppend) 两个参数,左边是File文件,而右边是一个boolean值,为true时,数据将会接在原来文件的后面写入,而false是则会覆盖。
    读:

    public static String ReadFromSDCardFile(String directory,String fileName){
            String res="";
            File file = null;
            file = new File(Environment.getExternalStorageDirectory()
                    + File.separator + directory + File.separator + fileName);
            try {
                FileInputStream fis = new FileInputStream(file);
                int length = fis.available();
                byte [] buffer = new byte[length];
                fis.read(buffer);            //将字节按照编码格式转成字符串
                res = EncodingUtils.getString(buffer, "UTF-8");
                fis.close();
                return res;
            }catch (FileNotFoundException  e) {
                // TODO Auto-generated catch block
                Log.d("TEST", "FileNotFound");
                e.printStackTrace();
            }catch (Exception  e) {
                Log.d("TEST", "Can Not Open File");
                e.printStackTrace();
            }
            return null;
        }

    编码默认是UTF-8,若是想要改变的话,将其作为参数传入就行。
    Activity中在按钮中加入响应

    case R.id.button3: {
                FileUtil.writeToSDCardFile("forexample", "test.txt",
                        editText.getText().toString(), "UTF-8", true);
                Toast.makeText(this, "WriteFile", Toast.LENGTH_SHORT).show();
                break;
            }
            case R.id.button4: {
                textView.setText(FileUtil.ReadFromSDCardFile("forexample", "test.txt"));
                Toast.makeText(this, "ReadFile", Toast.LENGTH_SHORT).show();
                break;
            }

    在文字编辑框上写入“我是cpacm”,先点击writefile按钮,再点击ReadFile,得到运行结果

    同时在根目录下的forexample文件夹里会找到test.txt,里面有着“我是cpacm”的一行字。到此,文件的读写成功。
    (2)放在该应用数据目录下的文件读写
         存储在应用目录下的私有数据目录,通常不会通过File类的方式直接读写,而是利用一些封装过的类或函数来操作。一般可以通过Context.openFileOutput来执行。
        在Activity加入两个方法,分别为文件的读和写

        public void writeFile(String fileName,String writestr){
            try{
                    FileOutputStream fout =openFileOutput(fileName,MODE_PRIVATE);
                    byte [] bytes = writestr.getBytes();
                    fout.write(bytes);
                    fout.close();
                  }
                    catch(Exception e){
                    e.printStackTrace();
                   }
            } 
    
            //读数据
        public String readFile(String fileName){
          String res="";
          try{
                 FileInputStream fin = openFileInput(fileName);
                 int length = fin.available();
                 byte [] buffer = new byte[length];
                 fin.read(buffer);
                 res = EncodingUtils.getString(buffer, "UTF-8");
                 fin.close();
             }
             catch(Exception e){
                 e.printStackTrace();
             }
             return res;
        }

    同时在按钮的响应中加入

    case R.id.button5: {
                writeFile("test2.txt",editText.getText().toString());
                Toast.makeText(this, "WritePrivateFile", Toast.LENGTH_SHORT).show();
                break;
            }
            case R.id.button6: {
                textView.setText(readFile("test2.txt"));
                Toast.makeText(this, "ReadPrivateFile", Toast.LENGTH_SHORT).show();
                break;
            }

    效果图跟上张一样。

    最后不要忘记在配置文件中声明权限

    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS" />
 
 

二、Android设置文件的使用

用户在使用应用时,常常会有一些个人偏好。为了满足不同用户的需求,应用通常会提供对应的设置项(Preference),让用户根据自己的喜好选择。这些设置信息会存储在本地并进行结构化地展示,使用户可以编辑。

  1. 设置文件的存储和使用

    Android应用的设置数据,可以通过android.content.SharedPreferences类来表示。它提供了一组数据读取的接口,可以从设置文件中读取给定键值的整形数,布尔型数等数据。
    首先是获取SharedPreferences

    private SharedPreferences userInfo;

            //在界面组件或服务组件中调用,构造应用默认的设置文件,默认文件名字为_preferences.xml
            //userInfo = PreferenceManager.getDefaultSharedPreferences(this);
            //或获取指定名字的SharedPreferences对象  参数分别为存储的文件名和存储模式。
            userInfo = getSharedPreferences("preferences", Activity.MODE_PRIVATE); 
    
            //读取数据,如果无法找到则会使用默认值
            String username = userInfo.getString("name", "未定义姓名");
            String msg = userInfo.getString("msg", "未定义信息");
            //显示文本
            textView.setText(username+","+msg);

    两种获取方式,默认或者指定一个文件
    接下来加入响应按钮

            case R.id.button7: {
                //获得SharedPreferences的编辑器
                SharedPreferences.Editor editor = userInfo.edit();
                //将信息存入相应的键值中
                editor.putString("name", editText.getText().toString()).commit();
                Toast.makeText(this, "SetName", Toast.LENGTH_SHORT).show();
                break;
            }
            case R.id.button8: {
                //获得SharedPreferences的编辑器
                SharedPreferences.Editor editor = userInfo.edit();
                //将信息存入相应的键值中ss
                editor.putString("msg", editText.getText().toString()).commit();
                Toast.makeText(this, "SetMessage", Toast.LENGTH_SHORT).show();
                break;
            }
            case R.id.button9: {            //获得SharedPreferences文件
                userInfo = getSharedPreferences("preferences", Activity.MODE_PRIVATE);
                String username = userInfo.getString("name", "未定义姓名");
                String msg = userInfo.getString("msg", "未定义信息");
                textView.setText(username+","+msg);
                Toast.makeText(this, "ShowMsg", Toast.LENGTH_SHORT).show();
                break;
            }
            case R.id.button10: {            //输出XML文件
                textView.setText(print());
                Toast.makeText(this, "ShowXML", Toast.LENGTH_SHORT).show();
                break;
            }

    按钮7,8可以设置信息,按钮9则从SharedPreferences文件中读取信息并显示在文字框中。按钮10会显示这个XML文件中的所有信息。

    访问其他应用中的Preference(在SecondApp中访问FirstApp的数据),前提条件是:FirstApp的preference创建时指定了Context.MODE_WORLD_READABLE或者Context.MODE_WORLD_WRITEABLE权限。

    如:在<package name>为com.first.app的应用使用下面语句创建了preference("first_app_perferences")。

    Java代码

    getSharedPreferences("first_app_perferences", Context.MODE_WORLD_READABLE);

    在SecondApp中要访问FirstApp应用中的preference,首先需要创建FirstApp应用的Context,然后通过Context 访问preference ,访问preference时会在应用所在包下的shared_prefs目录找到preference

    Context firstAppContext = createPackageContext("com.first.app", Context.CONTEXT_IGNORE_SECURITY);
    SharedPreferences sharedPreferences = firstAppContext.getSharedPreferences("first_app_perferences",  Context.MODE_WORLD_READABLE);
    String name = sharedPreferences.getString("name", "");
    int age = sharedPreferences.getInt("age", 0); 

    如果不通过创建Context访问FirstApp应用的preference,可以以读取xml文件方式直接访问FirstApp应用的preference对应的xml文件,

    如: 
    File xmlFile = new File(“/data/data/<package name>/shared_prefs/first_app_perferences.xml”);//<package name>应替换成应用的包名: com.first.app

  2. 设置界面组件
    有一类特殊的Preference对象:android.preference.PreferenceGroup。它是容器型的Preference对象,负责管理一组相关联的Preference对象。设置项编辑的界面组件,通常派生自android.preference.PreferenceActivity类。它可以将一个定制好的设置树转换成对应的控件呈现出来。

    public class PreferencesDemo extends PreferenceActivity{
         @Override
         public void onCreate(Bundle savadInstanceState){
             super.onCreate(savadInstanceState);
             this.addPreferencesFromResource(R.xml.preference);
         }
    }

时间: 2024-10-10 11:25:41

【Android的从零单排开发日记】——Android数据存储(上)的相关文章

【Android的从零单排开发日记】之入门篇(四)——Android四大组件之Activity

在Android中,无论是开发者还是用户,接触最多的就算是Activity.它是Android中最复杂.最核心的组件.Activity组件是负责与用户进行交互的组件,它的设计理念在很多方面都和Web页面类似.当然,这种相似性主要体现在设计思想上.在具体实现方面,Android的Activity组件有自己的设计规范,同时,它能够更简便地使用线程.文件数据等本地资源. 一.Activity 的生命周期 Activity 的生命周期是被以下的函数控制的. 1 public class Activity

【Android的从零单排开发日记】之入门篇(六)——Android四大组件之Broadcast Receiver

广播接受者是作为系统的监听者存在着的,它可以监听系统或系统中其他应用发生的事件来做出响应.如设备开机时,应用要检查数据的变化状况,此时就可以通过广播来把消息通知给用户.又如网络状态改变时,电量变化时都可以通过广播来通知用户.要做比喻的话,广播就像是我们的感官,能够有效且快速的从外界获取信息来反馈给自身. 一.广播的功能和特征 广播的生命周期很短,经过 调用对象—实现onReceive—结束 整个过程就结束了.从实现的复杂度和代码量来看,广播无疑是最迷你的Android 组件,实现往往只需几行代码

【Android的从零单排开发日记】之入门篇(三)——Android目录结构

本来的话,这一章想要介绍的是Android的系统架构,毕竟有了这些知识的储备,再去看实际的项目时才会更清楚地理解为什么要这样设计,同时在开发中遇到难题,也可以凭借着对Android的了解,尽快找出哪些模块和设计能够帮助解决该问题.但想了一下,这毕竟是入门篇,若没有实际项目开发经验的人看了之后肯定是一头雾水,所以就决定将其搁浅到大家熟悉Android之后再为大家介绍. 那么今天的主题是Android的目录结构,将系统架构比作人的骨骼架构的话,目录结构就像是人的各个器官,彼此功能各不相同,却能有序地

【Android的从零单排开发日记】之入门篇(五)——Android四大组件之Service

这几天忙着驾校考试,连电脑都碰不到了,今天总算告一段落了~~Service作为Android的服务组件,默默地在后台为整个程序服务,辅助应用与系统中的其他组件或系统服务进行沟通.它跟Activity的级别差不多,但不能自己运行只能后台运行.service可以在很多场合的应用中使用,比如播放多媒体的时候用户启动了其他Activity这个时候程序要在后台继续播放,比如检测SD卡上文件的变化,再或者在后台记录你地理信息位置的改变等等, 总之服务总是藏在后台的. ps:Service运行在主线程中的,所

【Android的从零单排开发日记】之入门篇(八)——Android数据存储(下)

废话不多说了,紧接着来讲数据库的操作吧.Come On! 提到数据存储问题,数据库是不得不提的.数据库是用来存储关系型数据的不二利器.Android为开发者提供了强大的数据库支持,可以用来轻松地构造基于数据库的应用.Android的数据库应用,依托于当下最流行的开源嵌入式数据库SQLite.在Android中,应用的数据库文件是该应用私有的,存储在应用数据目录下的databases子目录内.从代码结构来看,Android的数据库实现可以分成两个层次,在底层通过C++调用SQLite的接口来执行S

【Android的从零单排开发日记】之入门篇(七)——Android数据存储(上)

在讲解Android的数据源组件——ContentProvider之前我觉得很有必要先弄清楚Android的数据结构. 数据和程序是应用构成的两个核心要素,数据存储永远是应用开发中最重要的主题之一,也是开发平台必须提供的基础功能.不光是在Android平台上,在其他的平台上,数据的存储永远是不可缺少的一块.Android的数据存储是构建在Linux的文件系统上,它充分利用Linux的账号系统来限定应用对数据的访问,部署了一套安全和灵活并重的数据存储解决方案.Android的文件框架,以及各种数据

【Android的从零单排开发日记】之入门篇(一)——开发环境的搭建

写给自己的话:至此,大学的时光已经剩下一年的时光,下一年等毕业设计结束后就算是正式地踏入社会.自己学android也不过几个月的时间,为了更好管理文档,写点东西记录下自己曾经做过的点点滴滴是一个不错的选择,接下来都会将我自己所学所感一一记录下来,算是给后来的我一份复习的资料和还算不错的回忆. 开始正题吧,android环境的安装,网上很多教程,本来不想写这章的,后来帮同学配置时想想有个总结也是不错的,所以就写了这篇满是链接的文章. 一.需要准备的文件 JDK 官方网站 http://www.or

【Android的从零单排开发日记】——Android数据存储(下)

废话不多说了,紧接着来讲数据库的操作吧.Come On! 提到数据存储问题,数据库是不得不提的.数据库是用来存储关系型数据的不二利器.Android为开发者提供了强大的数据库支持,可以用来轻松地构造基于数据库的应用.Android的数据库应用,依托于当下最流行的开源嵌入式数据库SQLite.在Android中,应用的数据库文件是该应用私有的,存储在应用数据目录下的databases子目录内.从代码结构来看,Android的数据库实现可以分成两个层次,在底层通过C++调用SQLite的接口来执行S

【Android的从零单排开发日记】——Android四大组件之ContentProvider

数据源组件ContentProvider与其他组件不同,数据源组件并不包括特定的功能逻辑.它只是负责为应用提供数据访问的接口.Android内置的许多数据都是使用ContentProvider形式,供开发者调用的(如视频,音频,图片,通讯录等).如果把第三方应用比作一个黑盒子的话,ContentProvider就像是从里面延伸出来的管道,从这个管道,应用可以把一些数据共享出来,我们也可以往里面输送数据.但是里面怎么处理数据我们看不到,也管不着.并且这个管道是有规范标准的,不是它规定的数据你塞不进