feat: 添加文件选择和相机拍照功能
主要功能: 1. 支持WebView中选择文件(任意类型) 2. 支持选择图片(相册+相机) 3. 支持直接调用相机拍照 4. 自动处理相机和存储权限请求 新增文件: - FileChooserHelper.java - 文件选择和相机功能核心工具类 - CameraHelper.java - 直接调用相机的JavaScript接口 - FileTestActivity.java - 文件选择测试Activity - file_paths.xml - FileProvider配置文件 - file_test.html - 功能测试页面(ES5语法兼容老版本) - INTEGRATION_GUIDE.md - 接入指南文档 - FILE_CHOOSER_USAGE.md - 详细使用文档 修改文件: - AndroidManifest.xml - 添加相机和存储权限,注册FileProvider和FileTestActivity - MainActivity.java - 集成FileChooserHelper,添加WebChromeClient支持文件选择 - menu_main.xml - 添加"文件测试"菜单项 - strings.xml - 添加相关字符串资源 技术特性: - 支持Android 9+(API 28+) - 适配Android 10+分区存储 - 适配Android 13+新媒体权限 - 权限授予后自动重新打开文件选择器 - 使用ES5语法兼容老版本WebView - 支持FileProvider安全文件共享 使用方式: 在WebView加载的HTML页面中使用标准input标签: <input type="file" accept="image/*" capture="environment"> 版本:2.15
This commit is contained in:
@ -11,7 +11,7 @@ android {
|
|||||||
minSdk 28
|
minSdk 28
|
||||||
targetSdk 28
|
targetSdk 28
|
||||||
versionCode 1
|
versionCode 1
|
||||||
versionName "2.14"
|
versionName "2.15"
|
||||||
|
|
||||||
// 1.0 IDATA广播模式处理
|
// 1.0 IDATA广播模式处理
|
||||||
// 1.1 霍尼韦尔的监听修改(扫描网站二维码跳出程序,监听失效,调整)、斑马PDA广播模式设置
|
// 1.1 霍尼韦尔的监听修改(扫描网站二维码跳出程序,监听失效,调整)、斑马PDA广播模式设置
|
||||||
@ -50,6 +50,7 @@ android {
|
|||||||
// 2.13 取消监听旋转角度,使用系统自带的旋转(根据配置初始化,旋转方向:横、竖、随意)
|
// 2.13 取消监听旋转角度,使用系统自带的旋转(根据配置初始化,旋转方向:横、竖、随意)
|
||||||
// 瑞芯适配器 接入 新的型号,使用的是 ttyS8;而不是ttyS1;并且只有一个接口。
|
// 瑞芯适配器 接入 新的型号,使用的是 ttyS8;而不是ttyS1;并且只有一个接口。
|
||||||
// 2.14 适配 AIFUU 陈安良:陆军特色中心医院
|
// 2.14 适配 AIFUU 陈安良:陆军特色中心医院
|
||||||
|
// 2.15 添加文件选择和相机拍照功能
|
||||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||||
ndk {
|
ndk {
|
||||||
abiFilters 'armeabi-v7a'
|
abiFilters 'armeabi-v7a'
|
||||||
|
|||||||
@ -14,6 +14,12 @@
|
|||||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
||||||
<uses-permission android:name="com.honeywell.decode.permission.DECODE" />
|
<uses-permission android:name="com.honeywell.decode.permission.DECODE" />
|
||||||
<uses-permission android:name="android.permission.STOP_SERVICE" />
|
<uses-permission android:name="android.permission.STOP_SERVICE" />
|
||||||
|
<uses-permission android:name="android.permission.CAMERA" />
|
||||||
|
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
|
||||||
|
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
|
||||||
|
|
||||||
|
<uses-feature android:name="android.hardware.camera" android:required="false" />
|
||||||
|
<uses-feature android:name="android.hardware.camera.autofocus" android:required="false" />
|
||||||
|
|
||||||
<application
|
<application
|
||||||
android:allowBackup="true"
|
android:allowBackup="true"
|
||||||
@ -32,6 +38,9 @@
|
|||||||
<activity
|
<activity
|
||||||
android:name=".activity.VoiceSettingActivity"
|
android:name=".activity.VoiceSettingActivity"
|
||||||
android:label="@string/title_activity_setting_voice" />
|
android:label="@string/title_activity_setting_voice" />
|
||||||
|
<activity
|
||||||
|
android:name=".activity.FileTestActivity"
|
||||||
|
android:label="@string/title_activity_file_test" />
|
||||||
<activity
|
<activity
|
||||||
android:name=".activity.MainActivity"
|
android:name=".activity.MainActivity"
|
||||||
android:configChanges="orientation|screenSize"
|
android:configChanges="orientation|screenSize"
|
||||||
@ -53,6 +62,16 @@
|
|||||||
<action android:name="com.example.chaoran.ScanServiceZEBRA"/>
|
<action android:name="com.example.chaoran.ScanServiceZEBRA"/>
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
</service>
|
</service>
|
||||||
|
|
||||||
|
<provider
|
||||||
|
android:name="androidx.core.content.FileProvider"
|
||||||
|
android:authorities="${applicationId}.fileprovider"
|
||||||
|
android:exported="false"
|
||||||
|
android:grantUriPermissions="true">
|
||||||
|
<meta-data
|
||||||
|
android:name="android.support.FILE_PROVIDER_PATHS"
|
||||||
|
android:resource="@xml/file_paths" />
|
||||||
|
</provider>
|
||||||
</application>
|
</application>
|
||||||
|
|
||||||
</manifest>
|
</manifest>
|
||||||
175
app/src/main/assets/demo/file_test.html
Normal file
175
app/src/main/assets/demo/file_test.html
Normal file
@ -0,0 +1,175 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="zh-CN">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>文件选择测试</title>
|
||||||
|
<style>
|
||||||
|
body { font-family: Arial, sans-serif; padding: 20px; max-width: 600px; margin: 0 auto; }
|
||||||
|
.section { margin: 20px 0; padding: 15px; border: 1px solid #ddd; border-radius: 5px; }
|
||||||
|
h2 { color: #333; margin-top: 0; }
|
||||||
|
input[type="file"] { display: block; margin: 10px 0; padding: 10px; width: 100%; box-sizing: border-box; }
|
||||||
|
.preview { margin-top: 15px; max-width: 100%; }
|
||||||
|
.preview img { max-width: 100%; height: auto; border: 1px solid #ddd; border-radius: 5px; }
|
||||||
|
.info { margin-top: 10px; padding: 10px; background-color: #f5f5f5; border-radius: 5px; font-size: 14px; }
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>文件选择和相机测试</h1>
|
||||||
|
|
||||||
|
<!-- 测试1:选择任意文件 -->
|
||||||
|
<div class="section">
|
||||||
|
<h2>1. 选择任意文件</h2>
|
||||||
|
<input type="file" id="fileInput" onchange="handleFileSelect(this)">
|
||||||
|
<div id="fileInfo" class="info" style="display:none;"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 测试2:选择图片(支持相机和文件) -->
|
||||||
|
<div class="section">
|
||||||
|
<h2>2. 选择图片(支持相机)</h2>
|
||||||
|
<!-- accept="image/*": 只接受图片, capture="environment": 启用相机 -->
|
||||||
|
<input type="file" id="imageInput" accept="image/*" capture="environment" onchange="handleImageSelect(this)">
|
||||||
|
<div id="imageInfo" class="info" style="display:none;"></div>
|
||||||
|
<div id="imagePreview" class="preview"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 测试3:直接打开相机 -->
|
||||||
|
<div class="section">
|
||||||
|
<h2>3. 仅使用相机拍照</h2>
|
||||||
|
<!-- 通过JavaScript接口直接调用相机,不显示选择器 -->
|
||||||
|
<button onclick="openCameraDirectly()" style="padding: 10px 20px; font-size: 16px; cursor: pointer;">打开相机</button>
|
||||||
|
<div id="cameraInfo" class="info" style="display:none;"></div>
|
||||||
|
<div id="cameraPreview" class="preview"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
/**
|
||||||
|
* 处理文件选择 - 显示文件信息
|
||||||
|
*/
|
||||||
|
function handleFileSelect(input) {
|
||||||
|
var file = input.files[0]; // 获取选择的文件
|
||||||
|
var infoDiv = document.getElementById('fileInfo'); // 获取信息显示区域
|
||||||
|
|
||||||
|
if (file) {
|
||||||
|
infoDiv.style.display = 'block'; // 显示信息区域
|
||||||
|
infoDiv.innerHTML = '<strong>文件信息:</strong><br>文件名: ' + file.name + '<br>文件大小: ' + formatFileSize(file.size) + '<br>文件类型: ' + (file.type || '未知');
|
||||||
|
} else {
|
||||||
|
infoDiv.style.display = 'none'; // 隐藏信息区域
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 处理图片选择 - 显示信息和预览
|
||||||
|
*/
|
||||||
|
function handleImageSelect(input) {
|
||||||
|
var file = input.files[0]; // 获取选择的文件
|
||||||
|
var infoDiv = document.getElementById('imageInfo');
|
||||||
|
var previewDiv = document.getElementById('imagePreview');
|
||||||
|
|
||||||
|
if (file) {
|
||||||
|
infoDiv.style.display = 'block';
|
||||||
|
infoDiv.innerHTML = '<strong>图片信息:</strong><br>文件名: ' + file.name + '<br>文件大小: ' + formatFileSize(file.size) + '<br>文件类型: ' + file.type;
|
||||||
|
|
||||||
|
// 使用FileReader读取图片并显示预览
|
||||||
|
var reader = new FileReader();
|
||||||
|
reader.onload = function(e) {
|
||||||
|
// e.target.result 是图片的Base64数据
|
||||||
|
previewDiv.innerHTML = '<img src="' + e.target.result + '" alt="图片预览">';
|
||||||
|
};
|
||||||
|
reader.readAsDataURL(file); // 读取为DataURL格式
|
||||||
|
} else {
|
||||||
|
infoDiv.style.display = 'none';
|
||||||
|
previewDiv.innerHTML = '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 直接打开相机 - 通过Android接口
|
||||||
|
*/
|
||||||
|
function openCameraDirectly() {
|
||||||
|
// CameraHelper是Android注入的JavaScript接口
|
||||||
|
if (typeof CameraHelper !== 'undefined') {
|
||||||
|
CameraHelper.openCamera(); // 调用Android方法打开相机
|
||||||
|
} else {
|
||||||
|
alert('相机功能不可用');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 相机拍照结果回调 - 由Android调用
|
||||||
|
* @param uri - 照片URI
|
||||||
|
* @param path - 照片路径
|
||||||
|
*/
|
||||||
|
function onCameraResult(uri, path) {
|
||||||
|
var infoDiv = document.getElementById('cameraInfo');
|
||||||
|
var previewDiv = document.getElementById('cameraPreview');
|
||||||
|
|
||||||
|
if (uri && path) {
|
||||||
|
// 拍照成功
|
||||||
|
infoDiv.style.display = 'block';
|
||||||
|
infoDiv.innerHTML = '<strong>拍照成功:</strong><br>URI: ' + uri + '<br>路径: ' + path;
|
||||||
|
previewDiv.innerHTML = '<img src="' + uri + '" alt="照片预览">';
|
||||||
|
} else {
|
||||||
|
// 拍照取消
|
||||||
|
infoDiv.style.display = 'block';
|
||||||
|
infoDiv.innerHTML = '<strong>拍照已取消</strong>';
|
||||||
|
previewDiv.innerHTML = '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 格式化文件大小 - 字节转换为KB/MB/GB
|
||||||
|
*/
|
||||||
|
function formatFileSize(bytes) {
|
||||||
|
if (bytes === 0) return '0 Bytes';
|
||||||
|
var k = 1024; // 换算基数
|
||||||
|
var sizes = ['Bytes', 'KB', 'MB', 'GB']; // 单位数组
|
||||||
|
var i = Math.floor(Math.log(bytes) / Math.log(k)); // 计算单位索引
|
||||||
|
return Math.round(bytes / Math.pow(k, i) * 100) / 100 + ' ' + sizes[i]; // 计算并返回
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
<!--
|
||||||
|
其他地方接入
|
||||||
|
<input type="file" accept="image/*" capture="environment" onchange="handlePhoto(this)">
|
||||||
|
// 处理照片选择
|
||||||
|
function handlePhoto(input) {
|
||||||
|
var file = input.files[0];
|
||||||
|
|
||||||
|
if (file) {
|
||||||
|
// 读取文件
|
||||||
|
var reader = new FileReader();
|
||||||
|
reader.onload = function(e) {
|
||||||
|
var base64Data = e.target.result;
|
||||||
|
|
||||||
|
// 显示预览
|
||||||
|
document.getElementById('preview').innerHTML =
|
||||||
|
'<img src="' + base64Data + '" style="max-width: 100%;">';
|
||||||
|
|
||||||
|
// 上传到服务器
|
||||||
|
uploadPhoto(base64Data);
|
||||||
|
};
|
||||||
|
reader.readAsDataURL(file);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 上传照片
|
||||||
|
function uploadPhoto(base64Data) {
|
||||||
|
var xhr = new XMLHttpRequest();
|
||||||
|
xhr.open('POST', '/api/upload', true);
|
||||||
|
xhr.setRequestHeader('Content-Type', 'application/json');
|
||||||
|
|
||||||
|
xhr.onload = function() {
|
||||||
|
if (xhr.status === 200) {
|
||||||
|
console.log('上传成功');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
xhr.send(JSON.stringify({
|
||||||
|
image: base64Data
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
-->
|
||||||
@ -0,0 +1,113 @@
|
|||||||
|
package chaoran.business.activity;
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.net.Uri;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.webkit.ValueCallback;
|
||||||
|
import android.webkit.WebChromeClient;
|
||||||
|
import android.webkit.WebSettings;
|
||||||
|
import android.webkit.WebView;
|
||||||
|
import android.webkit.WebViewClient;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.appcompat.app.AppCompatActivity;
|
||||||
|
|
||||||
|
import chaoran.business.R;
|
||||||
|
import chaoran.business.utils.FileChooserHelper;
|
||||||
|
import chaoran.business.utils.CameraHelper;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 文件选择测试Activity
|
||||||
|
*/
|
||||||
|
public class FileTestActivity extends AppCompatActivity {
|
||||||
|
|
||||||
|
private WebView webView;
|
||||||
|
private FileChooserHelper fileChooserHelper;
|
||||||
|
private CameraHelper cameraHelper;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
setContentView(R.layout.activity_file_test);
|
||||||
|
|
||||||
|
fileChooserHelper = new FileChooserHelper(this);
|
||||||
|
|
||||||
|
webView = findViewById(R.id.webViewFileTest);
|
||||||
|
cameraHelper = new CameraHelper(this, webView);
|
||||||
|
initWebView();
|
||||||
|
|
||||||
|
// 加载测试页面
|
||||||
|
webView.loadUrl("file:///android_asset/demo/file_test.html");
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressLint("SetJavaScriptEnabled")
|
||||||
|
private void initWebView() {
|
||||||
|
WebSettings settings = webView.getSettings();
|
||||||
|
settings.setJavaScriptEnabled(true);
|
||||||
|
settings.setDomStorageEnabled(true);
|
||||||
|
|
||||||
|
// 添加JavaScript接口
|
||||||
|
webView.addJavascriptInterface(cameraHelper, "CameraHelper");
|
||||||
|
|
||||||
|
webView.setWebViewClient(new WebViewClient());
|
||||||
|
webView.setWebChromeClient(new WebChromeClient() {
|
||||||
|
@Override
|
||||||
|
public boolean onShowFileChooser(WebView webView, ValueCallback<Uri[]> filePathCallback,
|
||||||
|
FileChooserParams fileChooserParams) {
|
||||||
|
// 检查是否接受图片
|
||||||
|
String[] acceptTypes = fileChooserParams.getAcceptTypes();
|
||||||
|
boolean acceptImage = false;
|
||||||
|
boolean acceptCamera = false;
|
||||||
|
boolean cameraOnly = false;
|
||||||
|
|
||||||
|
if (acceptTypes != null && acceptTypes.length > 0) {
|
||||||
|
for (String type : acceptTypes) {
|
||||||
|
if (type.contains("image")) {
|
||||||
|
acceptImage = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查capture模式
|
||||||
|
if (fileChooserParams.isCaptureEnabled()) {
|
||||||
|
acceptCamera = true;
|
||||||
|
|
||||||
|
// 注意:Android WebView中,capture="user"或capture="environment"都会返回true
|
||||||
|
// 我们通过简单的方式判断:如果是图片且启用capture,默认显示选择器
|
||||||
|
// 只有在特定情况下才直接打开相机
|
||||||
|
|
||||||
|
// 这里我们不设置cameraOnly=true,让所有capture都显示选择器
|
||||||
|
// 如果需要仅相机模式,可以通过其他方式(如URL参数)来控制
|
||||||
|
}
|
||||||
|
|
||||||
|
fileChooserHelper.openFileChooser(filePathCallback, acceptImage, acceptCamera, cameraOnly);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||||
|
super.onActivityResult(requestCode, resultCode, data);
|
||||||
|
if (!fileChooserHelper.onActivityResult(requestCode, resultCode, data)) {
|
||||||
|
cameraHelper.onActivityResult(requestCode, resultCode, data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
|
||||||
|
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
|
||||||
|
fileChooserHelper.onRequestPermissionsResult(requestCode, permissions, grantResults);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onBackPressed() {
|
||||||
|
if (webView.canGoBack()) {
|
||||||
|
webView.goBack();
|
||||||
|
} else {
|
||||||
|
super.onBackPressed();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -16,6 +16,7 @@ import android.hardware.SensorEvent;
|
|||||||
import android.hardware.SensorEventListener;
|
import android.hardware.SensorEventListener;
|
||||||
import android.hardware.SensorManager;
|
import android.hardware.SensorManager;
|
||||||
import android.media.MediaPlayer;
|
import android.media.MediaPlayer;
|
||||||
|
import android.net.Uri;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.os.Vibrator;
|
import android.os.Vibrator;
|
||||||
@ -58,6 +59,7 @@ import chaoran.business.service.ScanServiceZEBRA;
|
|||||||
import chaoran.business.utils.DataCleanManager;
|
import chaoran.business.utils.DataCleanManager;
|
||||||
import chaoran.business.utils.LocalAddressUtil;
|
import chaoran.business.utils.LocalAddressUtil;
|
||||||
import chaoran.business.utils.StatusBarUtil;
|
import chaoran.business.utils.StatusBarUtil;
|
||||||
|
import chaoran.business.utils.FileChooserHelper;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 流程:联网认证设备型号,验证通过,查找设备品牌进行调用驱动操作
|
* 流程:联网认证设备型号,验证通过,查找设备品牌进行调用驱动操作
|
||||||
@ -77,6 +79,7 @@ public class MainActivity extends AppCompatActivity implements ResultListener{
|
|||||||
private SettingEngine settingEngine;
|
private SettingEngine settingEngine;
|
||||||
private ProgressBar progressBar;
|
private ProgressBar progressBar;
|
||||||
private ActionBar actionBar;
|
private ActionBar actionBar;
|
||||||
|
private FileChooserHelper fileChooserHelper;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onCreate(@Nullable Bundle savedInstanceState) {
|
protected void onCreate(@Nullable Bundle savedInstanceState) {
|
||||||
@ -215,9 +218,11 @@ public class MainActivity extends AppCompatActivity implements ResultListener{
|
|||||||
actionBar = getSupportActionBar();
|
actionBar = getSupportActionBar();
|
||||||
voiceEngine = new TekVoiceEngine(this);
|
voiceEngine = new TekVoiceEngine(this);
|
||||||
settingEngine = new NetworkSettingEngine(this);
|
settingEngine = new NetworkSettingEngine(this);
|
||||||
|
fileChooserHelper = new FileChooserHelper(this);
|
||||||
webView = findViewById(R.id.webView);
|
webView = findViewById(R.id.webView);
|
||||||
progressBar = findViewById(R.id.loading);
|
progressBar = findViewById(R.id.loading);
|
||||||
webView.setWebViewClient(disposeView());
|
webView.setWebViewClient(disposeView());
|
||||||
|
webView.setWebChromeClient(createWebChromeClient());
|
||||||
WebSettings settings = webView.getSettings();
|
WebSettings settings = webView.getSettings();
|
||||||
settings.setJavaScriptEnabled(true);
|
settings.setJavaScriptEnabled(true);
|
||||||
|
|
||||||
@ -241,6 +246,48 @@ public class MainActivity extends AppCompatActivity implements ResultListener{
|
|||||||
|
|
||||||
private boolean isPageFinished = false; // 新增标志位
|
private boolean isPageFinished = false; // 新增标志位
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建WebChromeClient以支持文件选择
|
||||||
|
*/
|
||||||
|
private WebChromeClient createWebChromeClient() {
|
||||||
|
return new WebChromeClient() {
|
||||||
|
// For Android 5.0+
|
||||||
|
@Override
|
||||||
|
public boolean onShowFileChooser(WebView webView, ValueCallback<Uri[]> filePathCallback,
|
||||||
|
FileChooserParams fileChooserParams) {
|
||||||
|
// 检查是否接受图片
|
||||||
|
String[] acceptTypes = fileChooserParams.getAcceptTypes();
|
||||||
|
boolean acceptImage = false;
|
||||||
|
boolean acceptCamera = false;
|
||||||
|
boolean cameraOnly = false;
|
||||||
|
|
||||||
|
if (acceptTypes != null && acceptTypes.length > 0) {
|
||||||
|
for (String type : acceptTypes) {
|
||||||
|
if (type.contains("image")) {
|
||||||
|
acceptImage = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查capture模式
|
||||||
|
if (fileChooserParams.isCaptureEnabled()) {
|
||||||
|
acceptCamera = true;
|
||||||
|
|
||||||
|
// 注意:Android WebView中,capture="user"或capture="environment"都会返回true
|
||||||
|
// 我们通过简单的方式判断:如果是图片且启用capture,默认显示选择器
|
||||||
|
// 只有在特定情况下才直接打开相机
|
||||||
|
|
||||||
|
// 这里我们不设置cameraOnly=true,让所有capture都显示选择器
|
||||||
|
// 如果需要仅相机模式,可以通过其他方式(如URL参数)来控制
|
||||||
|
}
|
||||||
|
|
||||||
|
fileChooserHelper.openFileChooser(filePathCallback, acceptImage, acceptCamera, cameraOnly);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
//配置客户端
|
//配置客户端
|
||||||
private WebViewClient disposeView() {
|
private WebViewClient disposeView() {
|
||||||
return new WebViewClient() {
|
return new WebViewClient() {
|
||||||
@ -391,6 +438,9 @@ public class MainActivity extends AppCompatActivity implements ResultListener{
|
|||||||
case R.id.action_setting_voice:
|
case R.id.action_setting_voice:
|
||||||
startActivity(new Intent(this, VoiceSettingActivity.class));
|
startActivity(new Intent(this, VoiceSettingActivity.class));
|
||||||
break;
|
break;
|
||||||
|
case R.id.action_file_test:
|
||||||
|
startActivity(new Intent(this, FileTestActivity.class));
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
return super.onOptionsItemSelected(item);
|
return super.onOptionsItemSelected(item);
|
||||||
}
|
}
|
||||||
@ -529,4 +579,20 @@ public class MainActivity extends AppCompatActivity implements ResultListener{
|
|||||||
.show();
|
.show();
|
||||||
// 不调用 super.onBackPressed(),避免直接退出
|
// 不调用 super.onBackPressed(),避免直接退出
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||||
|
super.onActivityResult(requestCode, resultCode, data);
|
||||||
|
if (!fileChooserHelper.onActivityResult(requestCode, resultCode, data)) {
|
||||||
|
// 如果不是文件选择器的结果,可以在这里处理其他结果
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
|
||||||
|
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
|
||||||
|
if (!fileChooserHelper.onRequestPermissionsResult(requestCode, permissions, grantResults)) {
|
||||||
|
// 如果不是文件选择器的权限请求,可以在这里处理其他权限请求
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
97
app/src/main/java/chaoran/business/utils/CameraHelper.java
Normal file
97
app/src/main/java/chaoran/business/utils/CameraHelper.java
Normal file
@ -0,0 +1,97 @@
|
|||||||
|
package chaoran.business.utils;
|
||||||
|
|
||||||
|
import android.app.Activity;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.net.Uri;
|
||||||
|
import android.os.Environment;
|
||||||
|
import android.provider.MediaStore;
|
||||||
|
import android.webkit.JavascriptInterface;
|
||||||
|
import android.webkit.WebView;
|
||||||
|
|
||||||
|
import androidx.core.content.FileProvider;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.text.SimpleDateFormat;
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.Locale;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 相机辅助类 - 通过JavaScript接口直接调用相机
|
||||||
|
*/
|
||||||
|
public class CameraHelper {
|
||||||
|
|
||||||
|
private static final int REQUEST_CODE_CAMERA_DIRECT = 1003;
|
||||||
|
private Activity activity;
|
||||||
|
private WebView webView;
|
||||||
|
private Uri cameraPhotoUri;
|
||||||
|
private String currentPhotoPath;
|
||||||
|
|
||||||
|
public CameraHelper(Activity activity, WebView webView) {
|
||||||
|
this.activity = activity;
|
||||||
|
this.webView = webView;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 直接打开相机拍照
|
||||||
|
*/
|
||||||
|
@JavascriptInterface
|
||||||
|
public void openCamera() {
|
||||||
|
activity.runOnUiThread(() -> {
|
||||||
|
Intent cameraIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
|
||||||
|
if (cameraIntent.resolveActivity(activity.getPackageManager()) != null) {
|
||||||
|
File photoFile = createImageFile();
|
||||||
|
if (photoFile != null) {
|
||||||
|
currentPhotoPath = photoFile.getAbsolutePath();
|
||||||
|
cameraPhotoUri = FileProvider.getUriForFile(
|
||||||
|
activity,
|
||||||
|
activity.getPackageName() + ".fileprovider",
|
||||||
|
photoFile
|
||||||
|
);
|
||||||
|
cameraIntent.putExtra(MediaStore.EXTRA_OUTPUT, cameraPhotoUri);
|
||||||
|
cameraIntent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
|
||||||
|
activity.startActivityForResult(cameraIntent, REQUEST_CODE_CAMERA_DIRECT);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建图片文件
|
||||||
|
*/
|
||||||
|
private File createImageFile() {
|
||||||
|
try {
|
||||||
|
String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss", Locale.getDefault()).format(new Date());
|
||||||
|
String imageFileName = "JPEG_" + timeStamp + "_";
|
||||||
|
File storageDir = activity.getExternalFilesDir(Environment.DIRECTORY_PICTURES);
|
||||||
|
if (storageDir != null && !storageDir.exists()) {
|
||||||
|
storageDir.mkdirs();
|
||||||
|
}
|
||||||
|
return File.createTempFile(imageFileName, ".jpg", storageDir);
|
||||||
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 处理Activity结果
|
||||||
|
*/
|
||||||
|
public boolean onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||||
|
if (requestCode == REQUEST_CODE_CAMERA_DIRECT) {
|
||||||
|
if (resultCode == Activity.RESULT_OK && cameraPhotoUri != null) {
|
||||||
|
// 通知JavaScript照片已拍摄
|
||||||
|
String jsCode = "javascript:onCameraResult('" + cameraPhotoUri.toString() + "', '" + currentPhotoPath + "')";
|
||||||
|
webView.post(() -> webView.loadUrl(jsCode));
|
||||||
|
} else {
|
||||||
|
// 取消拍照
|
||||||
|
String jsCode = "javascript:onCameraResult(null, null)";
|
||||||
|
webView.post(() -> webView.loadUrl(jsCode));
|
||||||
|
}
|
||||||
|
cameraPhotoUri = null;
|
||||||
|
currentPhotoPath = null;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
378
app/src/main/java/chaoran/business/utils/FileChooserHelper.java
Normal file
378
app/src/main/java/chaoran/business/utils/FileChooserHelper.java
Normal file
@ -0,0 +1,378 @@
|
|||||||
|
package chaoran.business.utils;
|
||||||
|
|
||||||
|
import android.Manifest;
|
||||||
|
import android.app.Activity;
|
||||||
|
import android.content.ContentResolver;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.content.pm.PackageManager;
|
||||||
|
import android.database.Cursor;
|
||||||
|
import android.net.Uri;
|
||||||
|
import android.os.Build;
|
||||||
|
import android.os.Environment;
|
||||||
|
import android.provider.MediaStore;
|
||||||
|
import android.provider.OpenableColumns;
|
||||||
|
import android.util.Base64;
|
||||||
|
import android.util.Log;
|
||||||
|
import android.webkit.ValueCallback;
|
||||||
|
import android.widget.Toast;
|
||||||
|
|
||||||
|
import androidx.core.app.ActivityCompat;
|
||||||
|
import androidx.core.content.ContextCompat;
|
||||||
|
import androidx.core.content.FileProvider;
|
||||||
|
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileInputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.text.SimpleDateFormat;
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.Locale;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 文件选择和相机拍照辅助类
|
||||||
|
*/
|
||||||
|
public class FileChooserHelper {
|
||||||
|
|
||||||
|
private static final String TAG = "FileChooserHelper";
|
||||||
|
private static final int REQUEST_CODE_CAMERA = 1001;
|
||||||
|
private static final int REQUEST_CODE_FILE_CHOOSER = 1002;
|
||||||
|
private static final int REQUEST_CODE_PERMISSION_CAMERA = 2001;
|
||||||
|
private static final int REQUEST_CODE_PERMISSION_STORAGE = 2002;
|
||||||
|
|
||||||
|
private Activity activity;
|
||||||
|
private ValueCallback<Uri[]> filePathCallback;
|
||||||
|
private Uri cameraPhotoUri;
|
||||||
|
private boolean pendingAcceptImage = false;
|
||||||
|
private boolean pendingAcceptCamera = false;
|
||||||
|
private boolean pendingCameraOnly = false;
|
||||||
|
|
||||||
|
public FileChooserHelper(Activity activity) {
|
||||||
|
this.activity = activity;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 打开文件选择器(支持相机和图片选择)
|
||||||
|
*/
|
||||||
|
public void openFileChooser(ValueCallback<Uri[]> callback, boolean acceptImage, boolean acceptCamera) {
|
||||||
|
openFileChooser(callback, acceptImage, acceptCamera, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 打开文件选择器(支持相机和图片选择)
|
||||||
|
* @param callback 文件选择回调
|
||||||
|
* @param acceptImage 是否只接受图片
|
||||||
|
* @param acceptCamera 是否支持相机
|
||||||
|
* @param cameraOnly 是否仅使用相机(不显示文件选择器)
|
||||||
|
*/
|
||||||
|
public void openFileChooser(ValueCallback<Uri[]> callback, boolean acceptImage, boolean acceptCamera, boolean cameraOnly) {
|
||||||
|
this.filePathCallback = callback;
|
||||||
|
this.pendingAcceptImage = acceptImage;
|
||||||
|
this.pendingAcceptCamera = acceptCamera;
|
||||||
|
this.pendingCameraOnly = cameraOnly;
|
||||||
|
|
||||||
|
// 检查权限
|
||||||
|
if (acceptCamera && !checkCameraPermission()) {
|
||||||
|
requestCameraPermission();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!cameraOnly && !checkStoragePermission()) {
|
||||||
|
requestStoragePermission();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建选择器
|
||||||
|
showFileChooser();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 显示文件选择器
|
||||||
|
*/
|
||||||
|
private void showFileChooser() {
|
||||||
|
Intent chooserIntent = createChooserIntent(pendingAcceptImage, pendingAcceptCamera);
|
||||||
|
if (chooserIntent != null) {
|
||||||
|
try {
|
||||||
|
activity.startActivityForResult(chooserIntent, REQUEST_CODE_FILE_CHOOSER);
|
||||||
|
} catch (Exception e) {
|
||||||
|
Log.e(TAG, "打开文件选择器失败", e);
|
||||||
|
Toast.makeText(activity, "打开文件选择器失败", Toast.LENGTH_SHORT).show();
|
||||||
|
if (filePathCallback != null) {
|
||||||
|
filePathCallback.onReceiveValue(null);
|
||||||
|
filePathCallback = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建选择器Intent
|
||||||
|
*/
|
||||||
|
private Intent createChooserIntent(boolean acceptImage, boolean acceptCamera) {
|
||||||
|
// 如果只需要相机,直接返回相机Intent
|
||||||
|
if (acceptCamera && pendingCameraOnly) {
|
||||||
|
return createCameraIntent();
|
||||||
|
}
|
||||||
|
|
||||||
|
Intent chooserIntent = new Intent(Intent.ACTION_CHOOSER);
|
||||||
|
Intent contentSelectionIntent = new Intent(Intent.ACTION_GET_CONTENT);
|
||||||
|
contentSelectionIntent.addCategory(Intent.CATEGORY_OPENABLE);
|
||||||
|
|
||||||
|
if (acceptImage) {
|
||||||
|
contentSelectionIntent.setType("image/*");
|
||||||
|
} else {
|
||||||
|
contentSelectionIntent.setType("*/*");
|
||||||
|
}
|
||||||
|
|
||||||
|
Intent[] intentArray;
|
||||||
|
if (acceptCamera) {
|
||||||
|
Intent cameraIntent = createCameraIntent();
|
||||||
|
if (cameraIntent != null) {
|
||||||
|
intentArray = new Intent[]{cameraIntent};
|
||||||
|
} else {
|
||||||
|
intentArray = new Intent[0];
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
intentArray = new Intent[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
chooserIntent.putExtra(Intent.EXTRA_INTENT, contentSelectionIntent);
|
||||||
|
chooserIntent.putExtra(Intent.EXTRA_INITIAL_INTENTS, intentArray);
|
||||||
|
chooserIntent.putExtra(Intent.EXTRA_TITLE, "选择文件");
|
||||||
|
|
||||||
|
return chooserIntent;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建相机Intent
|
||||||
|
*/
|
||||||
|
private Intent createCameraIntent() {
|
||||||
|
Intent cameraIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
|
||||||
|
if (cameraIntent.resolveActivity(activity.getPackageManager()) != null) {
|
||||||
|
File photoFile = createImageFile();
|
||||||
|
if (photoFile != null) {
|
||||||
|
cameraPhotoUri = FileProvider.getUriForFile(
|
||||||
|
activity,
|
||||||
|
activity.getPackageName() + ".fileprovider",
|
||||||
|
photoFile
|
||||||
|
);
|
||||||
|
cameraIntent.putExtra(MediaStore.EXTRA_OUTPUT, cameraPhotoUri);
|
||||||
|
cameraIntent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
|
||||||
|
return cameraIntent;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建图片文件
|
||||||
|
*/
|
||||||
|
private File createImageFile() {
|
||||||
|
try {
|
||||||
|
String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss", Locale.getDefault()).format(new Date());
|
||||||
|
String imageFileName = "JPEG_" + timeStamp + "_";
|
||||||
|
File storageDir = activity.getExternalFilesDir(Environment.DIRECTORY_PICTURES);
|
||||||
|
if (storageDir != null && !storageDir.exists()) {
|
||||||
|
storageDir.mkdirs();
|
||||||
|
}
|
||||||
|
return File.createTempFile(imageFileName, ".jpg", storageDir);
|
||||||
|
} catch (IOException e) {
|
||||||
|
Log.e(TAG, "创建图片文件失败", e);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 处理Activity结果
|
||||||
|
*/
|
||||||
|
public boolean onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||||
|
if (requestCode == REQUEST_CODE_FILE_CHOOSER) {
|
||||||
|
if (filePathCallback == null) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
Uri[] results = null;
|
||||||
|
if (resultCode == Activity.RESULT_OK) {
|
||||||
|
if (data != null && data.getData() != null) {
|
||||||
|
// 从文件选择器选择的文件
|
||||||
|
results = new Uri[]{data.getData()};
|
||||||
|
} else if (cameraPhotoUri != null) {
|
||||||
|
// 从相机拍摄的照片
|
||||||
|
results = new Uri[]{cameraPhotoUri};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
filePathCallback.onReceiveValue(results);
|
||||||
|
filePathCallback = null;
|
||||||
|
cameraPhotoUri = null;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 处理权限请求结果
|
||||||
|
*/
|
||||||
|
public boolean onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
|
||||||
|
if (requestCode == REQUEST_CODE_PERMISSION_CAMERA) {
|
||||||
|
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
|
||||||
|
Toast.makeText(activity, "相机权限已授予", Toast.LENGTH_SHORT).show();
|
||||||
|
// 权限授予后,检查存储权限
|
||||||
|
if (!checkStoragePermission()) {
|
||||||
|
requestStoragePermission();
|
||||||
|
} else {
|
||||||
|
// 权限都已授予,重新打开文件选择器
|
||||||
|
showFileChooser();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Toast.makeText(activity, "相机权限被拒绝", Toast.LENGTH_SHORT).show();
|
||||||
|
if (filePathCallback != null) {
|
||||||
|
filePathCallback.onReceiveValue(null);
|
||||||
|
filePathCallback = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
} else if (requestCode == REQUEST_CODE_PERMISSION_STORAGE) {
|
||||||
|
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
|
||||||
|
Toast.makeText(activity, "存储权限已授予", Toast.LENGTH_SHORT).show();
|
||||||
|
// 权限授予后,重新打开文件选择器
|
||||||
|
showFileChooser();
|
||||||
|
} else {
|
||||||
|
Toast.makeText(activity, "存储权限被拒绝", Toast.LENGTH_SHORT).show();
|
||||||
|
if (filePathCallback != null) {
|
||||||
|
filePathCallback.onReceiveValue(null);
|
||||||
|
filePathCallback = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查相机权限
|
||||||
|
*/
|
||||||
|
private boolean checkCameraPermission() {
|
||||||
|
return ContextCompat.checkSelfPermission(activity, Manifest.permission.CAMERA)
|
||||||
|
== PackageManager.PERMISSION_GRANTED;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 请求相机权限
|
||||||
|
*/
|
||||||
|
private void requestCameraPermission() {
|
||||||
|
ActivityCompat.requestPermissions(activity,
|
||||||
|
new String[]{Manifest.permission.CAMERA},
|
||||||
|
REQUEST_CODE_PERMISSION_CAMERA);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查存储权限
|
||||||
|
* Android 10+ 使用分区存储,不需要READ_EXTERNAL_STORAGE权限来访问通过文件选择器选择的文件
|
||||||
|
*/
|
||||||
|
private boolean checkStoragePermission() {
|
||||||
|
if (Build.VERSION.SDK_INT >= 33) { // Android 13 (TIRAMISU)
|
||||||
|
// Android 13+ 使用新的媒体权限
|
||||||
|
return ContextCompat.checkSelfPermission(activity, "android.permission.READ_MEDIA_IMAGES")
|
||||||
|
== PackageManager.PERMISSION_GRANTED;
|
||||||
|
} else if (Build.VERSION.SDK_INT >= 29) { // Android 10 (Q)
|
||||||
|
// Android 10-12 使用分区存储,通过文件选择器不需要权限
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
// Android 9 需要存储权限
|
||||||
|
return ContextCompat.checkSelfPermission(activity, Manifest.permission.READ_EXTERNAL_STORAGE)
|
||||||
|
== PackageManager.PERMISSION_GRANTED;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 请求存储权限
|
||||||
|
*/
|
||||||
|
private void requestStoragePermission() {
|
||||||
|
if (Build.VERSION.SDK_INT >= 33) { // Android 13 (TIRAMISU)
|
||||||
|
// Android 13+ 请求新的媒体权限
|
||||||
|
ActivityCompat.requestPermissions(activity,
|
||||||
|
new String[]{"android.permission.READ_MEDIA_IMAGES"},
|
||||||
|
REQUEST_CODE_PERMISSION_STORAGE);
|
||||||
|
} else if (Build.VERSION.SDK_INT >= 29) { // Android 10 (Q)
|
||||||
|
// Android 10-12 不需要请求权限
|
||||||
|
Toast.makeText(activity, "存储权限已授予", Toast.LENGTH_SHORT).show();
|
||||||
|
} else {
|
||||||
|
// Android 9 请求存储权限
|
||||||
|
ActivityCompat.requestPermissions(activity,
|
||||||
|
new String[]{Manifest.permission.READ_EXTERNAL_STORAGE},
|
||||||
|
REQUEST_CODE_PERMISSION_STORAGE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取文件名
|
||||||
|
*/
|
||||||
|
public static String getFileName(Context context, Uri uri) {
|
||||||
|
String result = null;
|
||||||
|
if (uri.getScheme().equals("content")) {
|
||||||
|
try (Cursor cursor = context.getContentResolver().query(uri, null, null, null, null)) {
|
||||||
|
if (cursor != null && cursor.moveToFirst()) {
|
||||||
|
int nameIndex = cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME);
|
||||||
|
if (nameIndex >= 0) {
|
||||||
|
result = cursor.getString(nameIndex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
Log.e(TAG, "获取文件名失败", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (result == null) {
|
||||||
|
result = uri.getPath();
|
||||||
|
int cut = result.lastIndexOf('/');
|
||||||
|
if (cut != -1) {
|
||||||
|
result = result.substring(cut + 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 将文件转换为Base64字符串
|
||||||
|
*/
|
||||||
|
public static String fileToBase64(Context context, Uri uri) {
|
||||||
|
try {
|
||||||
|
InputStream inputStream = context.getContentResolver().openInputStream(uri);
|
||||||
|
if (inputStream == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
|
||||||
|
byte[] buffer = new byte[8192];
|
||||||
|
int bytesRead;
|
||||||
|
while ((bytesRead = inputStream.read(buffer)) != -1) {
|
||||||
|
byteArrayOutputStream.write(buffer, 0, bytesRead);
|
||||||
|
}
|
||||||
|
inputStream.close();
|
||||||
|
|
||||||
|
byte[] fileBytes = byteArrayOutputStream.toByteArray();
|
||||||
|
return Base64.encodeToString(fileBytes, Base64.NO_WRAP);
|
||||||
|
} catch (Exception e) {
|
||||||
|
Log.e(TAG, "文件转Base64失败", e);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取文件大小
|
||||||
|
*/
|
||||||
|
public static long getFileSize(Context context, Uri uri) {
|
||||||
|
try (Cursor cursor = context.getContentResolver().query(uri, null, null, null, null)) {
|
||||||
|
if (cursor != null && cursor.moveToFirst()) {
|
||||||
|
int sizeIndex = cursor.getColumnIndex(OpenableColumns.SIZE);
|
||||||
|
if (sizeIndex >= 0) {
|
||||||
|
return cursor.getLong(sizeIndex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
Log.e(TAG, "获取文件大小失败", e);
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
11
app/src/main/res/layout/activity_file_test.xml
Normal file
11
app/src/main/res/layout/activity_file_test.xml
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
|
<WebView
|
||||||
|
android:id="@+id/webViewFileTest"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent" />
|
||||||
|
|
||||||
|
</RelativeLayout>
|
||||||
@ -14,4 +14,10 @@
|
|||||||
android:orderInCategory="100"
|
android:orderInCategory="100"
|
||||||
app:showAsAction="never" />
|
app:showAsAction="never" />
|
||||||
|
|
||||||
|
<item
|
||||||
|
android:id="@+id/action_file_test"
|
||||||
|
android:title="@string/action_file_test"
|
||||||
|
android:orderInCategory="100"
|
||||||
|
app:showAsAction="never" />
|
||||||
|
|
||||||
</menu>
|
</menu>
|
||||||
@ -4,6 +4,8 @@
|
|||||||
<string name="title_activity_setting_network">网络设置界面</string>
|
<string name="title_activity_setting_network">网络设置界面</string>
|
||||||
<string name="action_setting_voice">语音设置</string>
|
<string name="action_setting_voice">语音设置</string>
|
||||||
<string name="title_activity_setting_voice">语音设置界面</string>
|
<string name="title_activity_setting_voice">语音设置界面</string>
|
||||||
|
<string name="action_file_test">文件测试</string>
|
||||||
|
<string name="title_activity_file_test">文件选择测试</string>
|
||||||
<string name="title_activity_main">主页</string>
|
<string name="title_activity_main">主页</string>
|
||||||
|
|
||||||
<!-- 讯飞离线语音appid-->
|
<!-- 讯飞离线语音appid-->
|
||||||
|
|||||||
7
app/src/main/res/xml/file_paths.xml
Normal file
7
app/src/main/res/xml/file_paths.xml
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<paths xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<external-path name="external_files" path="." />
|
||||||
|
<external-cache-path name="external_cache" path="." />
|
||||||
|
<cache-path name="cache" path="." />
|
||||||
|
<files-path name="files" path="." />
|
||||||
|
</paths>
|
||||||
Reference in New Issue
Block a user