Фон: Я пытаюсь создать очень простое c приложение WebView для доступа к адаптивному веб-приложению / веб-сайту, которое я недавно создал. Одна из функций веб-сайта включает кнопку «выбрать фото», которая позволяет пользователю загружать изображение на холст HTML5 для временного просмотра изображения и извлечения данных. Эта кнопка основана на простом вводе файла HTML:
<input type="file" accept="image/*;capture=camera">
Эта функция отлично работает в «обычных» мобильных браузерах (Chrome, Safari, Firefox, и c. На различных Android и iOS устройств). Когда пользователи нажимают кнопку «выбрать фото» в любом из этих браузеров, они могут либо (1) использовать камеру своего устройства, чтобы сделать / сохранить / загрузить новое изображение на холст HTML5, либо (2) загрузить существующее изображение, хранящееся на их телефоне.
Вот проблема: В моем приложении WebView я могу загрузить существующую фотографию из локального хранилища на холст HTML5, но я не могу загрузить фотографии, сделанные камерой при открытии камеры из приложения WebView .
Когда я нажимаю кнопку веб-сайта «Выбрать фото» в приложении WebView, мне предлагается выбрать между камерой и локальным хранилищем (это хорошо). Просмотр / загрузка из локального хранилища на холст HTML5 работает нормально. Однако, когда я нажимаю на опцию камеры, я могу сделать снимок, как обычно, с помощью камеры (передней или задней камеры), но фотография не сохраняется и не загружается на холст HTML5 (это плохо!). Я просто получаю пустой холст HTML5 после съемки / принятия фотографии. Фотография также не отображается в локальном хранилище.
Я не могу найти никаких связанных ошибок в журналах (проверено на нескольких эмуляторах / различных версиях Android и на моем Pixel 3a с Android 10). Приложение запрашивает разрешения для камеры и хранения / записи, и приложение, похоже, сохраняет эти разрешения при предоставлении. Я предполагаю, что приложение не может сохранять / получать новые фотографии с камеры, потому что я указываю неправильный каталог или имя файла для нового файла фотографии. Не знаю, как с этим справиться.
Что я пробовал: Последние несколько дней я пытался установить это приложение для загрузки фотографий с камеры. Самое близкое, что я получил, - это код из этого сообщения (как показано в моем коде ниже).
Я попытался дополнить код из этого сообщения некоторой информацией из Android документы разработчика (особенно камера> сохранение медиафайлов ) и пример входного файла хрома от Google на GitHub . Я несколько раз переписывал приложение WebView частично и полностью. Также пробовал вариантов этого сообщения и , изменяя путь к каталогу, , а также все предложения здесь и этот собственный метод "Захват изображения с камеры с помощью File Provider "" и др.
Вот мой текущий код MainActivity. java. Пожалуйста, дайте мне знать, если вам нужны другие файлы. (примечание: url заменен на example.com)
package com.example.placeholderexamplename;
import android.Manifest;
import android.app.Activity;
import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.pm.PackageManager;
import androidx.core.app.ActivityCompat;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.os.Environment;
import android.provider.MediaStore;
import android.view.KeyEvent;
import android.webkit.ValueCallback;
import android.webkit.WebChromeClient;
import android.widget.Toast;
import android.util.Log;
import android.view.View;
import android.view.Window;
import android.webkit.WebSettings;
import android.webkit.WebView;
import android.webkit.WebViewClient;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.content.ContextCompat;
import java.io.File;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public class MainActivity extends AppCompatActivity {
private static final int INPUT_FILE_REQUEST_CODE = 2;
private static final int FILECHOOSER_RESULTCODE = 1;
private static final String TAG = MainActivity.class.getSimpleName();
Context context;
WebView webView;
private WebSettings webSettings;
private ValueCallback<Uri> mUploadMessage;
private Uri mCapturedImageURI = null;
private ValueCallback<Uri[]> mFilePathCallback;
private String mCameraPhotoPath;
private static final int CAMERA_PERMISSION_CODE = 100;
private static final int STORAGE_PERMISSION_CODE = 101;
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode != INPUT_FILE_REQUEST_CODE || mFilePathCallback == null) {
super.onActivityResult(requestCode, resultCode, data);
return;
}
Uri[] results = null;
// Check response
if (resultCode == Activity.RESULT_OK) {
if (data == null) {
// If no data, we may have taken a photo
if (mCameraPhotoPath != null) {
results = new Uri[]{Uri.parse(mCameraPhotoPath)};
}
} else {
String dataString = data.getDataString();
if (dataString != null) {
results = new Uri[]{Uri.parse(dataString)};
}
}
}
mFilePathCallback.onReceiveValue(results);
mFilePathCallback = null;
return;
}
//start check permissions
private final static int REQUEST_CODE_ASK_PERMISSIONS = 1;
private static final String[] REQUIRED_SDK_PERMISSIONS = new String[] {
Manifest.permission.CAMERA, Manifest.permission.WRITE_EXTERNAL_STORAGE };
protected void checkPermissions() {
final List<String> missingPermissions = new ArrayList<String>();
// check all required dynamic permissions
for (final String permission : REQUIRED_SDK_PERMISSIONS) {
final int result = ContextCompat.checkSelfPermission(this, permission);
if (result != PackageManager.PERMISSION_GRANTED) {
missingPermissions.add(permission);
}
}
if (!missingPermissions.isEmpty()) {
// request all missing permissions
final String[] permissions = missingPermissions
.toArray(new String[missingPermissions.size()]);
ActivityCompat.requestPermissions(this, permissions, REQUEST_CODE_ASK_PERMISSIONS);
} else {
final int[] grantResults = new int[REQUIRED_SDK_PERMISSIONS.length];
Arrays.fill(grantResults, PackageManager.PERMISSION_GRANTED);
onRequestPermissionsResult(REQUEST_CODE_ASK_PERMISSIONS, REQUIRED_SDK_PERMISSIONS,
grantResults);
}
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String permissions[],
@NonNull int[] grantResults) {
switch (requestCode) {
case REQUEST_CODE_ASK_PERMISSIONS:
for (int index = permissions.length - 1; index >= 0; --index) {
if (grantResults[index] != PackageManager.PERMISSION_GRANTED) {
Toast.makeText(this, "To search by photo, please grant camera and storage access via device settings.", Toast.LENGTH_LONG).show();
}
}
}
}
//end check permissions
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
View decorView = getWindow().getDecorView();
int uiOptions = View.SYSTEM_UI_FLAG_FULLSCREEN;
decorView.setSystemUiVisibility(uiOptions);
requestWindowFeature(Window.FEATURE_NO_TITLE);
setContentView(R.layout.activity_main);
context = this;
webView = (WebView) findViewById(R.id.activity_main_webview);
WebSettings settings = webView.getSettings();
settings.setJavaScriptEnabled(true);
webView.loadUrl("https://www.example.com");
webView.setWebChromeClient(new ChromeClient());
checkPermissions();
webView.setWebViewClient(new WebViewClient() {
public boolean shouldOverrideUrlLoading(WebView view, String url) {
boolean isLocalUrl = false;
try {
URL givenUrl = new URL(url);
String host = givenUrl.getHost();
if(host.contains("example.com"))
isLocalUrl = true;
} catch (MalformedURLException e) {
}
if (isLocalUrl)
return super.shouldOverrideUrlLoading(view, url);
else
{
Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url));
startActivity(intent);
return true;
}
}
@Override
public void onPageFinished(WebView view, String url) {
super.onPageFinished(view, url);
Log.w("----------", "page finish : " + url);
}
public void onReceivedError(WebView webView, int errorCode, String description, String failingUrl) {
try {
webView.stopLoading();
} catch (Exception e) {
}
if (webView.canGoBack()) {
webView.goBack();
}
webView.loadUrl("about:blank");
AlertDialog alertDialog = new AlertDialog.Builder(context).create();
alertDialog.setTitle("Error");
alertDialog.setCancelable(false);
alertDialog.setMessage("Check your internet connection and try again.");
alertDialog.setButton(DialogInterface.BUTTON_POSITIVE, "Try Again", new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
startActivity(getIntent());
finish();
}
});
alertDialog.show();
super.onReceivedError(webView, errorCode, description, failingUrl);
}
});
}
private File createImageFile() throws IOException {
// Create an image file name
String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date());
String imageFileName = "JPEG_" + timeStamp + "_";
//File storageDir = Environment.getExternalStoragePublicDirectory(Environment.getExternalStorageState());
File storageDir = context.getExternalFilesDir(Environment.DIRECTORY_PICTURES);
File imageFile = File.createTempFile(
imageFileName, // prefix
".jpg", // suffix
storageDir // directory
);
return imageFile;
}
public boolean onKeyDown(int keyCode, KeyEvent event) {
// Check if the key event was the Back button and if there's history
if ((keyCode == KeyEvent.KEYCODE_BACK) && webView.canGoBack()) {
webView.goBack();
return true;
}
return super.onKeyDown(keyCode, event);
}
@Override
public void onBackPressed() {
super.onBackPressed();
}
public class ChromeClient extends WebChromeClient {
// For Android 5.0
public boolean onShowFileChooser(WebView view, ValueCallback<Uri[]> filePath, WebChromeClient.FileChooserParams fileChooserParams) {
// Double check that we don't have any existing callbacks
if (mFilePathCallback != null) {
mFilePathCallback.onReceiveValue(null);
}
mFilePathCallback = filePath;
Intent takePictureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
//Intent intent = new Intent("android.media.action.IMAGE_CAPTURE");
// startActivity(intent);
if (takePictureIntent.resolveActivity(getPackageManager()) != null) {
// Create the File where the photo should go
File photoFile = null;
try {
photoFile = createImageFile();
takePictureIntent.putExtra("PhotoPath", mCameraPhotoPath);
} catch (IOException ex) {
// Error occurred while creating the File
Log.e(TAG, "Unable to create Image File", ex);
}
// Continue only if the File was successfully created
if (photoFile != null) {
mCameraPhotoPath = "file:" + photoFile.getAbsolutePath();
takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT,
Uri.fromFile(photoFile));
} else {
takePictureIntent = null;
}
}
Intent contentSelectionIntent = new Intent(Intent.ACTION_GET_CONTENT);
contentSelectionIntent.addCategory(Intent.CATEGORY_OPENABLE);
contentSelectionIntent.setType("image/*");
//contentSelectionIntent.setType("*/*");
Intent[] intentArray;
if (takePictureIntent != null) {
intentArray = new Intent[]{takePictureIntent};
} else {
intentArray = new Intent[0];
}
Intent chooserIntent = new Intent(Intent.ACTION_CHOOSER);
chooserIntent.putExtra(Intent.EXTRA_INTENT, contentSelectionIntent);
chooserIntent.putExtra(Intent.EXTRA_TITLE, "Choose from...");
chooserIntent.putExtra(Intent.EXTRA_INITIAL_INTENTS, intentArray);
startActivityForResult(chooserIntent, INPUT_FILE_REQUEST_CODE);
return true;
}
}
public void onWindowFocusChanged(boolean hasFocus) {
super.onWindowFocusChanged(hasFocus);
if (hasFocus) {
View decorView = getWindow().getDecorView();
int uiOptions = View.SYSTEM_UI_FLAG_FULLSCREEN;
decorView.setSystemUiVisibility(uiOptions);
}
}
}
(1-е изменение) Вот мой код AndroidManifest. xml (для url используются значения-заполнители, et c.). Это включает в себя некоторый неиспользованный код, оставшийся от предыдущих попыток (например, раздел); удаление неиспользуемого кода не повлияло на проблему. В настоящее время я запрашиваю и записываю только разрешения «КАМЕРА» и «WRITE_EXTERNAL_STORAGE» (это правильные разрешения?):
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.placeholderexamplename">
<application
android:allowBackup="true"
android:icon="@mipmap/example_icon"
android:label="@string/app_name"
android:roundIcon="@mipmap/example_icon_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity
android:name="com.example.placeholderexamplename.MainActivity"
android:configChanges="orientation|screenSize">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<intent-filter>
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="https" android:host="www.example.com" />
<data android:scheme="https" />
</intent-filter>
</activity>
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="${applicationId}.provider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_paths">
</meta-data>
</provider>
</application>
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.CAMERA2" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-feature android:name="android.hardware.camera" />
<uses-feature android:name="android.hardware.camera2" />
</manifest>
И мой activity_main. xml код:
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.example.placeholderexamplename.MainActivity">
<WebView
android:id="@+id/activity_main_webview"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
Я хотел бы поддерживать KitKat и более поздние версии, но на данный момент подойдет решение, ограниченное Android 10. Приложение WebView должно иметь возможность получать изображение на холст HTML5 либо (1) с камеры устройства, либо (2) из локального хранилища. Новые фотографии, сделанные из приложения, могут быть сохранены либо в каталоге publi c, либо в каталоге, привязанном к приложению - никаких предпочтений, мне просто нужно иметь возможность сделать снимок из приложения и показать его здесь HTML5 холст для быстрого действия. Я буду работать оттуда, как только разблокирую.
Это моя первая попытка Android development / Android Studio. Я новичок, и я застрял. Буду очень признателен за любую помощь! Все остальное в приложении WebView работает, это мой последний блокировщик.
Заранее благодарим вас за ваш вклад.