Android網(wǎng)絡(luò)與數(shù)據(jù)存儲——File存儲(實現(xiàn)SD卡文件瀏覽器)

Java提供了一套完整的IO流體系,包括FileInputStream、FileOutPutStream等,通過這些IO流可以非常方便的訪問磁盤上的文件內(nèi)容。Android同樣支持以這種方式來訪問手機存儲器上的文件。

一.存儲在內(nèi)部還是外部?


AndroidManifest.xml中manifest標(biāo)簽下有一個屬性android:installLocation,用于指定應(yīng)用程序安裝在什么地方,該屬性有三個可選值:

  • auto:程序可能被安裝在外部存儲器上,例如SD卡;但是默認會被安裝到手機內(nèi)存中。當(dāng)手機內(nèi)存為空時,程序?qū)⒈话惭b到外部存儲器上;當(dāng)程序安裝到手機上后,用戶可以決定把程序放在外部存儲器還是內(nèi)存中。
  • internalOnly:默認值,程序只能被安裝在內(nèi)存中,如果內(nèi)存為空,程序則不能成功被安裝。
  • preferExternal:將程序安裝在外部存儲器,但是系統(tǒng)不保證程序一定會被安裝到外部存儲器上。當(dāng)外部存儲器不可以安裝或為空時,程序?qū)⒈话惭b到內(nèi)存中。當(dāng)程序使用了forward-locking機制時也將被安裝到內(nèi)存中,因為外部存儲不支持此機制。程序安裝后,用戶可以自由切換程序應(yīng)該在外部還是內(nèi)部存儲器上。

獲取External存儲的權(quán)限:

<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>

二.openFileInput和openFileOutput


Context提供了兩個方法打開應(yīng)用程序的數(shù)據(jù)文件夾里的文件IO流:

  • FileInputStream openFileInput(String name):打開應(yīng)用程序的數(shù)據(jù)文件夾下的name文件對應(yīng)的輸入流。
  • FileOutputStream openFileOutput(String name, int mode):打開應(yīng)用程序的數(shù)據(jù)文件夾下的name文件對應(yīng)的輸出流。第二個參數(shù)指定打開文件的模式,該模式支持如下值:
    • MODE_PRIVATE:該文件只能被當(dāng)前程序讀寫。
    • MODE_APPEND:以追加方式打開該文件,應(yīng)用程序可以向該文件中追加內(nèi)容。
    • MODE_WORLD_READABLE:該文件的內(nèi)容可以被其他程序讀取。
    • MODE_WORLD_WRITEABLE:該文件的內(nèi)容可以由其他程序讀寫。

Context還提供了訪問應(yīng)用程序的數(shù)據(jù)文件夾的方法:

  • getDir(String name, int mode):在應(yīng)用程序的數(shù)據(jù)文件夾下獲取或創(chuàng)建name對應(yīng)的子目錄。
  • File getFileDir():獲取應(yīng)用程序的數(shù)據(jù)文件夾的絕對路徑。
  • String[] fileList():返回應(yīng)用程序的數(shù)據(jù)文件夾下的全部文件。
  • deleteFile(String):刪除應(yīng)用程序的數(shù)據(jù)文件夾下的指定文件。

三.讀寫SD卡上的文件


為了更好的存取應(yīng)用程序的大文件數(shù)據(jù),應(yīng)用程序需要讀寫SD卡上的文件。

讀寫SD卡上文件的步驟:

  1. 調(diào)用Environment的getExternalStorageState()方法判斷手機上是否插入了SD卡,并且應(yīng)用程序具有讀寫SD卡的權(quán)限。使用如下代碼:
//如果返回true,說明已插入SD卡,且應(yīng)用程序具有讀寫SD卡的能力
Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED) 
  1. 調(diào)用Environment的getExternalStorageDirectory()方法來獲取外部存儲器,也就是SD卡的目錄。
  2. 使用FileInputStream、FileOutputStream、FileReader或FileWriter讀寫SD卡里的文件。

為了讀寫SD卡上的數(shù)據(jù),必須在AndroidManifest.xml中添加讀寫SD卡的權(quán)限:

<!-- 在SD卡中創(chuàng)建于刪除文件權(quán)限 -->
<uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS"/>
<!-- 向SD卡中寫入數(shù)據(jù)權(quán)限 -->
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>

四.操作assets、raw、res目錄下文件


1.assets

資源文件夾,在main下與res同級,與res不同的是,該目錄下的資源文件在打包apk時,會按原格式一并被打包。

有三種使用方法:

  • 在assets下放一個test.html文件,加載該文件:

webView.loadUrl("file:///android_asset/test.html");//假設(shè)已經(jīng)創(chuàng)建了一個WebView實例```

  • 同樣是讀取test.html文件:

//這里的open只能打開文件,不能打開文件夾
InputStream inputStream = getResource().getAssets().open("test.html");

- 讀取列表、讀取圖片、讀音樂,assets目錄下包含一個images目錄和一個mp3文件xuwei.mp3,images目錄中包含一張圖片dog.jpg:

String[] fileNames = getAssets().list("images/");//讀列表

InputStream inputStream = getAssets().open("images/dog.jpg");//讀圖片
Bitmap bitmap = BitmapFactory.decodeStream(inputStream);
imageView.setImageBitmap(bitmap);

AssetFileDescriptor assetFileDescriptor = getAssets().openFd("xuwei.mp3");//得到asset文件描述符
player.reset();//假設(shè)已創(chuàng)建一個MediaPlayer實例
player.setDataResource(assetFileDescriptor.getFileDescriptor(), assetFileDescriptor.getStartOffset(), assetFileDescriptor.getLength());
player.prepare();
player.start();


### 2.raw
資源文件夾,在res目錄下,系統(tǒng)會為res目錄下的所有資源生成相應(yīng)的資源ID,raw中的文件也不例外,所以可以通過ID去訪問res/raw目錄中的任何文件,而assets目錄中的文件就需要借助AssetManager去訪問了。

assets目錄允許下面有多級子目錄,而res/raw下不允許存在目錄結(jié)構(gòu)。

讀raw下的xuwei.mp3文件:

InputStream is = getResources().openRawResource(R.raw.xuwei);


### 3.res
res目錄下的文件都可用getResources()方法讀取。

# 五.SD卡文件瀏覽器
***
利用Java的File類開發(fā)一個SD卡文件瀏覽器,通過Environment.getExternalStorageDirectory()訪問系統(tǒng)的SD卡目錄,然后通過File的listFiles()方法獲取指定目錄下的全部文件和文件夾。

布局文件如下:

activity_main.xml
```xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
  xmlns:tools="http://schemas.android.com/tools"
  android:layout_width="match_parent"
  android:layout_height="match_parent"
  tools:context="com.trampcr.sdfileexplorer.MainActivity">

  <TextView
      android:id="@+id/path"
      android:layout_width="match_parent"
      android:layout_height="wrap_content"
      android:layout_alignParentTop="true"
      android:gravity="center_horizontal" />

  <ListView
      android:id="@+id/list"
      android:layout_width="wrap_content"
      android:layout_height="wrap_content"
      android:layout_below="@id/path" />

  <Button
      android:id="@+id/parent"
      android:layout_width="38dp"
      android:layout_height="34dp"
      android:layout_alignParentBottom="true"
      android:layout_centerHorizontal="true"
      android:background="@drawable/home" />

</RelativeLayout>

布局文件包含一個TextView用于顯示當(dāng)前路徑,ListView顯示當(dāng)前目錄下文件和文件夾,Button用于返回上一級目錄。

ListView中的子布局,包含一個ImageView和一個TextView:

line.xml:

<?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="50dp"
    android:orientation="horizontal"
    android:gravity="center_vertical">

    <ImageView
        android:id="@+id/icon"
        android:layout_width="30dp"
        android:layout_height="30dp"
        android:background="@drawable/folder"/>

    <TextView
        android:id="@+id/file_name"
        android:layout_marginLeft="20dp"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="abc"/>

</LinearLayout>

主程序代碼如下:

public class MainActivity extends AppCompatActivity {

    private ListView mListView;
    private TextView mTextView;
    //記錄當(dāng)前的父文件夾
    private File mCurrentParent;
    //記錄當(dāng)前路徑下的所有文件的文件數(shù)組
    File[] mCurrentFiles;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        mListView = (ListView) findViewById(R.id.list);
        mTextView = (TextView) findViewById(R.id.path);
        //獲取系統(tǒng)的SD卡的目錄
        File root = new File(String.valueOf(Environment.getExternalStorageDirectory()));
        //如果SD卡存在
        if (root.exists()){
            mCurrentParent = root;
            mCurrentFiles = root.listFiles();
            //使用當(dāng)前目錄下的全部文件、文件夾來填充ListView
            inflateListView(mCurrentFiles);
        }

        mListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
            @Override
            public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
                if (mCurrentFiles[position].isFile()){
                    return;
                }
                File[] tmp = mCurrentFiles[position].listFiles();
                if (tmp == null || tmp.length == 0){
                    Toast.makeText(MainActivity.this, "當(dāng)前路徑不可訪問或該路徑下沒有文件", Toast.LENGTH_SHORT).show();
                }else {
                    mCurrentParent = mCurrentFiles[position];
                    mCurrentFiles = tmp;
                    inflateListView(mCurrentFiles);
                }
            }
        });
        //獲取上一級目錄的按鈕
        Button parent = (Button) findViewById(R.id.parent);
        parent.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                try {
                    if (!mCurrentParent.getCanonicalFile().equals("/mnt/shell/emulated/0")){
                        mCurrentParent = mCurrentParent.getParentFile();
                        mCurrentFiles = mCurrentParent.listFiles();
                        inflateListView(mCurrentFiles);
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        });
    }


    private void inflateListView(File[] files) {
        //創(chuàng)建一個List集合,List集合的元素是Map
        List<Map<String, Object>> listItems = new ArrayList<>();
        for (int i = 0; i < files.length; i++) {
            Map<String, Object> listItem = new HashMap<>();
            if (files[i].isDirectory()){
                listItem.put("icon", R.drawable.folder);
            }else {
                listItem.put("icon", R.drawable.file);
            }
            listItem.put("fileName", files[i].getName());
            listItems.add(listItem);
        }
        //創(chuàng)建一個SimpleAdapter
        SimpleAdapter simpleAdapter = new SimpleAdapter(this, listItems, R.layout.line, new String[]{"icon", "fileName"}, new int[]{R.id.icon, R.id.file_name});
        mListView.setAdapter(simpleAdapter);
        try {
            mTextView.setText("當(dāng)前路徑為:" + mCurrentParent.getCanonicalPath());
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

使用File[]數(shù)組填充ListView,填充是程序會根據(jù)File[]數(shù)組里的數(shù)據(jù)元素代表的是文件還是文件夾來選擇使用文件圖標(biāo)或文件夾圖標(biāo)。

運行上面程序,可以看到:

SDExplorer.png

六.擴展學(xué)習(xí)


  • android-sdk中的samples
  • github/google samples
  • developers
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

推薦閱讀更多精彩內(nèi)容