电脑文件在硬盘分区上。Android 则分成内部存储 internal和外部存储 external。早期多数机器内置不可卸载内存,再加一张可卸 SD 卡;后来不少机型把两块都做成内置不可卸,但逻辑上仍按 internal / external 划分,不再用「能不能拔卡」当标准。
一、内部存储 vs 外部存储
| 对比 |
Internal |
External |
| 可用性 |
总是可用 |
不一定,USB 大容量模式时可能访问不到 |
| 权限 |
默认只有本 app 能访问 |
大家都能访问,没有访问控制 |
| 卸载 app |
相关文件全部清掉 |
只删 getExternalFilesDir() 下的私有文件 |
| 适合 |
不想被用户/其它 app 碰到的数据 |
要共享、或允许用电脑拷走的文件 |
Tip:app 默认装到 internal。可在 manifest 声明 android:installLocation,允许装到外部存储(安装包很大时用户更愿意有这个选项)。读写外部存储要权限:
<manifest ...>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
...
</manifest>
二、保存到内部存储
拿目录:getFilesDir() → /data/data/com.example.android/files/;getCacheDir() → /data/data/com.example.android/cache/。
File file = new File(getFilesDir(), filename);
FileOutputStream os;
try {
os = new FileOutputStream(file);
os.write(string.getBytes());
os.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
也可以 openFileOutput() 直接拿流写到 internal:
String filename = "myfile";
String string = "Hello world!";
FileOutputStream outputStream;
try {
outputStream = openFileOutput(filename, Context.MODE_PRIVATE);
outputStream.write(string.getBytes());
outputStream.close();
} catch (Exception e) {
e.printStackTrace();
}
三、保存到外部存储
读写前先检查是否可用:
private boolean isExternalStorageWritable() {
String state = Environment.getExternalStorageState();
if (state.equals(Environment.MEDIA_MOUNTED)) {
return true;
} else {
return false;
}
}
外部再分公共区 public和私有区 private。公共文件对用户和其它 app 可见,卸载本 app 也应保留(拍照、下载)。私有文件归本 app,卸载时系统会删掉 private 目录(例如缓存);技术上别人也能读,但对别人通常没意义。
公共区用 getExternalStoragePublicDirectory(),参数指定类型(如 DIRECTORY_MUSIC、DIRECTORY_PICTURES):
public File getPictureStorageDir(String pictureName) {
File file = new File(Environment.getExternalStoragePublicDirectory(
Environment.DIRECTORY_PICTURES), pictureName);
if (!file.mkdirs()) {
Log.e(LOG_TAG, "Directory not created");
}
return file;
}
DIRECTORY_PICTURES 对应 mnt/sdcard/Pictures。其它标准目录:
私有区用 getExternalFilesDir():
public File getAlbumStorageDir(Context context, String albumName) {
File file = new File(context.getExternalFilesDir(
Environment.DIRECTORY_PICTURES), albumName);
if (!file.mkdirs()) {
Log.e(LOG_TAG, "Directory not created");
}
return file;
}
路径形如 /mnt/sdcard/Android/data/com.example.xx/files/Pictures。传 null 则返回该 app 外部私有根目录。
四、实例:资源图写入公共区再读回
从 app 读出图片资源,写入外部公共区,再读文件显示到界面:
mWriterBtn.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
if (!isExternalStorageWritable()) {
Toast.makeText(MyFileActivity.this, "外部存储不可用", Toast.LENGTH_SHORT).show();
return;
}
File file = new File(fileDir, PCITRUE_FILE_NAME);
FileOutputStream os;
try {
os = new FileOutputStream(file);
Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.m3);
bitmap.compress(Bitmap.CompressFormat.PNG, 90, os);
os.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
});
mReaderBtn.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
if (!isExternalStorageWritable()) {
Toast.makeText(MyFileActivity.this, "外部存储不可用", Toast.LENGTH_SHORT).show();
return;
}
File file = new File(fileDir, PCITRUE_FILE_NAME);
mDirTxt.setText(file.getPath());
Bitmap bitmap = BitmapFactory.decodeFile(file.getPath());
mShowImg.setImageBitmap(bitmap);
}
});
显示效果

一句话总结:私密数据放 internal(getFilesDir / openFileOutput);要共享或给电脑看的放 external,先 MEDIA_MOUNTED 检查,公共目录 getExternalStoragePublicDirectory,私有目录 getExternalFilesDir。