1、喚出系統(tǒng)文件管理器
開啟文件上傳,可使用HTML5標(biāo)簽 <input type="file"> 喚出系統(tǒng)文件管理器或自定義文件管理器,然后選擇文件。
MainActivity.java:
private WebView webView;
private WVChromeClient wv = null;
@Override
protected void onCreate(Bundle savedInstanceState) {
setContentView(R.layout.activity_main);
webView = (WebView) findViewById(R.id.wv_webview);
WebSettings settings = webView.getSettings();
settings.setUseWideViewPort(true);
settings.setJavaScriptEnabled(true);
wv = new WVChromeClient(this,MainActivity.this);
webView.setWebChromeClient(wv);
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == WVChromeClient.CHOOSER_REQUEST) { // 處理返回的文件
wv.onActivityResultFileChooser(requestCode, resultCode, data); // 調(diào)用 WVChromeClient 類中的 回調(diào)方法
}
}
WVChromeClient.java:
public class WVChromeClient extends WebChromeClient {
private static final String TAG = "WebChromeClient:";
public final static int CHOOSER_REQUEST = 0x33;
private ValueCallback<Uri[]> uploadFiles = null;
Context context;
MainActivity _m;
public WVChromeClient(Context _context, MainActivity mainActivity)
{
context = _context;
_m = mainActivity;
}
// 第一種方式
@Override
public boolean onShowFileChooser(WebView webView, ValueCallback<Uri[]> filePathCallback,
FileChooserParams fileChooserParams) {
uploadFiles = filePathCallback;
Intent i = fileChooserParams.createIntent();
i.addCategory(Intent.CATEGORY_OPENABLE);
i.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true); // 設(shè)置多選
_m.startActivityForResult(Intent.createChooser(i, "Image Chooser"), CHOOSER_REQUEST);
return true;
}
// 第二種方式(過濾文件格式)
@Override
public boolean onShowFileChooser(WebView webView, ValueCallback<Uri[]> filePathCallback,
FileChooserParams fileChooserParams) {
uploadFiles = filePathCallback;
Intent i = new Intent(Intent.ACTION_GET_CONTENT);
i.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true);
i.setType("*/*"); // 設(shè)置文件類型
String[] mimeTypes = { "image/*,audio/*,video/*,*/*" };
i.putExtra(Intent.EXTRA_MIME_TYPES, mimeTypes); // 設(shè)置多種類型
i.addCategory(Intent.CATEGORY_OPENABLE);
_m.startActivityForResult(Intent.createChooser(i, "Image Chooser"), CHOOSER_REQUEST);
return true;
}
// 文件選擇回調(diào)(在 MainActivity.java 的 onActivityResult中調(diào)用此方法)
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
public void onActivityResultFileChooser(int requestCode, int resultCode, Intent intent) {
if (requestCode != CHOOSER_REQUEST || uploadFiles == null)
return;
Uri[] results = null;
if (resultCode == Activity.RESULT_OK) {
if (intent != null) {
String dataString = intent.getDataString();
ClipData clipData = intent.getClipData();
if (clipData != null) {
results = new Uri[clipData.getItemCount()];
for (int i = 0; i < clipData.getItemCount(); i++) {
ClipData.Item item = clipData.getItemAt(i);
results[i] = item.getUri();
}
}
if (dataString != null)
results = new Uri[]{Uri.parse(dataString)};
}
}
uploadFiles.onReceiveValue(results);
uploadFiles = null;
}
}
2、喚出自定義文件管理器
2.1 使用第三方插件
這里使用 AndroidFilePicker 插件可自定義文件管理器,見詳細(xì)使用
(1)添加依賴
在項(xiàng)目 build.gradle 配置文件添加倉庫:
allprojects {
repositories {
...
maven { url 'https://jitpack.io' }
}
}
在子模塊(app)的配置文件添加依賴:
dependencies {
implementation 'me.rosuh:AndroidFilePicker:0.8.2'
}
此庫需要一個權(quán)限:
android.permission.READ_EXTERNAL_STORAGE
如果您沒有提前授予,這個庫會自動申請?jiān)摍?quán)限的。
修改上文 WVChromeClient 類中的 onShowFileChooser() 方法:
private ValueCallback<Uri[]> uploadFiles = null;
// 重寫選擇文件
@Override
public boolean onShowFileChooser(WebView webView, ValueCallback<Uri[]> filePathCallback,
FileChooserParams fileChooserParams) {
uploadFiles = filePathCallback;
String uDiskUrl = getUDisk(); // 檢測U盤
if(uDiskUrl != null) {
showSingleAlertDialog(uDiskUrl); // 彈出選擇框
} else {
showFilePickerManager(""); // 直接打開本地路徑,若無需支持U盤則可以直接調(diào)用此方法喚出自定義文件管理器
}
return true;
}
// 判斷是否存在U盤
private String getUDisk() {
String path = "/mnt/usb/"; // u盤路徑
File storage = new File(path);
File[] files = storage.listFiles();
if(files != null && files.length != 0) {
return path + files[0].getName() + "/";
}
return null;
}
// 打開文件管理器
private void showFilePickerManager(String path) {
FilePickerManager
.from((Activity) context) // context 為上文實(shí)例化 WVChromeClient 類時傳入
.setCustomRootPath(path)
.forResult(CHOOSER_REQUEST);
}
private int checkedItem = 0;
private boolean isNotOK = true;
// 選擇框
private void showSingleAlertDialog(String path) {
String[] items = {"本地存儲", "U盤"};
AlertDialog.Builder alertBuilder = new AlertDialog.Builder(context);
alertBuilder.setTitle("請選擇");
alertBuilder.setSingleChoiceItems(items, 0, (dialogInterface, i) -> {
checkedItem = i;
});
alertBuilder.setPositiveButton("確定", (dialogInterface, i) -> {
isNotOK = false;
dialogInterface.dismiss();
String paths = "";
if(checkedItem == 1) {
paths = path; // 當(dāng)前選擇U盤,默認(rèn)為本地存儲
}
showFilePickerManager(paths);
});
alertBuilder.setNegativeButton("取消", (dialogInterface, i) -> dialogInterface.dismiss());
alertBuilder.setOnDismissListener(dialog -> {
if(isNotOK) { // 若沒有選擇確定按鈕則取消文件上傳
uploadFiles.onReceiveValue(null);
uploadFiles = null;
}
});
alertBuilder.show();
}
// 文件選擇回調(diào)
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
public void onActivityResultFileChooser(int requestCode, int resultCode, Intent intent) {
if (requestCode != Config.CHOOSER_REQUEST_CODE || uploadFiles == null)
return;
Uri[] results = null;
if (resultCode == Activity.RESULT_OK) {
List<String> list = FilePickerManager.obtainData(); // 取到選擇的文件列表
results = new Uri[list.size()];
for (int i = 0; i < list.size(); i++) {
String item = list.get(i);
Uri uri = getUriForFile(new File(item));
results[i] = uri;
}
}
uploadFiles.onReceiveValue(results);
uploadFiles = null;
isNotOK = true;
}
// File 轉(zhuǎn) Uri
private Uri getUriForFile(File file) {
String packageName = getPackage(context).packageName;
Uri contentUri = FileProvider.getUriForFile(context,packageName+".fileProvider", file); // 需要 FileProvider,詳細(xì)使用見下文
return contentUri;
}
// 獲取當(dāng)前包名
public static PackageInfo getPackage(Context context) {
PackageManager manager = context.getPackageManager();
try {
PackageInfo info = manager.getPackageInfo(context.getPackageName(), 0);
return info;
} catch (PackageManager.NameNotFoundException e) {
e.printStackTrace();
return null;
}
}
FileProvider 使用詳見
2.2 手寫文件管理頁面
手寫文件管理頁面有兩個個步驟:獲取文件列表、展示文件列表
(1)獲取文件列表
按一般文件管理器大致有幾個目錄:文檔、音頻、視頻、圖片、下載、所有文件目錄
public class FileManage {
private Context _c;
private static final String TAG = "FileManage:";
public FileManage(Context context) {
_c = context;
}
// 獲取視頻
public JSONObject getVideos() { // 這里返回一個 JSONObject,返回格式可以自行定義
Cursor c = null;
JSONArray array = new JSONArray();
try {
Log.e(TAG,MediaStore.Video.Media.EXTERNAL_CONTENT_URI.toString());
c = _c.getContentResolver().query(MediaStore.Video.Media.EXTERNAL_CONTENT_URI, null, null, null, MediaStore.Video.Media.DEFAULT_SORT_ORDER);
while (c.moveToNext()) {
String path = c.getString(c.getColumnIndexOrThrow(MediaStore.Video.Media.DATA));// 路徑
if (!new File(path).exists()) {
continue;
}
int id = c.getInt(c.getColumnIndexOrThrow(MediaStore.Video.Media._ID));// 視頻的id
String name = c.getString(c.getColumnIndexOrThrow(MediaStore.Video.Media.DISPLAY_NAME)); // 視頻名稱
String resolution = c.getString(c.getColumnIndexOrThrow(MediaStore.Video.Media.RESOLUTION)); //分辨率
long size = c.getLong(c.getColumnIndexOrThrow(MediaStore.Video.Media.SIZE));// 大小
long duration = c.getLong(c.getColumnIndexOrThrow(MediaStore.Video.Media.DURATION));// 時長
long date = c.getLong(c.getColumnIndexOrThrow(MediaStore.Video.Media.DATE_MODIFIED));//修改時間
JSONObject obj = new JSONObject();
obj.put("id",id);
obj.put("name",name);
obj.put("url",path);
obj.put("resolution",resolution);
obj.put("size",size);
obj.put("duration",duration);
obj.put("time",getDateToString(date*1000));
obj.put("timestamp", date*1000);
array.put(obj);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
if (c != null) {
c.close();
}
}
File file = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_MOVIES);
JSONObject obj = new JSONObject();
try {
obj.put("name","視頻");
obj.put("svg","#icon-file_video");
obj.put("url", file.getPath());
if(array != null && array.length() != 0) {
obj.put("child", array);
}
} catch (JSONException e) {
e.printStackTrace();
}
return obj;
}
// 獲取音頻
public JSONObject getMusics() {
Cursor c = null;
JSONArray array = new JSONArray();
try {
c = _c.getContentResolver().query(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, null, null, null,
MediaStore.Audio.Media.DEFAULT_SORT_ORDER);
while (c.moveToNext()) {
String path = c.getString(c.getColumnIndexOrThrow(MediaStore.Audio.Media.DATA));// 路徑
if (!new File(path).exists()) {
continue;
}
int id = c.getInt(c.getColumnIndexOrThrow(MediaStore.Audio.Media._ID)); // 歌曲的id
String name = c.getString(c.getColumnIndexOrThrow(MediaStore.Audio.Media.DISPLAY_NAME)); // 歌曲名
String album = c.getString(c.getColumnIndexOrThrow(MediaStore.Audio.Media.ALBUM)); // 專輯
String artist = c.getString(c.getColumnIndexOrThrow(MediaStore.Audio.Media.ARTIST)); // 作者
long size = c.getLong(c.getColumnIndexOrThrow(MediaStore.Audio.Media.SIZE));// 大小
int duration = c.getInt(c.getColumnIndexOrThrow(MediaStore.Audio.Media.DURATION));// 時長
long date = c.getLong(c.getColumnIndexOrThrow(MediaStore.Audio.Media.DATE_MODIFIED));//修改時間
int albumId = c.getInt(c.getColumnIndexOrThrow(MediaStore.Audio.Media.ALBUM_ID));
JSONObject obj = new JSONObject();
obj.put("id",id);
obj.put("name",name);
obj.put("url",path);
obj.put("album",album);
obj.put("artist",artist);
obj.put("size",size);
obj.put("duration",duration);
obj.put("time",getDateToString(date*1000));
obj.put("timestamp", date*1000);
obj.put("albumId",albumId);
array.put(obj);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
if (c != null) {
c.close();
}
}
File file = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_MUSIC);
JSONObject obj = new JSONObject();
try {
obj.put("name","音頻");
obj.put("svg","#icon-file_music");
obj.put("url", file.getPath());
if(array != null && array.length() != 0) {
obj.put("child", array);
}
} catch (JSONException e) {
e.printStackTrace();
}
return obj;
}
// 獲取圖片
public JSONObject getImages() {
// 掃描圖片
Cursor c = null;
JSONArray array = new JSONArray();
try {
c = _c.getContentResolver().query(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, null,
MediaStore.Images.Media.MIME_TYPE + "= ? or " + MediaStore.Images.Media.MIME_TYPE + "= ?",
new String[]{"image/jpeg", "image/png"}, MediaStore.Images.Media.DATE_MODIFIED);
while (c.moveToNext()) {
String path = c.getString(c.getColumnIndexOrThrow(MediaStore.Images.Media.DATA));// 路徑
// @SuppressLint("Range") String path = c.getString(c.getColumnIndex(MediaStore.Images.Media.DATA));// 路徑
File parentFile = new File(path).getParentFile();
if (parentFile == null)
continue;
int id = c.getInt(c.getColumnIndexOrThrow(MediaStore.Images.Media._ID)); // 圖片的id
String name = c.getString(c.getColumnIndexOrThrow(MediaStore.Images.Media.DISPLAY_NAME)); // 圖片名
long size = c.getLong(c.getColumnIndexOrThrow(MediaStore.Images.Media.SIZE));// 大小
long date = c.getLong(c.getColumnIndexOrThrow(MediaStore.Images.Media.DATE_MODIFIED));//修改時間
JSONObject obj = new JSONObject();
obj.put("id",id);
obj.put("name",name);
obj.put("url",path);
obj.put("size",size);
obj.put("time",getDateToString(date*1000));
obj.put("timestamp", date*1000);
array.put(obj);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
if (c != null) {
c.close();
}
}
File file = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES);
JSONObject obj = new JSONObject();
try {
obj.put("name","圖片");
obj.put("svg","#icon-file_img");
obj.put("url", file.getPath());
if(array != null && array.length() != 0) {
obj.put("child", array);
}
} catch (JSONException e) {
e.printStackTrace();
}
return obj;
}
// 獲取文檔
public JSONObject getDocuments() {
File file = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOCUMENTS);
JSONObject obj = getFileList(file);
try {
obj.put("name","文檔");
obj.put("svg","#icon-file1");
obj.put("isFilter",true);
} catch (JSONException e) {
e.printStackTrace();
}
return obj;
}
// 獲取下載
public JSONObject getDownloads() {
File file = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS);
JSONObject obj = getFileList(file);
try {
obj.put("name","下載");
obj.put("svg","#icon-file_download");
obj.put("isFilter",true);
} catch (JSONException e) {
e.printStackTrace();
}
return obj;
}
// 獲取根文件
public JSONObject getRoots() {
File file = Environment.getExternalStorageDirectory();
return getFileList(file);
}
// 獲取本地文件
public JSONObject getLocalStore() {
JSONObject obj = getRoots();
try {
obj.put("name","本地");
JSONArray array = new JSONArray();
array.put(getDocuments());
array.put(getMusics());
array.put(getVideos());
array.put(getImages());
array.put(getDownloads());
obj.put("typeList", array);
} catch (JSONException e) {
e.printStackTrace();
}
return obj;
}
// 獲取U盤文件
public JSONObject getDiskFiles() {
JSONObject obj = null;
String path = "/mnt/usb/";
File file = new File(path);
File[] files = file.listFiles();
if(files != null && files.length != 0) {
obj = getFileList(file);
try {
obj.put("name","U盤");
} catch (JSONException e) {
e.printStackTrace();
}
}
return obj;
}
// 獲取目錄文件下所有文件列表
private JSONObject getFileList(File file) {
JSONObject message = new JSONObject();
try {
if(file.exists()){
message.put("name", file.getName());
message.put("url", file.getPath());
message.put("timestamp", file.lastModified());
message.put("time", getDateToString(file.lastModified()));
message.put("size", file.length());
File[] list = file.listFiles();
if(list != null && list.length != 0) {
List fileList = Arrays.asList(list);
Collections.sort(fileList, (Comparator<File>) (o1, o2) -> {
if (o1.isDirectory() && o2.isFile())
return -1;
if (o1.isFile() && o2.isDirectory())
return 1;
return o1.getName().compareTo(o2.getName());
});
JSONArray arr = new JSONArray();
for(File item : list) {
arr.put(getFileList(item));
}
message.put("child", arr);
} else {
if(file.isDirectory()) {
message.put("child",new JSONArray());
}
}
}
} catch (JSONException e) {
e.printStackTrace();
}
return message;
}
// File 轉(zhuǎn) Uri
public Uri getUriForFile(File file) {
String packageName = GetDevice.getPackage(_c).packageName;
Uri contentUri = FileProvider.getUriForFile(_c,packageName+".fileProvider", file);
return contentUri;
}
private String getDateToString(long milSecond) {
String pattern = "yyyy-MM-dd HH:mm:ss";
Date date = new Date(milSecond);
SimpleDateFormat format = new SimpleDateFormat(pattern);
return format.format(date);
}
// 獲取視頻縮略圖
public Bitmap getVideoThumbnail(int id) {
Bitmap bitmap = null;
BitmapFactory.Options options = new BitmapFactory.Options();
options.inDither = false;
options.inPreferredConfig = Bitmap.Config.ARGB_8888;
bitmap = MediaStore.Video.Thumbnails.getThumbnail(_c.getContentResolver(), id, MediaStore.Images.Thumbnails.MICRO_KIND, options);
return bitmap;
}
}
(2)展示文件列表
通過上面的 FileManage 里面的方法就可以獲取到 Android 系統(tǒng)里面大部分文件列表了
展示可以使用 Android 的 Activity 布局展示,這里使用的是 H5 寫的文件管理,實(shí)現(xiàn)邏輯一致(取到文件列表->展示->選擇文件->得到文件URI)。
在 WChromeClient.java 中編寫
private ValueCallback<Uri[]> uploadFiles = null;
// 重寫選擇文件
@Override
public boolean onShowFileChooser(WebView webView, ValueCallback<Uri[]> filePathCallback,
FileChooserParams fileChooserParams) {
uploadFiles = filePathCallback; // 取到 filePathCallback 回調(diào)之后不做處理
return true;
}
// 文件選擇回調(diào)(這個方法提供給js調(diào)用)
public void resultFileChoose(String json) { // 參數(shù)是前端js選擇了一項(xiàng)或多項(xiàng)的列表
Uri[] results = null;
try {
JSONArray jsonArray = new JSONArray(json);
results = new Uri[jsonArray.length()];
for(int i=0; i<jsonArray.length(); i++) {
JSONObject obj = jsonArray.getJSONObject(i);
String url = obj.getString("url");
File file = new File(url);
results[i] = getUriForFile(file);
}
} catch (Exception e) {
e.printStackTrace();
}
receiveFile(results);
}
public void receiveFile(Uri[] results) {
uploadFiles.onReceiveValue(results);
uploadFiles = null;
}
提供給js的方法:
FileManage fileManage = new FileManage(context); // context 就是 MainActivity 的 this
// 獲取本地文件列表
@JavascriptInterface
public String getLocalStore() {
String str = fileManage.getLocalStore().toString();
return str;
}
// 獲取U盤文件列表
@JavascriptInterface
public String getDiskFiles() {
JSONObject obj = fileManage.getDiskFiles();
if(obj!=null) {
String str = fileManage.getDiskFiles().toString();
return str;
}else{
return "null";
}
}
// 選擇文件
@JavascriptInterface
public void getFileList(String json) {
wChromeClient.resultFileChoose(json); // resultFileChoose 就是前面 WChromeClient.java 里面的
}
// 取消選擇(注意取消選擇文件必須置空回調(diào))
@JavascriptInterface
public void cancelFile() { wChromeClient.receiveFile(null); }
js如何調(diào)用:
// 獲取設(shè)備根目錄
toAndroid({ methods: 'getLocalStore' }, val => {
const json = JSON.parse(val);
this.fileList.push(json); // 文件列表
});
// 獲取設(shè)備U盤目錄
toAndroid({ methods: 'getDiskFiles' }, val => {
if (val !== 'null') {
const json = JSON.parse(val);
this.fileList.push(json);
}
});
// 調(diào)用安卓方法
export default function toAndroid(infor, success, error) {
try {
const { methods, params } = infor;
let data = null;
params
? (data = window.jsWebView[methods](params))
: (data = window.jsWebView[methods]());
success && success(data);
} catch (e) {
error && error();
}
}