你的位置:首页 > 操作系统

[操作系统]android菜鸟学习笔记17


假如有如下需求,要求能够记录用户输入的用户名和密码,下次登录时,能直接获取之前保存的用户名密码,并在相应的EditText中显示。

要保存用户输入的数据,最先想到的应该就是文件读写了。

通过对android应用打包安装过程的观察,发现每个应用安装之后,都会在/data/data/下新建一个与应用包名相同的目录,该应用的所有文件都存放在该目录下。

例如:

main_layout.

 1 <??> 2  3 <LinearLayout ="http://schemas.android.com/apk/res/android" 4  5   android:layout_width="match_parent" 6  7   android:layout_height="match_parent" 8  9   android:layout_margin="10dp"10 11   android:orientation="vertical" >12 13   <TextView14 15     android:id="@+id/tv_info"16 17     android:layout_width="match_parent"18 19     android:layout_height="wrap_content"20 21     android:textSize="25sp"22 23     android:textColor="#00ff00"24 25     android:text="@string/tv_info_text"/>26 27   <EditText28 29     android:id="@+id/et_username"30 31     android:layout_width="match_parent"32 33     android:layout_height="wrap_content"34 35     android:hint="@string/et_name_text"/>36 37   <EditText38 39     android:id="@+id/et_password"40 41     android:layout_width="match_parent"42 43     android:layout_height="wrap_content"44 45     android:hint="@string/et_password_text"46 47     android:inputType="textPassword"/>48 49   <CheckBox50 51     android:id="@+id/cb_reme"52 53     android:layout_width="wrap_content"54 55     android:layout_height="wrap_content"56 57     android:text="@string/cb_reme_text"58 59     />60 61   <Button62 63     android:id="@+id/btn_login"64 65     android:layout_width="wrap_content"66 67     android:layout_height="wrap_content"68 69     android:text="@string/btn_login_text"/>70 71 </LinearLayout>

 FileUtils.java代码:

 1 package cn.csc.utils; 2  3 import java.io.FileOutputStream; 4  5 public class FileUtils { 6  7    public static boolean writeFile(String data){ 8  9      String path = "/data/data/cn.csc.file/userinfo.txt";10 11      try {12 13         FileOutputStream fos = new FileOutputStream(path);14 15          fos.write(data.getBytes());16 17          fos.close();18 19         return true;20 21      } catch (Exception e) {22 23         // TODO Auto-generated catch block24 25          e.printStackTrace();26 27         return false;28 29       }30 31    }32 33 }

 MainActivity.java:

 1 public class MainActivity extends Activity { 2  3    private EditText et_username; 4  5    private EditText et_password; 6  7    private CheckBox cb_reme; 8  9    private Button btn_login;10 11    @Override12 13    protected void onCreate(Bundle savedInstanceState) {14 15     super.onCreate(savedInstanceState);16 17     setContentView(R.layout.main_layout);18 19     et_username = (EditText) findViewById(R.id.et_username);20 21     et_password = (EditText) findViewById(R.id.et_password);22 23     cb_reme = (CheckBox) findViewById(R.id.cb_reme);24 25     btn_login = (Button) findViewById(R.id.btn_login);26 27     btn_login.setOnClickListener(new OnClickListener() {28 29          @Override30 31         public void onClick(View v) {32 33            // TODO Auto-generated method stub34 35            String username = et_username.getText().toString();36 37            String password = et_password.getText().toString();38 39            boolean checked = cb_reme.isChecked();40 41            if(TextUtils.isEmpty(username)||TextUtils.isEmpty(password)){42 43               Toast.makeText(MainActivity.this, "用户名密码不能为空",Toast.LENGTH_SHORT).show();44 45               return;46 47            }48 49            if(checked){50 51               if(username.equals("dqrcsc")&&password.equals("abcdef")){52 53                  if(FileUtils.writeFile(username+"#"+password)){54 55                    Toast.makeText(MainActivity.this, "保存成功",Toast.LENGTH_SHORT).show();56 57                  }58 59                  else{60 61                    Toast.makeText(MainActivity.this, "保存失败",Toast.LENGTH_SHORT).show();62 63                  }64 65                  Toast.makeText(MainActivity.this, "登录成功",Toast.LENGTH_SHORT).show();66 67               }else{68 69                  Toast.makeText(MainActivity.this, "登录失败",Toast.LENGTH_SHORT).show();70 71               }72 73              74 75            }76 77          }78 79       });80 81   }82 83 }

 运行结果:

 

 注意到该文件的权限,默认仅能被当前用户读写。

保存用户名,密码成功,还需要实现下次打开时,自动读取保存的用户名密码,对应的方法可以在onCreate()中调用,已实现打开时自动加载。

在FileUtils中添加读取文件的方法:

 1 public static String readFile(){ 2  3      String path = "/data/data/cn.csc.file/userinfo.txt"; 4  5      try { 6  7         BufferedReader br = new BufferedReader(new FileReader(path)); 8  9         String line = br.readLine();10 11          br.close();12 13         return line;14 15      } catch (Exception e) {16 17         // TODO Auto-generated catch block18 19          e.printStackTrace();20 21       }22 23      return null;24 25 }

 修改MainActivity.java中onCreate(),添加如下代码:

 1      String info = FileUtils.readFile(); 2  3     if(TextUtils.isEmpty(info)){ 4  5        return; 6  7     } 8  9     if(!info.contains("#")){10 11        return;12 13     }else{14 15        String[] strs = info.split("#");16 17       et_password.setText(strs[1]);18 19       et_username.setText(strs[0]);20 21       cb_reme.setChecked(true);22 23     }

 

上述直接操作写死的文件路径,是存在问题的,如果我修改了当前应用的包名,然后去操作写死的文件,就会出现权限被拒绝的错误:

如Manifest.

运行程序,出现如下错误:

java.io.FileNotFoundException: /data/data/cn.csc.file/userinfo.txt (Permission denied)

 

文件仍然存在,但是相对与当前应用,已属于别的用户所有,而该文件只能被所属用户进行读写操作。

当然,如果把userinfo.txt的权限修改下,还是可以读写的。

adb shell挂载linux文件系统,然后进入/data/data/file目录

然后chmod 666 userinfo.txt

 

然后,就可以直接让cn.csc.file1应用直接读写了。

其实,Context有可以获取当前包文件存储路径的API:

getFilesDir();//返回/data/data/包名/files目录

getCacheDir();//返回/data/data/包名/cache目录

由于是在FileUtils类中读写文件,要使用Context的API,则需要把Context作为参数传递:

在FileUtils中添加对原有读写文件方法的重载:

 1 public static boolean writeFile(Context context, String data){ 2  3      File filesDir = context.getFilesDir(); 4  5      try { 6  7         FileOutputStream fos = new FileOutputStream(new File(filesDir, "userInfo.txt")); 8  9          fos.write(data.getBytes());10 11          fos.close();12 13         return true;14 15      } catch (Exception e) {16 17         // TODO Auto-generated catch block18 19          e.printStackTrace();20 21         return false;22 23       }24 25    }26 27    public static String readFile(Context context){28 29      File filesDir = context.getFilesDir();30 31      try {32 33         BufferedReader br = new BufferedReader(new FileReader(new File(filesDir, "userInfo.txt")));34 35         String line = br.readLine();36 37          br.close();38 39         return line;40 41      } catch (Exception e) {42 43         // TODO Auto-generated catch block44 45          e.printStackTrace();46 47       }48 49      return null;50 51 }

修改MainActivity.java,调用新添加的读写文件方法,把当前Activity实例作为Context参数传递:

FileUtils.writeFile(MainActivity.this,username+"#"+password)

String info = FileUtils.readFile(this);

重新运行程序:

 

cn.csc.file1目录下多出了一个空的目录files

然后,填写信息,点登录,保存数据

 

files目录下,便多出了userInfo.txt的文件

文件的默认权限总是-rw- --- ---,即只有该文件所有者可以读写,其他用户均不具有对该文件的读写权限,如何设置文件权限呢?

Context提供了两个方法:

abstract FileInputStream  openFileInput(String name)

返回指向/data/data/包名/files/name文件的FileInputStream实例

abstract FileOutputStream  openFileOutput(String name, int mode)

返回指向/data/data/包名/files/name文件的FileOutputStream实例,文件不存在时,创建该文件,并根据mode参数,设置文件的权限。

mode的取值可以为:

Context.MODE_PRIVATE:(实际值:0x00000000)只有所有者具有读写权限,文件已存在直接覆盖

Context.MODE_APPEND:(实际值:0x00008000)文件已存在,则追加

Context.MODE_WORLD_READABLE:(实际值:0x00000001)其他所有用户具有读权限

Context.MODE_WORLD_WRITEABLE:(实际值:0x00000002)其他所有用户具有写权限

可以将这些取值位或操作,然后传递给openFileOutput()方法,设置同时具有多个属性。

修改FileUtils中的两个文件读写方法:

 1 public static boolean writeFile(Context context, String data){ 2  3      try { 4  5         FileOutputStream fos = context.openFileOutput("userInfo.txt", Context.MODE_PRIVATE); 6  7          fos.write(data.getBytes()); 8  9          fos.close();10 11         return true;12 13      } catch (Exception e) {14 15         // TODO Auto-generated catch block16 17          e.printStackTrace();18 19         return false;20 21       }22 23    }24 25    public static String readFile(Context context){26 27      try {28 29         BufferedReader br = new BufferedReader(new InputStreamReader(context.openFileInput("userInfo.txt")));30 31         String line = br.readLine();32 33          br.close();34 35         return line;36 37      } catch (Exception e) {38 39         // TODO Auto-generated catch block40 41          e.printStackTrace();42 43       }44 45      return null;46 47    }

 修改mode字段,查看对应文件权限:

MODE_PRIVATE:

 

MODE_APPEND:

 

此时,点击登陆按钮,文件大小变成了26,存放了两条用户名及密码信息,可见,是在已存在文件中,追加内容。并且,文件权限变味了同组用户也可读写。

MODE_WORLD_READABLE:

 

所有用户均可读

MODE_WORLD_WRITEABLE:

 

所有用户均可写

MODE_WORLD_READABLE| MODE_WORLD_WRITEABLE:

 

所有用户均可读可写

MODE_APPEND| MODE_WORLD_READABLE| MODE_WORLD_WRITEABLE:

 

所有用户均可读可写,并且是追加模式,当文件存在时,向已存在文件中追加内容,而非覆盖操作。

注意:由于MODE_WORLD_READABLE和MODE_WORLD_WRITEABLE这两种模式过于危险,易产生安全问题,在Android4.2中已然废弃。

读写缓存中的文件

利用Context的getCacheDir();//返回/data/data/包名/cache目录,获取缓存的存放路径。

在FileUtils中添加两个方法:

 1 public static boolean writeCache(Context context, String data){ 2  3      File filesDir = context.getCacheDir(); 4  5      try { 6  7         FileOutputStream fos = new FileOutputStream(new File(filesDir, "userInfo.txt")); 8  9          fos.write(data.getBytes());10 11          fos.close();12 13         return true;14 15      } catch (Exception e) {16 17         // TODO Auto-generated catch block18 19          e.printStackTrace();20 21         return false;22 23       }24 25    }26 27    public static String readCache(Context context){28 29      File filesDir = context.getCacheDir();30 31      try {32 33         BufferedReader br = new BufferedReader(new FileReader(new File(filesDir, "userInfo.txt")));34 35         String line = br.readLine();36 37          br.close();38 39         return line;40 41      } catch (Exception e) {42 43         // TODO Auto-generated catch block44 45          e.printStackTrace();46 47       }48 49      return null;50 51    }

 在MainActivity调用这两个读写Cache的方法:

运行结果:

 

注意:文件和缓存的区别:

 

Clear cache会直接清空cache目录,并且不会弹出警告信息

Clear data则会先弹出警告窗口,确认之后,才会清空files目录。

 

一般优化工具,都会清空Cache目录,而不会直接清空files目录,所以常用的文件要保存在files目录中,而临时文件则可以考虑放在cache目录中。

读写SD卡中的文件

Environment类提供了几个很实用的方法:

static File  getDataDirectory() /data/data/包名/files

static File  getDownloadCacheDirectory() 下载缓存路径

static File  getExternalStorageDirectory() SD卡路径

static String  getExternalStorageState() SD卡状态

static File  getRootDirectory() 根目录

测试上述方法输出:

1               Log.i("Environment","getDataDirectory:"+Environment.getDataDirectory().getPath());2 3            Log.i("Environment","getDownloadCacheDirectory:"+Environment.getDownloadCacheDirectory().getPath());4 5            Log.i("Environment","getExternalStorageDirectory:"+Environment.getExternalStorageDirectory().getPath());6 7            Log.i("Environment","getRootDirectory:"+Environment.getRootDirectory().getPath());8 9            Log.i("Environment","getExternalStorageState:"+Environment.getExternalStorageState());

 

修改FileUtils添加两个读写SD卡的方法:

 1 public static boolean writeSD(String data){ 2  3      try { 4  5         String state = Environment.getExternalStorageState(); 6  7         if(state.equals(Environment.MEDIA_MOUNTED)){ 8  9            File ext = Environment.getExternalStorageDirectory();10 11            FileOutputStream fos = new FileOutputStream(new File(ext,"userInfo.txt"));12 13            fos.write(data.getBytes());14 15            fos.close();16 17            return true;18 19          }20 21         return false;22 23         24 25      } catch (Exception e) {26 27         // TODO Auto-generated catch block28 29          e.printStackTrace();30 31         return false;32 33       }34 35 }36 37    public static String readSD(){38 39      try {40 41         String state = Environment.getExternalStorageState();42 43         if(state.equals(Environment.MEDIA_MOUNTED)){44 45            File ext = Environment.getExternalStorageDirectory();46 47            BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream(ext.getPath()+"/userInfo.txt")));48 49            String line = br.readLine();50 51            br.close();52 53            return line;54 55          }56 57      } catch (Exception e) {58 59         // TODO Auto-generated catch block60 61          e.printStackTrace();62 63       }64 65      return null;66 67 }

 此外,写SD需要配置权限: <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/> 

运行结果:

 

注意:在读写SD卡之前,需要先检查SD卡的状态,已挂载才能进行读写操作。此外,读写SD卡需要在Manifest.

1 <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>2 3 <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>