Android之从特定目录读取和写入数据

Android之访问内外部存储空间

本文链接:http://blog.csdn.net/qq_16628781/article/details/61413556


知识点:

1、权限管理;

2、获取内外部目录的方法;

3、此示例演示如何从特定目录读取和写入数据,同时需要较少的权限

在开发中,我们经常会用到存储空间,这是一个不可避免的问题。那么,我们该如何来获取到我们需要的存储空间呢。下面为大家介绍一个方法,因为我都在代码里头做了解释,所以文字我就不写那么多了,省的大家一开头就看到这么多的文字,就不想看下去了。

关键的代码都做了注释。


这里要注意一点:我的项目至少是24的SDK才能运行的。

compileSdkVersion 24
    buildToolsVersion "25.0.2"

    defaultConfig {
        minSdkVersion 24
        targetSdkVersion 24
    }


什么?你手头没有24以上的机器?这还不简单,我们可以利用as提供的虚拟机设备啊。只要你勤点更新SDK,你就能启动一个25SDK机器了。

首先上图



我打开的是sd卡的目录,我们可以看到,列出了这么多,但是因为是模拟题,里头都是没有东西的。


下面,我先把这个页面的代码贴出来


1、是主页fragment_scoped_directory_access的布局:


<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:gravity="center_vertical"
    android:orientation="vertical"
    android:padding="@dimen/margin_medium">

    <LinearLayout
        android:id="@+id/container_volumes"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:orientation="vertical">

        <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginBottom="@dimen/margin_small"
            android:orientation="horizontal">

            <TextView
                android:id="@+id/textview_primary_volume_name"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_marginEnd="@dimen/margin_medium"
                android:text="内部存储空间" />

            <Button
                android:id="@+id/button_open_directory_primary_volume"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="打开" />
        </LinearLayout>
    </LinearLayout>

    <Spinner
        android:id="@+id/spinner_directories"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginBottom="@dimen/margin_small" />

    <LinearLayout
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginEnd="@dimen/margin_small"
        android:layout_marginStart="@dimen/margin_small"
        android:orientation="horizontal">

        <TextView
            android:id="@+id/label_current_directory"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="选择的目录" />

        <TextView
            android:id="@+id/textview_current_directory"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:enabled="false"
            android:textColor="#000000" />

    </LinearLayout>

    <TextView
        android:id="@+id/textview_nothing_in_directory"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginStart="@dimen/margin_small"
        android:layout_marginTop="@dimen/margin_medium"
        android:text="选择的文件夹为空"
        android:visibility="gone" />

    <android.support.v7.widget.RecyclerView
        android:id="@+id/recyclerview_directory_entries"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_marginEnd="@dimen/margin_small"
        android:layout_marginStart="@dimen/margin_small"
        android:drawSelectorOnTop="true"
        android:scrollbars="vertical"
        app:layoutManager="LinearLayoutManager" />

</LinearLayout>


然后是volume_entry的布局:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_marginBottom="@dimen/margin_small"
    android:orientation="horizontal">

    <TextView
        android:id="@+id/textview_volume_name"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginEnd="@dimen/margin_medium" />

    <Button
        android:id="@+id/button_open_directory"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="打开" />
</LinearLayout>


2、是Java代码:

import android.app.Activity;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
import android.os.Environment;
import android.os.storage.StorageManager;
import android.os.storage.StorageVolume;
import android.provider.DocumentsContract;
import android.support.v4.app.Fragment;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.LinearLayout;
import android.widget.Spinner;
import android.widget.TextView;

import java.util.ArrayList;
import java.util.List;

/**
 * 访问内外部存储空间的例子
 */
public class ScopedDirectoryAccessFragment extends Fragment {

    private static final String DIRECTORY_ENTRIES_KEY = "directory_entries";
    private static final String SELECTED_DIRECTORY_KEY = "selected_directory";
    private static final int OPEN_DIRECTORY_REQUEST_CODE = 1;

    private static final String[] DIRECTORY_SELECTION = new String[]{
            DocumentsContract.Document.COLUMN_DISPLAY_NAME,
            DocumentsContract.Document.COLUMN_MIME_TYPE,
            DocumentsContract.Document.COLUMN_DOCUMENT_ID,
    };

    private Activity mActivity;
    private StorageManager mStorageManager;
    private TextView mCurrentDirectoryTextView;
    private TextView mNothingInDirectoryTextView;
    private TextView mPrimaryVolumeNameTextView;
    private Spinner mDirectoriesSpinner;
    private DirectoryEntryAdapter mAdapter;
    private ArrayList<DirectoryEntry> mDirectoryEntries;

    public static ScopedDirectoryAccessFragment newInstance() {
        ScopedDirectoryAccessFragment fragment = new ScopedDirectoryAccessFragment();
        return fragment;
    }

    public ScopedDirectoryAccessFragment() {
    }

    @Override
    public void onAttach(Context context) {
        super.onAttach(context);
        mActivity = getActivity();
        mStorageManager = mActivity.getSystemService(StorageManager.class);
    }

    @Override
    public void onActivityResult(int requestCode, int resultCode, Intent data) {
        // 回调
        super.onActivityResult(requestCode, resultCode, data);
        if (requestCode == OPEN_DIRECTORY_REQUEST_CODE && resultCode == Activity.RESULT_OK) {
            // 向用户获取权读取内部存储和外部存储的权限
            getActivity().getContentResolver().takePersistableUriPermission(data.getData(),
                    Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
            updateDirectoryEntries(data.getData());
        }
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        return inflater.inflate(R.layout.fragment_scoped_directory_access, container, false);
    }

    @Override
    public void onViewCreated(final View rootView, Bundle savedInstanceState) {
        super.onViewCreated(rootView, savedInstanceState);

        mCurrentDirectoryTextView = (TextView) rootView
                .findViewById(R.id.textview_current_directory);
        mNothingInDirectoryTextView = (TextView) rootView
                .findViewById(R.id.textview_nothing_in_directory);
        mPrimaryVolumeNameTextView = (TextView) rootView
                .findViewById(R.id.textview_primary_volume_name);

        // Set onClickListener for the primary volume
        Button openPictureButton = (Button) rootView
                .findViewById(R.id.button_open_directory_primary_volume);
        /* 内部存储空间按钮 */
        openPictureButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                // 得到spinner的选择项
                String selected = mDirectoriesSpinner.getSelectedItem().toString();
                // 获取到要访问的目录名称
                String directoryName = getDirectoryName(selected);
                // 获取到外部存储空间的大小
                StorageVolume storageVolume = mStorageManager.getPrimaryStorageVolume();
                // 创建一个访问的意图,得到用户的允许之后,就可以访问了
                Intent intent = storageVolume.createAccessIntent(directoryName);
                // 启动
                startActivityForResult(intent, OPEN_DIRECTORY_REQUEST_CODE);
            }
        });

        //获取外部存储空间
        List<StorageVolume> storageVolumes = mStorageManager.getStorageVolumes();
        LinearLayout containerVolumes = (LinearLayout) mActivity
                .findViewById(R.id.container_volumes);
        //如果内部存储空间(sd卡)存在的话,就加入访问外部存储空间的按钮
        for (final StorageVolume volume : storageVolumes) {
            String volumeDescription = volume.getDescription(mActivity);
            if (volume.isPrimary()) {
                // Primary volume area is already added...
                if (volumeDescription != null) {
                    // 设置外部存储的真实名称,如果可用的话
                    mPrimaryVolumeNameTextView.setText(volumeDescription);
                }
                continue;
            }
            // 加载自定义布局
            LinearLayout volumeArea = (LinearLayout) mActivity.getLayoutInflater()
                    .inflate(R.layout.volume_entry, containerVolumes);
            TextView volumeName = (TextView) volumeArea.findViewById(R.id.textview_volume_name);
            volumeName.setText(volumeDescription);
            Button button = (Button) volumeArea.findViewById(R.id.button_open_directory);
            // 设置点击事件
            button.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View view) {
                    String selected = mDirectoriesSpinner.getSelectedItem().toString();
                    String directoryName = getDirectoryName(selected);
                    Intent intent = volume.createAccessIntent(directoryName);
                    startActivityForResult(intent, OPEN_DIRECTORY_REQUEST_CODE);
                }
            });
        }

        RecyclerView recyclerView = (RecyclerView) rootView
                .findViewById(R.id.recyclerview_directory_entries);
        // 这里做一个判断,是否之前有保存此fragment的状态
        if (savedInstanceState != null) {
            mDirectoryEntries = savedInstanceState.getParcelableArrayList(DIRECTORY_ENTRIES_KEY);
            mCurrentDirectoryTextView.setText(savedInstanceState.getString(SELECTED_DIRECTORY_KEY));
            mAdapter = new DirectoryEntryAdapter(mDirectoryEntries);
            if (mAdapter.getItemCount() == 0) {
                mNothingInDirectoryTextView.setVisibility(View.VISIBLE);
            }
        } else {
            mDirectoryEntries = new ArrayList<>();
            mAdapter = new DirectoryEntryAdapter();
        }
        recyclerView.setAdapter(mAdapter);
        recyclerView.setLayoutManager(new LinearLayoutManager(getActivity()));

        // 给spinner设置值
        mDirectoriesSpinner = (Spinner) rootView.findViewById(R.id.spinner_directories);
        ArrayAdapter<CharSequence> directoriesAdapter = ArrayAdapter
                .createFromResource(getActivity(),
                        R.array.directories, android.R.layout.simple_spinner_item);
        directoriesAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
        mDirectoriesSpinner.setAdapter(directoriesAdapter);
    }

    @Override
    public void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);
        // 当fragment被系统意外杀死是,会调用此方法
        // 如果有不明白的,可以看 这个文章:
        // http://blog.csdn.net/qq_16628781/article/details/60877412
        outState.putString(SELECTED_DIRECTORY_KEY, mCurrentDirectoryTextView.getText().toString());
        outState.putParcelableArrayList(DIRECTORY_ENTRIES_KEY, mDirectoryEntries);
    }

    private void updateDirectoryEntries(Uri uri) {
        mDirectoryEntries.clear();

        // 这里涉及到内容提供者,可以搜索内容提供者的相关知识
        // 也可以看这篇文章的解释:http://blog.csdn.net/qq_16628781/article/details/61195621
        // 获取内容获得者
        ContentResolver contentResolver = getActivity().getContentResolver();

        // 根据URI和id,建立一个URI代表目标去访问内容提供者
        Uri docUri = DocumentsContract.buildDocumentUriUsingTree(uri,
                DocumentsContract.getTreeDocumentId(uri));
        // 访问URI的子目录
        Uri childrenUri = DocumentsContract.buildChildDocumentsUriUsingTree(uri,
                DocumentsContract.getTreeDocumentId(uri));

        // 查询URI提供者
        try (Cursor docCursor = contentResolver
                .query(docUri, DIRECTORY_SELECTION, null, null, null)) {
            while (docCursor != null && docCursor.moveToNext()) {
                mCurrentDirectoryTextView.setText(docCursor.getString(docCursor.getColumnIndex(
                        DocumentsContract.Document.COLUMN_DISPLAY_NAME)));
            }
        }

        // 查询子目录
        try (Cursor childCursor = contentResolver
                .query(childrenUri, DIRECTORY_SELECTION, null, null, null)) {
            while (childCursor != null && childCursor.moveToNext()) {
                // 获得子目录下的所有文件夹,最后在recycleview里头展示出来
                DirectoryEntry entry = new DirectoryEntry();
                entry.fileName = childCursor.getString(childCursor.getColumnIndex(
                        DocumentsContract.Document.COLUMN_DISPLAY_NAME));
                entry.mimeType = childCursor.getString(childCursor.getColumnIndex(
                        DocumentsContract.Document.COLUMN_MIME_TYPE));
                mDirectoryEntries.add(entry);
            }

            if (mDirectoryEntries.isEmpty()) {
                mNothingInDirectoryTextView.setVisibility(View.VISIBLE);
            } else {
                mNothingInDirectoryTextView.setVisibility(View.GONE);
            }
            mAdapter.setDirectoryEntries(mDirectoryEntries);
            mAdapter.notifyDataSetChanged();
        }
    }

    /**
     * 获取目录的名称
     *
     * @param name name
     * @return name,比如有dcim图片目录,下载目录,音乐等等文件类型
     */
    private String getDirectoryName(String name) {
        switch (name) {
            case "ALARMS":
                return Environment.DIRECTORY_ALARMS;
            case "DCIM":
                return Environment.DIRECTORY_DCIM;
            case "DOCUMENTS":
                return Environment.DIRECTORY_DOCUMENTS;
            case "DOWNLOADS":
                return Environment.DIRECTORY_DOWNLOADS;
            case "MOVIES":
                return Environment.DIRECTORY_MOVIES;
            case "MUSIC":
                return Environment.DIRECTORY_MUSIC;
            case "NOTIFICATIONS":
                return Environment.DIRECTORY_NOTIFICATIONS;
            case "PICTURES":
                return Environment.DIRECTORY_PICTURES;
            case "PODCASTS":
                return Environment.DIRECTORY_PODCASTS;
            case "RINGTONES":
                return Environment.DIRECTORY_RINGTONES;
            default:
                throw new IllegalArgumentException("Invalid directory representation: " + name);
        }
    }
}


里边还有一个数组directories在string.xml文件里头:

<?xml version="1.0" encoding="UTF-8"?>
<resources>
    <string-array name="directories">
        <item>ALARMS</item>
        <item>DCIM</item>
        <item>DOCUMENTS</item>
        <item>DOWNLOADS</item>
        <item>MOVIES</item>
        <item>MUSIC</item>
        <item>NOTIFICATIONS</item>
        <item>PICTURES</item>
        <item>PODCASTS</item>
        <item>RINGTONES</item>
    </string-array>
</resources>


然后是适配器:DirectoryEntryAdapter.java,适配器的代码比较简单,我就不做解释了,主要使用到了RecyclerView这个新的控件,如果��不懂的,可以自己搜索一下,这里都是最简单的用法,一看就懂了。


package com.example.android.scopeddirectoryaccess;

import android.provider.DocumentsContract;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;

import java.util.ArrayList;
import java.util.List;

/**
 * 适配器
 */
public class DirectoryEntryAdapter extends RecyclerView.Adapter<DirectoryEntryAdapter.ViewHolder> {

    private List<DirectoryEntry> mDirectoryEntries;

    public DirectoryEntryAdapter() {
        this(new ArrayList<DirectoryEntry>());
    }

    public DirectoryEntryAdapter(List<DirectoryEntry> directoryEntries) {
        mDirectoryEntries = directoryEntries;
    }

    @Override
    public ViewHolder onCreateViewHolder(ViewGroup viewGroup, int viewType) {
        View v = LayoutInflater.from(viewGroup.getContext())
                .inflate(R.layout.directory_entry, viewGroup, false);
        return new ViewHolder(v);
    }

    @Override
    public void onBindViewHolder(ViewHolder viewHolder, final int position) {
        viewHolder.fileName.setText(mDirectoryEntries.get(position).fileName);
        viewHolder.mimeType.setText(mDirectoryEntries.get(position).mimeType);

        if (DocumentsContract.Document.MIME_TYPE_DIR
                .equals(mDirectoryEntries.get(position).mimeType)) {
            viewHolder.imageView.setImageResource(R.drawable.ic_directory_grey600_36dp);
        } else {
            viewHolder.imageView.setImageResource(R.drawable.ic_description_grey600_36dp);
        }
    }

    @Override
    public int getItemCount() {
        return mDirectoryEntries.size();
    }

    public void setDirectoryEntries(List<DirectoryEntry> directoryEntries) {
        mDirectoryEntries = directoryEntries;
    }

    public class ViewHolder extends RecyclerView.ViewHolder {

        public TextView fileName;
        public TextView mimeType;
        public ImageView imageView;

        public ViewHolder(View v) {
            super(v);
            fileName = (TextView) v.findViewById(R.id.textview_filename);
            mimeType = (TextView) v.findViewById(R.id.textview_mimetype);
            imageView = (ImageView) v.findViewById(R.id.imageview_entry);
        }
    }
}

然后是适配器的布局文件:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="@dimen/directory_item_height"
    android:layout_centerVertical="true"
    android:gravity="center_vertical"
    android:orientation="horizontal">

    <ImageView
        android:id="@+id/imageview_entry"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:src="@drawable/ic_directory_grey600_36dp" />

    <LinearLayout
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginLeft="@dimen/margin_medium"
        android:orientation="vertical">

        <View
            android:id="@+id/divisor"
            android:layout_width="match_parent"
            android:layout_height="1dp"
            android:background="#aaaaaa" />

        <TextView
            android:id="@+id/textview_filename"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textColor="#000000" />

        <TextView
            android:id="@+id/textview_mimetype"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content" />

    </LinearLayout>
</LinearLayout>

代码基本上就是这样子。

下面再给一个运行的图片




GitHub地址:点击打开链接
















  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值