Android 中内部存储和外部存储的理解与应用

简介: Android 中内部存储和外部存储的理解与应用

一、Android存储分为:

内部存储(Internal Storage)和外部存储(External Storage)

特点

  1. 内部存储:随应用卸载被删除
  2. 外部存储:
    1.公有目录:存放一些下载的视频文件等,比如还有movies,fictures,music等公有的一些文件目录
    2.私有目录:随应用卸载被删除

下面来具体介绍一下内部存储和外部存储的具体路径:

1、内部存储:不需要任何权限

  1. /data/data/< applicationId,就是包名 >/shared_prefs
  2. /data/data/< applicationId >/databases
  3. /data/data/< applicationId >/files 通过context.getFilesDir() 获取该目录
  4. /data/data/< applicationId >/cache 通过context.getCacheDir() 获取该目录

Android SDK提供了几个常见的内部存储文件的权限

  1. Context.MODE_PRIVATE :私有方式存储,其他应用无法访问,覆盖旧的同名文件
  2. Context.MODE_APPEND:私有方式存储,若有旧的同名文件,则在该文件上追加数据

2、外部存储:

  1. 公有目录:Environment.getExternalStoragePublicDirectory(int type),这里的type类型不能为空,可以是DIRECTORY_MUSIC,DIRECTORY_MOVIES等 通过这个方法获取公有目录下相对应的文件
    Environment.getExternalStorageDirectory()也可以获取。
    注:Environment是操作SD卡的工具类
//publicDirectory路径为:/storage/emulated/0/Movies
 String publicDirectory = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_MOVIES).getAbsolutePath();
//sdPath路径为: /storage/emulated/0
String sdPath = Environment.getExternalStorageDirectory().getAbsolutePath();
  1. 私有目录:
    /mnt/sdcard/Android/data/data/< applicationId >/cache 通过getExternalCacheDir().getAbsolutePath()得到的路径为:
    /storage/emulated/0/Android/data/包名/cache等价于上面的路径。
    /mnt/sdcard/Android/data/data/< applicationId >/files 通过getExternalFilesDir().getAbsolutePath()方法可以获取到路径为:
    /storage/emulated/0/Android/data/包名/files等价于上面的路径。

注意:

外部存储的私有目录文件会随着应用的卸载而被删除,公有目录则不会被删除。

再看一下具体区别:

下面演示内部存储数据的一个实例

一、activity_internal.xml布局文件

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".InternalActivity">
    <EditText
        android:id="@+id/edit_username"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:hint="请输入用户名"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent" />
    <EditText
        android:id="@+id/edit_password"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:hint="请输入密码"
        android:inputType="textPassword"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toBottomOf="@id/edit_username" />
    <Button
        android:id="@+id/btn_save_userMessage"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="保存用户信息"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toBottomOf="@id/edit_password" />
    <Button
        android:id="@+id/btn_read"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="读取用户信息"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toBottomOf="@id/btn_save_userMessage" />
    <TextView
        android:id="@+id/tv_message"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toBottomOf="@id/btn_read" />
</androidx.constraintlayout.widget.ConstraintLayout>

二、InternalActivity类,实现具体的存储和读取操作

public class InternalActivity extends AppCompatActivity implements View.OnClickListener {
    private EditText edit_username;
    private EditText edit_password;
    private Button btn_save_userMessage;
    private Button btn_read;
    private TextView tv_message;
    //文件名
    private String fileName = "test";
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_internal);
        edit_username = findViewById(R.id.edit_username);
        edit_password = findViewById(R.id.edit_password);
        btn_save_userMessage = findViewById(R.id.btn_save_userMessage);
        btn_read = findViewById(R.id.btn_read);
        tv_message = findViewById(R.id.tv_message);
        btn_save_userMessage.setOnClickListener(this);
        btn_read.setOnClickListener(this);
    }
    @Override
    public void onClick(View v) {
        switch (v.getId()) {
            case R.id.btn_save_userMessage:
                String username = edit_username.getText().toString();
                String password = edit_password.getText().toString();
                if (TextUtils.isEmpty(username)) {
                    Toast.makeText(this, "用户名不能为空!", Toast.LENGTH_SHORT).show();
                    return;
                }
                if (TextUtils.isEmpty(password)) {
                    Toast.makeText(this, "密码不能为空!", Toast.LENGTH_SHORT).show();
                    return;
                }
                FileOutputStream fos = null;
                try {
                    //第一个参数:文件名
                    //第二个参数:表示文件输出的类型 这里选择Context.MODE_PRIVATE每次生成相同的文件名,则覆盖原有的文件
                    fos = openFileOutput(fileName, Context.MODE_PRIVATE);
                    String nameAndPassword = username + "." + password;
                    byte[] bytes = nameAndPassword.getBytes();
                    fos.write(bytes);
                } catch (Exception e) {
                    e.printStackTrace();
                } finally {
                    if (fos != null) {
                        try {
                            fos.close();
                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                    }
                }
                Toast.makeText(this, "保存成功!", Toast.LENGTH_SHORT).show();
                break;
            case R.id.btn_read:
                FileInputStream fis = null;
                try {
                    fis = openFileInput(fileName);
                    //fis.available() 判断文件有多少个字节
                    byte[] bytes = new byte[fis.available()];
                    while (fis.read(bytes) != -1) {
                        String message = new String(bytes);
                        String[] split = message.split("\\.");
                        tv_message.setText("用户名:" + split[0] + "\n" + "密码:" + split[1]);
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                }
                break;
            default:
                break;
        }
    }
}

效果演示如下:

下面演示外部存储数据的一个实例

一、布局页面activity_external.xml

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".ExternalActivity">
    <EditText
        android:id="@+id/edit_username"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:hint="请输入用户名"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent" />
    <EditText
        android:id="@+id/edit_password"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:hint="请输入密码"
        android:inputType="textPassword"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toBottomOf="@id/edit_username" />
    <Button
        android:id="@+id/btn_save_userMessage"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="保存用户信息到外部存储"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toBottomOf="@id/edit_password" />
    <Button
        android:id="@+id/btn_read"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="从外部存储读取用户信息"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toBottomOf="@id/btn_save_userMessage" />
    <TextView
        android:id="@+id/tv_message"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toBottomOf="@id/btn_read" />
</androidx.constraintlayout.widget.ConstraintLayout>

二、ExternalActivity类实现保存和读取数据的功能。

注意:在使用外部存储之前,需要调用Environment类的getExternalStorageState()方法来检查存储介质状态,是否可用。还是仅可以读,不能写等。

public class ExternalActivity extends AppCompatActivity implements View.OnClickListener {
    private EditText edit_username;
    private EditText edit_password;
    private Button btn_save_userMessage;
    private Button btn_read;
    private TextView tv_message;
    //文件名
    private String fileName = "test";
    //外部状态可用
    boolean mExternalStorageAvailable = false;
    //外部状态可写
    boolean mExternalStorageWrite = false;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_external);
        edit_username = findViewById(R.id.edit_username);
        edit_password = findViewById(R.id.edit_password);
        btn_save_userMessage = findViewById(R.id.btn_save_userMessage);
        btn_read = findViewById(R.id.btn_read);
        tv_message = findViewById(R.id.tv_message);
        btn_save_userMessage.setOnClickListener(this);
        btn_read.setOnClickListener(this);
    }
    @Override
    public void onClick(View v) {
        switch (v.getId()) {
            case R.id.btn_save_userMessage:
                String username = edit_username.getText().toString();
                String password = edit_password.getText().toString();
                if (TextUtils.isEmpty(username)) {
                    Toast.makeText(this, "用户名不能为空!", Toast.LENGTH_SHORT).show();
                    return;
                }
                if (TextUtils.isEmpty(password)) {
                    Toast.makeText(this, "密码不能为空!", Toast.LENGTH_SHORT).show();
                    return;
                }
                //首先判断外部存储设备的状态
                checkExternalStorage();
                OutputStream outputStream = null;
                try {
                    if (mExternalStorageWrite) {
                        outputStream = new FileOutputStream(new File(getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS), fileName));
                        String nameAndPassword = username + "." + password;
                        byte[] bytes = nameAndPassword.getBytes();
                        outputStream.write(bytes);
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                } finally {
                    if (outputStream != null) {
                        try {
                            outputStream.close();
                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                    }
                }
                Toast.makeText(this, "保存成功!", Toast.LENGTH_SHORT).show();
                break;
            case R.id.btn_read:
                checkExternalStorage();
                FileInputStream fis = null;
                try {
                    //第一个参数:文件目录 第二个参数:文件名
                    //getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS)对应的路径为:
                    // /storage/emulated/0/Android/data/com.example.customviewproject/files/Download
                    File file = new File(getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS), fileName);
                    if (mExternalStorageAvailable) {
                        fis = new FileInputStream(file);
                        //判断当前文件的字节个数
                        byte[] bytes = new byte[fis.available()];
                        while (fis.read(bytes) != -1) {
                            String message = new String(bytes);
                            String[] split = message.split("\\.");
                            tv_message.setText("用户名:" + split[0] + "\n" + "密码:" + split[1]);
                        }
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                } finally {
                    if (fis != null) {
                        try {
                            fis.close();
                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                    }
                }
                break;
            default:
                break;
        }
    }
    private void checkExternalStorage() {
        //获取外部存储器的状态
        String state = Environment.getExternalStorageState();
        //主要有两种状态
        //外部状态可用
        if (Environment.MEDIA_MOUNTED.equals(state)) {
            //外部存储器可用 表示既可以写 也可以读
            mExternalStorageAvailable = true;
            mExternalStorageWrite = true;
        } else if (Environment.MEDIA_MOUNTED_READ_ONLY.equals(state)) {
            //外部存储器可用 表示仅可以读 不可写
            mExternalStorageAvailable = true;
            mExternalStorageWrite = false;
        } else {//表示为不可用的状态
            mExternalStorageAvailable = false;
            mExternalStorageWrite = false;
        }
    }
}

效果演示:

操作assets目录下的图片文件,进行内部存储与读取操作,代码如下

public class InnerFileActivity extends AppCompatActivity {
    private ImageView imageView;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_inner_file);
        imageView = findViewById(R.id.imageView);
    }
    public void save(View view) {
        //操作assets目录下的文件
        AssetManager manager = getAssets();
        try {
            //得到assets目录下图片的输入流  \src\main\assets\logo.png
            InputStream is = manager.open("logo.png");
            //输出流指向的路径为:/data/data/com.example.customviewproject/files/logo.png
            FileOutputStream fileOutputStream = openFileOutput("logo.png", Context.MODE_PRIVATE);
            byte[] buffer = new byte[1024];
            int len;
            while ((len = is.read(buffer)) != -1) {
                fileOutputStream.write(buffer, 0, len);
            }
            fileOutputStream.close();
            is.close();
            Toast.makeText(this, "保存成功!", Toast.LENGTH_SHORT).show();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    public void read(View view) {
        /**
         *  图片保存的路径为: /data/data/com.example.customviewproject/files/logo.png
         */
        //1.得到图片文件路径
        //filesPath为: /data/user/0/com.example.customviewproject/files
        String filesPath = getFilesDir().getAbsolutePath();
        String imagePath = filesPath + "/logo.png";
        //根据图片路径得到bitmap对象
        Bitmap bitmap = BitmapFactory.decodeFile(imagePath);
        imageView.setImageBitmap(bitmap);
    }
}


目录
相关文章
|
3月前
|
开发框架 前端开发 Android开发
Flutter 与原生模块(Android 和 iOS)之间的通信机制,包括方法调用、事件传递等,分析了通信的必要性、主要方式、数据传递、性能优化及错误处理,并通过实际案例展示了其应用效果,展望了未来的发展趋势
本文深入探讨了 Flutter 与原生模块(Android 和 iOS)之间的通信机制,包括方法调用、事件传递等,分析了通信的必要性、主要方式、数据传递、性能优化及错误处理,并通过实际案例展示了其应用效果,展望了未来的发展趋势。这对于实现高效的跨平台移动应用开发具有重要指导意义。
344 4
|
15天前
|
前端开发 Java Shell
【08】flutter完成屏幕适配-重建Android,增加GetX路由,屏幕适配,基础导航栏-多版本SDK以及gradle造成的关于fvm的使用(flutter version manage)-卓伊凡换人优雅草Alex-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草Alex
【08】flutter完成屏幕适配-重建Android,增加GetX路由,屏幕适配,基础导航栏-多版本SDK以及gradle造成的关于fvm的使用(flutter version manage)-卓伊凡换人优雅草Alex-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草Alex
116 20
【08】flutter完成屏幕适配-重建Android,增加GetX路由,屏幕适配,基础导航栏-多版本SDK以及gradle造成的关于fvm的使用(flutter version manage)-卓伊凡换人优雅草Alex-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草Alex
|
12天前
|
Dart 前端开发 Android开发
【09】flutter首页进行了完善-采用android studio 进行真机调试开发-增加了直播间列表和短视频人物列表-增加了用户中心-卓伊凡换人优雅草Alex-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草Alex
【09】flutter首页进行了完善-采用android studio 进行真机调试开发-增加了直播间列表和短视频人物列表-增加了用户中心-卓伊凡换人优雅草Alex-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草Alex
36 4
【09】flutter首页进行了完善-采用android studio 进行真机调试开发-增加了直播间列表和短视频人物列表-增加了用户中心-卓伊凡换人优雅草Alex-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草Alex
|
2月前
|
JSON Java API
探索安卓开发:打造你的首个天气应用
在这篇技术指南中,我们将一起潜入安卓开发的海洋,学习如何从零开始构建一个简单的天气应用。通过这个实践项目,你将掌握安卓开发的核心概念、界面设计、网络编程以及数据解析等技能。无论你是初学者还是有一定基础的开发者,这篇文章都将为你提供一个清晰的路线图和实用的代码示例,帮助你在安卓开发的道路上迈出坚实的一步。让我们一起开始这段旅程,打造属于你自己的第一个安卓应用吧!
91 14
|
2月前
|
Java Linux 数据库
探索安卓开发:打造你的第一款应用
在数字时代的浪潮中,每个人都有机会成为创意的实现者。本文将带你走进安卓开发的奇妙世界,通过浅显易懂的语言和实际代码示例,引导你从零开始构建自己的第一款安卓应用。无论你是编程新手还是希望拓展技术的开发者,这篇文章都将为你打开一扇门,让你的创意和技术一起飞扬。
|
2月前
|
搜索推荐 前端开发 测试技术
打造个性化安卓应用:从设计到开发的全面指南
在这个数字时代,拥有一个定制的移动应用不仅是一种趋势,更是个人或企业品牌的重要延伸。本文将引导你通过一系列简单易懂的步骤,从构思你的应用理念开始,直至实现一个功能齐全的安卓应用。无论你是编程新手还是希望拓展技能的开发者,这篇文章都将为你提供必要的工具和知识,帮助你将创意转化为现实。
|
2月前
|
Java Android开发 开发者
探索安卓开发:构建你的第一个“Hello World”应用
在安卓开发的浩瀚海洋中,每个新手都渴望扬帆起航。本文将作为你的指南针,引领你通过创建一个简单的“Hello World”应用,迈出安卓开发的第一步。我们将一起搭建开发环境、了解基本概念,并编写第一行代码。就像印度圣雄甘地所说:“你必须成为你希望在世界上看到的改变。”让我们一起开始这段旅程,成为我们想要见到的开发者吧!
85 0
|
3月前
|
JSON Java Android开发
探索安卓开发之旅:打造你的第一个天气应用
【10月更文挑战第30天】在这个数字时代,掌握移动应用开发技能无疑是进入IT行业的敲门砖。本文将引导你开启安卓开发的奇妙之旅,通过构建一个简易的天气应用来实践你的编程技能。无论你是初学者还是有一定经验的开发者,这篇文章都将成为你宝贵的学习资源。我们将一步步地深入到安卓开发的世界中,从搭建开发环境到实现核心功能,每个环节都充满了发现和创造的乐趣。让我们开始吧,一起在代码的海洋中航行!
|
3月前
|
存储 搜索推荐 Java
打造个性化安卓应用:从设计到实现
【10月更文挑战第30天】在数字化时代,拥有一个个性化的安卓应用不仅能够提升用户体验,还能加强品牌识别度。本文将引导您了解如何从零开始设计和实现一个安卓应用,涵盖用户界面设计、功能开发和性能优化等关键环节。我们将以一个简单的记事本应用为例,展示如何通过Android Studio工具和Java语言实现基本功能,同时确保应用流畅运行。无论您是初学者还是希望提升现有技能的开发者,这篇文章都将为您提供宝贵的见解和实用的技巧。
|
3月前
|
搜索推荐 开发工具 Android开发
打造个性化Android应用:从设计到实现的旅程
【10月更文挑战第26天】在这个数字时代,拥有一个能够脱颖而出的移动应用是成功的关键。本文将引导您了解如何从概念化阶段出发,通过设计、开发直至发布,一步步构建一个既美观又实用的Android应用。我们将探讨用户体验(UX)设计的重要性,介绍Android开发的核心组件,并通过实际案例展示如何克服开发中的挑战。无论您是初学者还是有经验的开发者,这篇文章都将为您提供宝贵的见解和实用的技巧,帮助您在竞争激烈的应用市场中脱颖而出。

热门文章

最新文章

  • 1
    如何修复 Android 和 Windows 不支持视频编解码器的问题?
  • 2
    【08】flutter完成屏幕适配-重建Android,增加GetX路由,屏幕适配,基础导航栏-多版本SDK以及gradle造成的关于fvm的使用(flutter version manage)-卓伊凡换人优雅草Alex-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草Alex
  • 3
    当flutter react native 等混开框架-并且用vscode-idea等编译器无法打包apk,打包安卓不成功怎么办-直接用android studio如何打包安卓apk -重要-优雅草卓伊凡
  • 4
    【04】flutter补打包流程的签名过程-APP安卓调试配置-结构化项目目录-完善注册相关页面-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程
  • 5
    APP-国内主流安卓商店-应用市场-鸿蒙商店上架之必备前提·全国公安安全信息评估报告如何申请-需要安全评估报告的资料是哪些-优雅草卓伊凡全程操作
  • 6
    【09】flutter首页进行了完善-采用android studio 进行真机调试开发-增加了直播间列表和短视频人物列表-增加了用户中心-卓伊凡换人优雅草Alex-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草Alex
  • 7
    Android经典面试题之Kotlin中Lambda表达式和匿名函数的区别
  • 8
    【02】仿站技术之python技术,看完学会再也不用去购买收费工具了-本次找了小影-感觉页面很好看-本次是爬取vue需要用到Puppeteer库用node.js扒一个app下载落地页-包括安卓android下载(简单)-ios苹果plist下载(稍微麻烦一丢丢)-优雅草卓伊凡
  • 9
    【01】仿站技术之python技术,看完学会再也不用去购买收费工具了-用python扒一个app下载落地页-包括安卓android下载(简单)-ios苹果plist下载(稍微麻烦一丢丢)-客户的麻将软件需要下载落地页并且要做搜索引擎推广-本文用python语言快速开发爬取落地页下载-优雅草卓伊凡
  • 10
    Android学习自定义View(四)——继承控件(滑动时ListView的Item出现删除按钮)