2.4 文件I/O

I/O即输入与输出,几乎所有文件操作的工作都离不开I/O。对于Android游戏开发来说,I/O操作尤为重要,游戏中的地图数据、人物图片资源等的流畅读取是保证游戏可玩性的重要方面。因此,在开发时,不同的场合必须选用合适的I/O操作方式,才能保证满足用户的需求。

Android中文件I/O分为3种方式:SD卡文件读取、手机中文件夹的访问和应用程序中assets文件的读取,本节将结合简单的小案例对这几种文件I/O操作进行介绍。

2.4.1 访问SD卡中的文件

伴随着游戏品质的不断提高,游戏数据占用的存储空间也成几何级数增加,以往几十KB、几MB的手机游戏已经发展为几十MB、几百MB甚至几个GB的大型手机游戏。为了适应存储需求的增长,SD卡也一直在进行更新换代,4GB、8GB、16GB大小的SD卡已随处可见。

Android的设计者自然不会忽略这一点,在Android平台上可以轻松地对手机SD卡中的文件进行读取和操作。下面通过一个案例来详细讲解在Android开发中如何访问SD卡中的文件。

1.案例的运行效果

运行本节案例Sample2_5,在输入框中输入要读取的文件名,然后单击“打开文件”按钮。若SD卡中存在该文本文件,则在文本区域显示出文件的内容,反之则提示没有找到指定文件。本案例的运行效果如图2-15和图2-16所示。

▲图2-15 成功加载SD卡中的文件

▲图2-16 未找到指定的文件

提示

运行本案例时要注意SD卡中文本文件的编码格式,在Android系统中一般要采用UTF-8编码才能保证没有乱码出现。编码格式为UTF-8的文本文件,见随书中源代码/第2章目录下的AndroidIO.txt,读者可以将该文件导入到手机或模拟器的SD卡中再对本案例进行测试。

2.案例的开发

介绍完本案例的运行效果以及文本文件编码的注意事项后,接下来将对本案例的主控制类Sample2_5_Activity进行开发,其代码如下。

代码位置:见随书中源代码/第2章/Sample2_5/src/com/bn/pp5目录下的Sample2_5_Activity.java。

      1       package  com/bn/pp5;
      2     import java.io.File;                                           //引入相关包
      3     public class Sample2_5_Activity extends Activity {        //创建Activity
      4            @Override
      5         public void onCreate(Bundle savedInstanceState) {      //重写onCreate方法
      6                 super.onCreate(savedInstanceState);
      7             setContentView(R.layout.main);                        //跳转到主界面
      8             Button ok=(Button)this.findViewById(R.id.Button01); //获取打开按钮的引用
      9             ok.setOnClickListener(                                 //为打开按钮添加监听器
      10                              new  OnClickListener(){
      11                              public  void  onClick(View  v)  {
      12                                      EditText  et1=(EditText)findViewById(R.id.EditText01);
      13                                      String  nr=loadText(et1.getText().toString().trim());
      14                                      EditText  et2=(EditText)findViewById(R.id.EditText02);
      15                            et2.setText(nr);                        //设置显示框内容
      16                     }}); }
      17        public String loadText(String name){                     //加载SD卡文件方法
      18          String nr=null;                                           //内容字符串
      19             try{
      20                File f=new File("/sdcard/"+name);                //创建对应文件
      21                byte[] buff=new byte[(int) f.length()];         //创建响应大小的byte数组
      22                     FileInputStream  fis=new  FileInputStream(f);
      23                fis.read(buff);                                     //读入文件
      24                fis.close();                                        //关闭输入流
      25                nr=new String(buff, "utf-8");                      //转码生成字符串
      26                nr=nr.replaceAll("\\r\\n", "\n");                 //替换换行符
      27             }catch  (Exception  e)  {
      28                Toast.makeText(getBaseContext(),                 //Toast提示用户
      29                                  "对不起,没有找到指定文件。",
      30                                              Toast.LENGTH_LONG).show();
      31             }
      32          return nr;                                                 //返回内容字符串
      33     }}

❑ 第8-16行是为打开文件的按钮添加监听器,监听器的功能为首先获取文本框中用户输入的文件名,然后调用从SD卡加载文本文件的方法,获取SD卡中的对应文件内容并送入EditText控件显示。

❑ 第17-32行为加载SD卡中文本文件的方法,其入口参数为要加载文件的文件名。工作过程为首先用Java标准的I/O操作来加载指定文本文件中的数据,然后将数据编码为字符串,并替换换行符等空白字符,最后返回加载的内容。

提示

通过上面的小案例可以看出,Android中对SD卡文件的I/O实现与标准Java相同,因此,开发成本很低,易于上手。

2.4.2 访问手机中的文件夹

前一小节中是将文件存放到SD卡中,其实文件也可以存放在手机内部存储(ROM,相当于PC的硬盘)中。不过在Android系统中,其为每个应用程序在手机的ROM中都分配了一个私有的目录,命名规则为“/data/data/<应用程序的包名>”。例如,应用程序的包名为“com.bn”,则系统分配的私有目录为“/data/data/com.bn”。

应用程序开发时,若需要在ROM中存放文件,一般应该存放到系统分配的私有目录中。启动Eclipse,进入其中的DDMS面板,打开File Explorer,即可查看手机ROM中的文件组织情况,如图2-17所示。

▲图2-17 查看手机内部存储文件

下面给出一个浏览手机ROM中文件夹的小案例,通过单击文件夹的名称,可以打开相应的文件夹,显示其中的文件列表。运行本案例,其运行效果如图2-18、图2-19和图2-20所示。

▲图2-18 程序初始运行

▲图2-19 进入dev目录

▲图2-20 单击出错

提示

图2-18为程序一开始运行,显示“/”目录下内容的情况。图2-19为单击进入了dev目录,图2-20为单击了不允许进入或不是文件夹的条目。

了解了本案例的运行效果之后,接下来对本案例中唯一的类—Sample2_6_Activity进行开发,具体代码如下。

代码位置:见随书中源代码/第2章/Sample2_6/src/com/bn/pp6目录下的Sample2_6_Activity.java。

      1     package com.bn.pp6;                                       //声明包
      2     import java.io.File;                                      //引入相关类
      3     ……//此处省略了部分类的引入代码,读者可自行查看随书的源代码
      4     import android.widget.AdapterView.OnItemClickListener;    //引入相关类
      5     public class Sample2_6_Activity extends Activity {        //创建Activity
      6           String currPath;                           //当前路径字符串
      7           String rootPath = "/";                    //根目录路径
      8           TextView currDirTV;                       //显示当前路径的TextView引用
      9               @Override
      10             public  void  onCreate(Bundle  savedInstanceState)  {
      11                     super.onCreate(savedInstanceState);
      12                setContentView(R.layout.main);    //跳转到主界面
      13                final ListView lv=(ListView)this.findViewById(R.id.lv); //获取ListView
      14                Button back = (Button) this.findViewById(R.id.back);   //获取返回按钮
      15                     final  File[]  files  =  getFiles(rootPath);
                                                          //调用getFiles方法获取根目录下文件列表
      16                currDirTV = (TextView) this.findViewById(R.id.currDirTV); //获取ListView
      17                     currPath  =  rootPath;
      18                currDirTV.setText("当前路径:" + currPath);      //设置当前路径
      19                initListView(files, lv);                           //初始化显示列表
      20                back.setOnClickListener                            //返回按钮监听器
      21                     (new  OnClickListener()  {
      22                              @Override
      23                              public  void  onClick(View  v)  {
      24                                      if  (! currPath.equals(rootPath))  {
                                                          //若当前路径不是根目录,返回到上一层目录
      25                                  File f = new File(currPath); //获取当前路径下的文件列表
      26                                  f = f.getParentFile();      //获取当前路径的上层路径
      27                                  currPath = f.getPath();    //更改当前路径
      28                                  currDirTV.setText("当前路径:" + currPath); //设置当前路径
      29                                  initListView(getFiles(currPath), lv); //初始化显示列表
      30             }}}); }
      31          //获取当前目录下的文件列表的方法
      32             public  File[]  getFiles(String  filePath)  {
      33                File[] files = new File(filePath).listFiles(); //获取当前目录下的文件列表
      34                return files;                             //返回文件列表
      35             }
      36          ……//此处省略了初始化ListView的方法,读者可以自行查阅随书中的源代码
      37     }

❑ 第9-30行为案例整体功能的实现代码。程序运行时,首先调用initListView方法初始化文件显示列表,显示根目录下的文件情况。然后给“返回上一层”按钮添加了监听器,监听器功能为若存在上一层目录,则刷新文件显示列表为上一层目录下的情况。

❑ 第32-35行为获取当前路径下所有文件列表的方法。该方法中首先需要获得当前路径对应的File对象,之后返回其下对应的文件列表。

提示

由于篇幅所限,初始化文件列表的代码省略,有需要的读者请自行查阅中的源代码进行学习。

2.4.3 读取assets文件夹下的内容

Android还能将应用程序所需的数据文件打包到apk文件中,省去了使用者安装应用程序后还需要下载数据包的情况。但是要注意的是,打包到apk文件中的数据文件并不是可以放在任何位置,一般应该放到项目中的assets文件夹下。

提示

对于特别大(如50MB)的数据文件,在实际开发中一般还是与apk安装文件分开的。对于不大的数据文件,就非常适合打包到apk包中的assets文件夹下。

下面将通过一个小案例来说明如何编程访问assets文件夹下的内容,其主要功能是完成对应用程序内部的assets文件夹下文件的读取。操作过程为首先在案例初始界面输入文件名,然后单击打开按钮。若存在该文件,则在下方显示出文件内容;反之,则弹出Toast提示用户重新输入。案例运行效果如图2-21和图2-22所示。

▲图2-21 程序运行效果图

▲图2-22 无法找到指定文件

了解了本案例的功能后,下面来介绍本案例的开发过程,具体步骤如下。

(1)首先将AndroidIO.txt文本文件复制到项目中的assets文件夹中,如图2-23所示。

▲图2-23 添加文本文件

(2)将AndroidIO.txt文本文件复制到项目中的assets文件夹后,接下来将开发本案例中唯一的类Sample2_7_Activity,其代码如下。

代码位置:见随书中源代码/第2章/Sample2_7/src/com/bn/pp7目录下的Sample2_7_Activity.java。

      1       package  com.bn.pp7;
      2     import java.io.ByteArrayOutputStream;                        //引入相关包
      3     public class Sample2_7_Activity extends Activity {   //创建Activity
      4            @Override
      5         public void onCreate(Bundle savedInstanceState) { //重写onCreate方法
      6                 super.onCreate(savedInstanceState);
      7             setContentView(R.layout.main);                   //跳转至主界面
      8             Button ok=(Button)this.findViewById(R.id.Button01);     //获取打开按钮引用
      9             ok.setOnClickListener(                            //为打开按钮添加监听器
      10                  new  OnClickListener()  {
      11                     public  void  onClick(View  v)  {
      12                              EditText  et1=(EditText)findViewById(R.id.EditText01);
      13                      //调用loadText方法获取对应文件名的文件
      14                              String  nr=loadText(et1.getText().toString().trim());
      15                              EditText  et2=(EditText)findViewById(R.id.EditText02);
      16                      et2.setText(nr);                         //设置显示框内容
      17             }}); }
      18        public String loadText(String name){                //加载assets文件方法
      19          String nr=null;                                      //内容字符串
      20          try {                                                  //打开对应名称文件的输入流
      21                     InputStream  is=this.getResources().getAssets().open(name);
      22                     int  ch=0;
      23                //创建字节数组输出流
      24                     ByteArrayOutputStream  baos=new  ByteArrayOutputStream();
      25                while((ch=is.read())! =-1){baos.write(ch); }//读取文件
      26                byte[] buff=baos.toByteArray();             //转化为字节数组
      27                baos.close();                                  //关闭输入输出流
      28                is.close();                                     //关闭输入输出流
      29                nr=new String(buff, "utf-8");                 //转码生成新字符串
      30                nr=nr.replaceAll("\\r\\n", "\n");            //替换换行符等空白字符
      31                } catch (Exception e) {                       //没有找到对应文件,进行提示
      32                      Toast.makeText(getBaseContext(), "对不起,没有找到指定文件。",
      33                              Toast.LENGTH_LONG).show();
      34                     }
      35                return nr;                                      //返回内容字符串
      36     }}

❑ 第8-17行首先获取了打开按钮的引用,然后给此按钮添加了监听器。监听器的功能为读取用户输入名称的文件内容,并显示到界面上。

❑ 第18-36行为从assets中加载指定名称文本文件的方法,其首先获取了指向指定文件的输入流,然后从输入流中读取数据,接着将数据编码为字符串,并替换换行符等空白字符,最后返回加载的内容。

提示

通过上述案例的介绍,相信读者对Android平台下文件的存储和读取已经有所掌握。在真正的开发过程中,开发人员应该考虑各方面的因素,在需要时采用最适合自己的存储方式进行存储。