# [ui初級入門教程(七)— 基于native.js的文件系統管理功能實現](https://www.cnblogs.com/PheonixHkbxoic/p/6013373.html)
## 前言
這段時間以來一直有人問5+ sdk怎么在原生中集成,每次給了文檔和沒給沒啥大區別,這部分人之所以不能根據文檔寫出想要的結果,無非有兩種情況,一種對于原生完全懵逼,畢竟基于mui做APP畢竟前端還是占多數,而前端中熟悉原生的人畢竟是少數,很多人聲稱會原生哪里還會用h5,這話只能呵呵?就大趨勢而言,應用web化是現在的潮流所向,現在即使有資金和技術實力的大廠也在做混合式開發和H5的APP,不然dcloud官方也不會花大力氣在流應用上。近來越來越多的原生開發者朋友和我交流h5,他們很多是被逼著轉前端,這些人懂原生,但是不懂h5,所以這些朋友從原生轉mui過程中可能還是不能夠理解如果用5+ sdk,中間的交互怎么解決,這就是第二種人的困境?
正是基于這種現實窘境,我打算把自己一知半解的android開發經驗重新拿起來,試著去寫點什么,拋磚引玉,僅此而已。本文作為混合式開發的第一篇,暫時不會介紹離線打包集成5+ sdk的相關內容,先用nativejs練練手。
## 步驟分解
在開始集成5+sdk之前,我們先來用native.js寫一個文件管理的功能,以此熟悉native.js的相關API。先來預覽一下效果:

經常有人問使用5+怎么系統文件,其實用nativejs就可以實現,有人又要問nativejs怎么引用。每次遇到這種問題,真的要噴血而出。
nativejs是集成在5+ app中,默認不需要引入就可以直接引用。nativejs是通過js調用系統原生方法,從而實現5+標準中沒有提及的方法,所以說白了你還是要會原生,或者有人用nativejs將原生的方法轉成了js,這樣你只需要在頁面中調用js的方法就可以實現調用原生。nativejs在這里充當一個“語法糖”的作用。
對于這樣一個遍歷文件系統的功能,用原生方法寫,我們會這樣寫:
### 1.在AndroidManifest.xml下設置權限
~~~
<!-- 允許程序寫入外部存儲,如SD卡上寫文件 -->
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"></uses-permission>
~~~
5+ APP中這一步默認設置了,我們不要管。
### 2.導入Java類對象
~~~
import android.os.Environment;
~~~
Native.js中使用[plus.android.importClass](http://www.html5plus.org/doc/zh_cn/android.html#plus.android.importClass)方法:
~~~
var environment = plus.android.importClass("android.os.Environment");
~~~
### 3.判斷SD卡是否插入(涉及到SDK的讀取,最好先判斷SDK是否插入)
~~~
Environment.getExternalStorageState().equals(android.os.Environment.MEDIA_MOUNTED)
~~~
nativejs這樣寫:
~~~
environment.getExternalStorageState() === environment.MEDIA_MOUNTED
~~~
### 4.獲得sd卡根目錄
~~~
File skRoot = Environment.getExternalStorageDirectory();
~~~
nativejs這樣寫:
~~~
var sdRoot = environment.getExternalStorageDirectory();
~~~
### 5.遍歷sd卡根目錄下的所有文件和文件夾(返回值為數組)
~~~
File[] files = sdRoot.listFiles();
~~~
nativejs中使[plus.android.invoke](http://www.html5plus.org/doc/zh_cn/android.html#plus.android.invoke)調用對象(類對象/實例對象)的方法:
~~~
var files = plus.android.invoke(sdRoot,"listFiles");
~~~
### 6.過濾系統隱藏文件
~~~
import java.io.File;
import java.io.FileFilter;
FileFilter ff = new FileFilter() {
public boolean accept(File pathname) {
return !pathname.isHidden();//過濾隱藏文件
}
};
File[] files = sdRoot.listFiles(ff);
~~~
nativejs中可以這樣寫:
~~~
// 遍歷sd卡根目錄下的所有文件和文件夾
var files = plus.android.invoke(sdRoot,"listFiles");
var len = files.length;
for(var i=0; i<len; i++){
var file = files[i];
// 過濾隱藏文件
if(!plus.android.invoke(file,"isHidden")){
// 非隱藏文件執行操作
}
}
~~~
上面的預覽效果可以看出我們對文件夾和文件進行了不同的操作,接著需要在遍歷中判斷。
### 7.文件及文件夾類型判斷
~~~
for(File file : files){
if(file.isDirectory()){
// 文件夾
}else{
// 文件
}
}
~~~
nativejs寫法:
~~~
for(var i=0;i<len;i++){
if(plus.android.invoke(file,"isDirectory")){
// 文件夾
}else{
// 文件
}
}
~~~
### 8.文件大小及單位轉換(這里直接上nativejs版本)
~~~
// 讀文件大小
var FileInputStream = plus.android.importClass("java.io.FileInputStream");
var fileSize = new FileInputStream(file);
var size = fileSize.available();
// 單位轉換
var fileSizeString;
if(size == 0){
fileSizeString = "0B";
}else if(size < 1024){
fileSizeString = size + "B";
}else if(size < 1048576){
fileSizeString = (size/1024).toFixed(2) + "KB";
}else if (size < 1073741824){
fileSizeString = (size/1048576).toFixed(2) + "MB";
}else{
fileSizeString = (size/1073741824).toFixed(2) + "GB";
}
~~~
### 9.創建文件夾與刪除文件(文件夾)
~~~
/**
* 創建文件夾
* @param {Object} path
*/
function creatFolder(path){
var File = plus.android.importClass("java.io.File");
var fd = new File(path);
if(!fd.exists()){
fd.mkdirs();
plus.nativeUI.toast("創建成功");
}
}
/**
* 刪除文件(文件夾)
* @param {Object} path
*/
function deleteFile(path){
var File = plus.android.importClass("java.io.File");
var fd = new File(path);
if (fd != null && fd.exists()){
fd.delete();
plus.nativeUI.toast("刪除成功");
}
}
~~~
### 10.打開目錄和打開文件
我們在打開目錄的時候,會遍歷該目錄下的文件夾和文件,實現方法同上面,打開文件我們可以使用5+ runtime openFile調用第三方程序打開指定的文件。
~~~
void plus.runtime.openFile( filepath, options,?errorCB );
~~~
**參數:**
* filepath: ( String ) 必選 打開文件的路徑字符串類型,文件路徑必須是本地路徑,否則會導致打開文件失敗。
* options: ( OpenFileOptions ) 可選 打開文件參數
* errorCB: ( OpenErrorCallback ) 必選 打開文件失敗的回調打開文件操作失敗時回調,返回失敗信息。
本文完整代碼:
~~~
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
<title></title>
<link rel="stylesheet" type="text/css" href="css/mui.min.css"/>
<link rel="stylesheet" type="text/css" href="css/circles.css"/>
<style type="text/css">
.loading {
position: absolute;
top: 50%;
left: 50%;
width: 200px;
height: 200px;
margin-top: -100px;
margin-left: -100px;
z-index: 9999;
}
.loading .card {
display: table-cell;
text-align: center;
vertical-align: middle;
width: 200px;
height: 200px;
}
.mui-backdrop {
background-color: #15b5e9;
}
</style>
</head>
<body>
<div class="loading">
<div class="card">
<span class="circles-loader">Loading…</span>
</div>
</div>
<header class="mui-bar mui-bar-nav">
<h1 class="mui-title">文件系統</h1>
<a id="add" class="mui-icon mui-icon-plus mui-pull-right"></a>
</header>
<div class="mui-content">
<ul id="list" class="mui-table-view mui-table-view-chevron"></ul>
</div>
<script src="js/mui.min.js" type="text/javascript" charset="utf-8"></script>
<script src="js/njs-io.js" type="text/javascript" charset="utf-8"></script>
<script type="text/javascript">
mui.init({
gestureConfig:{
longtap: true //默認為false
}
});
var mask;
mui.ready(function () {
mask = mui.createMask();//callback為用戶點擊蒙版時自動執行的回調;
mask.show();//顯示遮罩
})
var sdRoot = null;
document.addEventListener("plusready", function(){
init();
}, false);
// 渲染列表
function init(){
// 獲得sd卡根目錄
sdRoot = getSDRoot();
// 遍歷sd卡根目錄下的所有文件和文件夾
var files = plus.android.invoke(sdRoot,"listFiles");
var len = files.length;
var list = document.getElementById("list");
var fragmentFolder = document.createDocumentFragment();
var fragmentFile = document.createDocumentFragment();
var li;
for(var i=0;i<len;i++){
var file = files[i];
// 過濾隱藏文件
if(!plus.android.invoke(file,"isHidden")){
var name = plus.android.invoke(file,"getName");
li= document.createElement('li');
li.className = 'mui-table-view-cell mui-media';
li.setAttribute('name',name);
// 判斷是文件還是文件夾
if(plus.android.invoke(file,"isDirectory")){
// 設置標志為文件夾,供后面使用
li.setAttribute('data-type', 'Folder');
// 讀取文件夾下子文件夾及子文件數目
var obj = readSonFilenum(file);
li.innerHTML = '<a class="mui-navigate-right">'+
'<img class="mui-media-object mui-pull-left" src="img/folder.png">'+
'<div class="mui-media-body">'+ name +
'<p class="mui-ellipsis">文件夾數量:'+ obj.subFolderNum + ' 文件數量:'+ obj.subFileNum +'</p></div></a>';
fragmentFolder.appendChild(li);
}else{
li.setAttribute('data-type', 'File');
// 讀文件大小
var fileSizeString = readFileSize(file);
li.innerHTML = '<a class="mui-navigate-right">'+
'<img class="mui-media-object mui-pull-left" src="img/file.png">'+
'<div class="mui-media-body mui-ellipsis">'+ name +
'<p class="mui-ellipsis">'+ fileSizeString +'</p></div></a>';
fragmentFile.appendChild(li);
}
};
}
list.appendChild(fragmentFolder);
list.appendChild(fragmentFile);
// 關閉遮罩
mask.close();
document.querySelector('.loading').style.display = 'none';
}
// 點擊打開文件
mui('.mui-table-view').on('tap','li',function(){
var name = this.getAttribute('name');
var fileType = this.getAttribute('data-type');
var filepath = sdRoot + '/' + name;
if(fileType === 'Folder'){
// 打開目錄詳細頁面
mui.openWindow({
url: 'sub.html',
id: 'sub',
extras:{
name: name,
filepath: filepath
}
})
}else{
// 打開文件
plus.runtime.openFile(filepath);
}
})
// 創建文件夾
document.querySelector('#add').addEventListener('tap',function (e) {
e.detail.gesture.preventDefault(); //修復iOS 8.x平臺存在的bug,使用plus.nativeUI.prompt會造成輸入法閃一下又沒了
var btnArray = ['取消', '確定'];
mui.prompt('請輸入要創建的文件夾名:', '文件夾名', '創建文件夾', btnArray, function(e) {
if (e.index == 1) {
creatFolder(sdRoot+"/"+e.value);
} else {
plus.nativeUI.toast('你點了取消按鈕');
}
})
})
// 長按刪除文件
mui('.mui-table-view').on('longtap','li',function(){
var name = this.getAttribute('name');
var btnArray = ['取消', '確定'];
mui.confirm('你確定要刪除這個文件夾?', '刪除文件夾', btnArray, function(e) {
if (e.index == 1) {
deleteFile(sdRoot+"/"+name);
} else {
plus.nativeUI.toast('你點了取消按鈕');
}
})
})
</script>
</body>
</html>
~~~
## 原生文件操作
### 判斷SD卡是否插入
~~~
Environment.getExternalStorageState().equals(
android.os.Environment.MEDIA_MOUNTED);
~~~
### 獲得sd卡根目錄
~~~
File skRoot = Environment.getExternalStorageDirectory();
~~~
### 獲得私有根目錄
~~~
File fileRoot = Context.getFilesDir()+"\";
~~~
### 文件夾或文件夾操作
#### 獲得文件夾和文件路徑
* 獲得文件或文件夾的絕對路徑和相對路徑。區別:
~~~
String path = File.getPath();//相對
String path = File.getAbsoultePath();//絕對
~~~
* 獲得文件或文件夾的父目錄
~~~
String parentPath = File.getParent();
~~~
* 獲得文件或文件夾的名稱:
~~~
String Name = File.getName();
~~~
#### 建立文件或文件夾
~~~
File.mkDir(); //建立文件夾
File.createNewFile();//建立文件
~~~
#### 判斷是文件或文件夾
~~~
File.isDirectory()
File.isDirectory()
~~~
#### 列出文件夾下的所有文件和文件夾名
~~~
File[] files = File.listFiles();
~~~
#### 修改文件夾和文件名
~~~
File.renameTo(dest);
~~~
#### 刪除文件夾或文件
~~~
File.delete();
~~~
這里列出原生的常用方法,大家可以根據需要進行實現。
## 后記
寫到這里,本篇本應該繼續講講5+ sdk集成中的各種細節,但是想想本篇作為nativejs的開篇,先簡單介紹這個實例,讓大家根據需要自己實現或許更好,本文僅供參考。
文章最后還是要放上文檔和本文詳細工程:
> [Native.js android API文檔](http://www.html5plus.org/doc/zh_cn/android.html)
> [5+ App開發Native.js入門指南](http://ask.dcloud.net.cn/docs/#http://ask.dcloud.net.cn/article/88)
[Native.js示例匯總](http://ask.dcloud.net.cn/docs/#http://ask.dcloud.net.cn/article/114)
[本文詳細工程 njs-io](https://github.com/zhaomenghuan/mui-demo/tree/master/example/njs-io)
本人博客歡迎轉載!但請注明出處!本人博客若有侵犯他人之處,望見諒,請聯系我。希望互相關注,互相學習 --[PheonixHkbxoic](http://www.cnblogs.com/PheonixHkbxoic/)