4.6.1.4 使用外部存储器(公共读写)文件

将文件存储在 SD 卡等外部存储器中时,就是这种情况。当存储比较庞大的信息(放置从 Web 下载的文件)或者将信息带出到外部时(备份等)时,应该使用它。

对于未指定的大量应用,“外部存储器文件(公共读写)”与“公共读写文件“有相同特性。另外,对于声明使用android.permission.WRITE_EXTERNAL_STORAGE权限的应用,它和“公共读写文件”具有相同的特性。因此,应尽可能减少“外部存储器(公共读写)文件”的使用。

按照 Android 应用的惯例,备份文件很可能是在外部存储器中创建的。但是,如上所述,外部存储器中的文件存在被其他应用(包括恶意软件)篡改/删除的风险。因此,在输出备份的应用中,为了最小化应用规范或设计方面的风险,一些设计是必要的,例如显示“尽快将备份文件复制到 PC 等安全位置”。

要点:

  1. 不得存储敏感信息。

  2. 文件必须存储在每个应用的唯一目录中。

  3. 对于要存储在文件中的信息,请仔细和安全地处理文件数据。

  4. 请求应用的文件写入应该按照规范禁止。

AndroidManifest.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="org.jssec.android.file.externalfile" >
    <!-- declare android.permission.WRITE_EXTERNAL_STORAGE permission to write to the external strage --
    >
    <!-- In Android 4.4 (API Level 19) and later, the application, which read/write only files in its sp
    ecific
    directories on external storage media, need not to require the permission and it should declare
    the maxSdkVersion -->
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"
    android:maxSdkVersion="18"/>
    <application
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name"
        android:allowBackup="false" >
        <activity
            android:name=".ExternalFileActivity"
            android:label="@string/app_name"
            android:exported="true" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>
</manifest>

ExternalFileActivity.java

package org.jssec.android.file.externalfile;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.widget.TextView;

public class ExternalFileActivity extends Activity {

    private TextView mFileView;
    private static final String TARGET_TYPE = "external";
    private static final String FILE_NAME = "external_file.dat";
    
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.file);
        mFileView = (TextView) findViewById(R.id.file_view);
    
    }
    /**
    * Create file process
    *
    * @param view
    */
    public void onCreateFileClick(View view) {
        FileOutputStream fos = null;
        try {
            // *** POINT 1 *** Sensitive information must not be stored.
            // *** POINT 2 *** Files must be stored in the unique directory per application.
            File file = new File(getExternalFilesDir(TARGET_TYPE), FILE_NAME);
            fos = new FileOutputStream(file, false);
            // *** POINT 3 *** Regarding the information to be stored in files, handle file data carefully and securely.
            // Omitted, since this is a sample. Please refer to "3.2 Handling Input Data Carefully and Securely."
            fos.write(new String("Non-Sensitive Information(ExternalFileActivity)¥n")
            .getBytes());
        } catch (FileNotFoundException e) {
            mFileView.setText(R.string.file_view);
        } catch (IOException e) {
            android.util.Log.e("ExternalFileActivity", "failed to read file");
        } finally {
            if (fos != null) {
                try {
                    fos.close();
                } catch (IOException e) {
                    android.util.Log.e("ExternalFileActivity", "failed to close file");
                }
            }
        }
        finish();
    }
    
    /**
    * Read file process
    *
    * @param view
    */
    public void onReadFileClick(View view) {
        FileInputStream fis = null;
        try {
            File file = new File(getExternalFilesDir(TARGET_TYPE), FILE_NAME);
            fis = new FileInputStream(file);
            byte[] data = new byte[(int) fis.getChannel().size()];
            fis.read(data);
            // *** POINT 3 *** Regarding the information to be stored in files, handle file data carefully and securely.
            // Omitted, since this is a sample. Please refer to "3.2 Handling Input Data Carefully and Securely."
            String str = new String(data);
            mFileView.setText(str);
        } catch (FileNotFoundException e) {
            mFileView.setText(R.string.file_view);
        } catch (IOException e) {
            android.util.Log.e("ExternalFileActivity", "failed to read file");
        } finally {
            if (fis != null) {
                try {
                    fis.close();
                } catch (IOException e) {
                    android.util.Log.e("ExternalFileActivity", "failed to close file");
                }
            }
        }
    }
    
    /**
    * Delete file process
    *
    * @param view
    */
    public void onDeleteFileClick(View view) {
        File file = new File(getExternalFilesDir(TARGET_TYPE), FILE_NAME);
        file.delete();
        mFileView.setText(R.string.file_view);
    }
}

使用的示例代码:

ExternalFileUser.java

package org.jssec.android.file.externaluser;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import android.app.Activity;
import android.app.AlertDialog;
import android.content.ActivityNotFoundException;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.pm.PackageManager.NameNotFoundException;
import android.os.Bundle;
import android.view.View;
import android.widget.TextView;

public class ExternalUserActivity extends Activity {

    private TextView mFileView;
    private static final String TARGET_PACKAGE = "org.jssec.android.file.externalfile";
    private static final String TARGET_CLASS = "org.jssec.android.file.externalfile.ExternalFileActivity";
    private static final String TARGET_TYPE = "external";
    private static final String FILE_NAME = "external_file.dat";
    
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.user);
        mFileView = (TextView) findViewById(R.id.file_view);
    }
    private void callFileActivity() {
        Intent intent = new Intent();
        intent.setClassName(TARGET_PACKAGE, TARGET_CLASS);
        try {
            startActivity(intent);
        } catch (ActivityNotFoundException e) {
            mFileView.setText("(File Activity does not exist)");
        }
    }
    
    /**
    * Call file Activity process
    *
    * @param view
    */
    public void onCallFileActivityClick(View view) {
        callFileActivity();
    }
    
    /**
    * Read file process
    *
    * @param view
    */
    public void onReadFileClick(View view) {
        FileInputStream fis = null;
        try {
            File file = new File(getFilesPath(FILE_NAME));
            fis = new FileInputStream(file);
            byte[] data = new byte[(int) fis.getChannel().size()];
            fis.read(data);
            // *** POINT 3 *** Regarding the information to be stored in files, handle file data carefully and securely.
            // Omitted, since this is a sample. Please refer to "3.2 Handling Input Data Carefully and Securely."
            String str = new String(data);
            mFileView.setText(str);
        } catch (FileNotFoundException e) {
            mFileView.setText(R.string.file_view);
        } catch (IOException e) {
            android.util.Log.e("ExternalUserActivity", "failed to read file");
        } finally {
            if (fis != null) {
                try {
                    fis.close();
                } catch (IOException e) {
                    android.util.Log.e("ExternalUserActivity", "failed to close file");
                }
            }
        }
    }
    
    /**
    * Rewrite file process
    *
    * @param view
    */
    public void onWriteFileClick(View view) {
        // *** POINT 4 *** Writing file by the requesting application should be prohibited as the specification.
        // Application should be designed supposing malicious application may overwrite or delete file.
        final AlertDialog.Builder alertDialogBuilder = new AlertDialog.Builder(this);
        alertDialogBuilder.setTitle("POINT 4");
        alertDialogBuilder.setMessage("Do not write in calling appllication.");
        alertDialogBuilder.setPositiveButton("OK",
        new DialogInterface.OnClickListener() {
            @Override
            public void onClick(DialogInterface dialog, int which) {
                callFileActivity();
            }
        });
        alertDialogBuilder.create().show();
    }
    
    private String getFilesPath(String filename) {
        String path = "";
        try {
            Context ctx = createPackageContext(TARGET_PACKAGE,
            Context.CONTEXT_IGNORE_SECURITY);
            File file = new File(ctx.getExternalFilesDir(TARGET_TYPE), filename);
            path = file.getPath();
        } catch (NameNotFoundException e) {
            android.util.Log.e("ExternalUserActivity", "no file");
        }
        return path;
    }
}

AndroidManifest.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="org.jssec.android.file.externaluser" >
    <!-- In Android 4.0.3 (API Level 14) and later, the permission for reading external storages
    has been defined and the application should decalre that it requires the permission.
    In fact in Android 4.4 (API Level 19) and later, that must be declared to read other directories
    than the package specific directories. -->
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
    
    <application
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name"
        android:allowBackup="false" >
        <activity
            android:name=".ExternalUserActivity"
            android:label="@string/app_name"
            android:exported="true" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>
</manifest>

书籍推荐