【发布时间】:2017-11-21 20:05:27
【问题描述】:
我是一名新手 Android 开发人员,在跨版本支持方面遇到了一些困难:我正在开发一个应用程序(名称为 RECIPE),最低 SDK 版本要求为 21(来自 Lollipop 等) )。
目前该应用程序只有几个功能:它通过意图切换活动,通过发送意图打开相机,允许用户使用相机模块制作单张照片,然后存储照片并将预览返回给主应用程序。
调用相机意图时会出现问题:在这种情况下,如果我在具有 API 23(棉花糖;对于 21 和 22 Lollipop 的 API)的设备的模拟器上运行应用程序,应用程序运行顺利,因为我必须做一些权限管理);但不幸的是,如果我在使用 API 24 或 25 (Nougat) 的设备上运行该应用程序会崩溃。
如果您想重现该问题,请在安装应用程序后(在具有 API 24 或 25 的物理或模拟设备上)打开它,然后点击“转到单张照片拍摄模式”,然后点击“拍照”启动相机意图。 通常还会提示您允许写入权限来存储照片文件。
我认为该错误来自写入权限或与相机意图有关的某些内容。
下面是代码
MainMenu.java
package it.iudiconenext.alessandro.recipecrowdsourcingapp;
/**
* TODO=check if AppCompatActivity is necessary
*/
import android.Manifest;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.support.annotation.NonNull;
import android.support.v4.app.ActivityCompat;
import android.support.v4.content.pm.ActivityInfoCompat;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Toast;
/** The main class with the one that extends and the implementation of the callback for the result
* of requesting permission */
public class MainMenu extends AppCompatActivity
implements ActivityCompat.OnRequestPermissionsResultCallback {
// Id to identify the Write External Storage permission request (it can be a random number)
private static final int REQUEST_WES = 0;
// The following string is used in log messages
public static final String TAG = "MainMenu";
/**Called when the activity is first created.*/
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main_menu);
}
/** The button for going from the Main Menu Activity to the Single Photo Shooting Activity*/
public void buttonMMAtoSPSA(View view) {
Log.i(TAG, "Accessing to SPSA. Checking permission.");
//Check if the Write External Storage permission is already available.
if (checkSelfPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED) {
// Write External Storage permission is already available, show the SPSA.
MMAtoSPSA();
} else {
// Write External Storage permission has not been granted.
// Provide an additional rationale to the user if the permission was not granted and the
// user would benefit from additional context for the use of the permission.
if (shouldShowRequestPermissionRationale(Manifest.permission.WRITE_EXTERNAL_STORAGE)) {
Toast.makeText(this, "External Storage Writing access is required.",
Toast.LENGTH_SHORT).show();
}
// Request Write External Storage permission
requestPermissions(new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, REQUEST_WES);
}
}
private void MMAtoSPSA () {
//The part of the code for switching to Single Photo Shooting Activity
Intent myIntent = new Intent(this, SinglePhotoShooting.class);
startActivityForResult(myIntent, 0);
}
/** The button for going from the Main Menu Activity to the Multi Photo Shooting Activity*/
public void buttonMMAtoMPSA(View view) {
//The part of the code for switching to Multi Photo Shooting Activity
Intent myIntent = new Intent(view.getContext(), MultiPhotoShooting.class);
startActivityForResult(myIntent, 0);
}
//@Override
public void onRequestPermissionResult (int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults){
if (requestCode == REQUEST_WES){
if (grantResults [0] == PackageManager.PERMISSION_GRANTED){
Log.i(TAG, "WES permission has now been granted; continuing.");
Toast.makeText(this, "WES permission has now been granted; continuing.",
Toast.LENGTH_SHORT).show();
}else {
Log.i(TAG, "WES permission was denied; stopping.");
}
} else
{
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
}
}
}
MultiPhotoShooting.java
package it.iudiconenext.alessandro.recipecrowdsourcingapp;
import android.content.Intent;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
/**
* Created by alessandro on 09/06/17.
* This is the Java Class corresponding to the "Multi Photo Shooting mode"
*/
public class MultiPhotoShooting extends AppCompatActivity{
/** Called when the activity is first created. */
public void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
setContentView(R.layout.multi_photo_shooting);}
/** The button for going from the Multi Photo Shooting Activity to the Main Menu Activity */
public void MPSAtoMMA(View view){
Intent myIntent = new Intent(view.getContext(), MainMenu.class);
startActivityForResult(myIntent, 0);
}
}
SinglePhotoShooting.java
package it.iudiconenext.alessandro.recipecrowdsourcingapp;
import android.Manifest;
import android.content.Intent;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.net.Uri;
import android.os.Bundle;
import android.os.Environment;
import android.provider.MediaStore;
import android.support.v4.content.ContextCompat;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.widget.ImageView;
import android.widget.Toast;
import java.io.File;
import java.io.IOException;
import java.util.Date;
import java.text.SimpleDateFormat;
/**
* Created by alessandro on 08/06/17.
* This is the Java Class corresponding to the "Single Photo Shooting mode"
*/
public class SinglePhotoShooting extends AppCompatActivity {
// This variable is needed as request code in the takePhoto method
private static final int ACTIVITY_START_CAMERA_APP = 0;
// This ImageView variable is useful for finding the view that we want inside the layout
private ImageView SPSAPhotoTakenImageView;
// A variable in the activity that saves the location of the file where we've written to
private String mImageFileLocation = "";
/** Called when the activity is first created. */
public void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
setContentView(R.layout.single_photo_shooting);
SPSAPhotoTakenImageView = (ImageView) findViewById(R.id. PrewievSPSA);
}
/** The button for going from the Single Photo Shooting Activity to the Main Menu Activity */
public void SPSAtoMMA(View view){
Intent myIntent = new Intent(view.getContext(), MainMenu.class);
startActivityForResult(myIntent, 0);
}
/** The method to call an Intent to open the camera app */
public void SPSATakePhoto(View view) {
// int permissionCheck = ContextCompat.checkSelfPermission(SinglePhotoShooting,
// Manifest.permission.WRITE_EXTERNAL_STORAGE);
Intent callCameraApplicationIntent = new Intent();
callCameraApplicationIntent.setAction(MediaStore.ACTION_IMAGE_CAPTURE);
// We give some instruction to the intent to save the image
File photoFile = null;
try {
// If the createImageFile will be succesful, photofile will have the address of the file
photoFile = createImageFile();
// Here we call the function that will try to catch the exception made by the throw function
} catch (IOException e){
e.printStackTrace();
}
// Here we add an extra filed to the intent to put the address on to
callCameraApplicationIntent.putExtra(MediaStore.EXTRA_OUTPUT, Uri.fromFile(photoFile));
startActivityForResult(callCameraApplicationIntent, ACTIVITY_START_CAMERA_APP);
}
/** The method to give a Bitmap back to the application for a preview */
protected void onActivityResult (int requestCode, int resultCode, Intent data){
if(requestCode == ACTIVITY_START_CAMERA_APP && resultCode == RESULT_OK ){
/** The code that handles the preview for the photo */
// Here we create a bitmap and use BitmapFactory to decode the file
Bitmap SPSAPhotoTakenBitmap = BitmapFactory.decodeFile(mImageFileLocation);
// Assign the bitmap to the ImageView
SPSAPhotoTakenImageView.setImageBitmap(SPSAPhotoTakenBitmap);
}
}
/** The function that specifies the location and the name of the file that we want to create */
// As certain function calls quite important rights, we wanna catch and be notified when something goes wrong and for this we throw an exception
File createImageFile() throws IOException {
// Here we create a "non-collision file name", alternatively said, "an unique filename" using the "timeStamp" functionality
String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmSS").format(new Date());
String imageFileName = "IMAGE_" + timeStamp + "_";
// Here we specify the location and environment where we want to save the so-created file
File storageDirectory = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES);
// Here we create the file using a prefix, a suffix and a directory
File image = File.createTempFile(imageFileName, ".jpg", storageDirectory);
// Here the location is saved into the string mImageFileLocation
mImageFileLocation = image.getAbsolutePath();
// The file is returned to the previous intent across the camera application
return image;
}
}
activity_main_menu.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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="it.iudiconenext.alessandro.recipecrowdsourcingapp.MainMenu">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_margin="8dp">
<TextView
android:id="@+id/Activity_Main_Menu"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:gravity="center"
android:text="Main Menu"
android:textSize="24sp"
android:textStyle="bold|italic"/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_below="@id/Activity_Main_Menu"
android:orientation="vertical">
<LinearLayout
android:id="@+id/TakePhotoButtons"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="160dp"
android:layout_weight="1"
android:layout_margin="8dp">
<Button
android:id="@+id/ButtonMMtoSPSA"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_margin="1dp"
android:paddingTop="30dp"
android:onClick="buttonMMAtoSPSA"
android:text="Go to Single Photo Shooting Mode"/>
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_weight="1"
android:layout_margin="8dp">
<Button
android:id="@+id/ButtonMMtoMPSA"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_margin="1dp"
android:paddingTop="30dp"
android:onClick="buttonMMAtoMPSA"
android:text="Go to Multi Photo Shooting Mode"/>
</LinearLayout>
</LinearLayout>
</LinearLayout>
</RelativeLayout>
</LinearLayout>
multi_photo_shooting.xml
<?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:layout_gravity="center"
android:gravity="center">
<TextView
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:text="Multi Photo Shooting mode"
android:textSize="24sp"
android:textStyle="bold|italic"/>
<Button
android:id="@+id/ButtonMPSAtoMM"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Press me for going back to the Main Menu"
android:onClick="MPSAtoMMA"/>
</LinearLayout>
</ScrollView>
single_photo_shooting.xml
<?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:layout_gravity="center"
android:gravity="center">
<TextView
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:text="Multi Photo Shooting mode"
android:textSize="24sp"
android:textStyle="bold|italic"/>
<Button
android:id="@+id/ButtonMPSAtoMM"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Press me for going back to the Main Menu"
android:onClick="MPSAtoMMA"/>
</LinearLayout>
</ScrollView>
AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="it.iudiconenext.alessandro.recipecrowdsourcingapp">
<!-- Permissions managing. -->
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<!-- camera permission is unnecessary due to the use of an external resource
<uses-permission android:name="android.permission.CAMERA"/>
-->
<!-- Read permission is unnecessary due to the already present write permission
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
-->
<!-- There is no need to access the location for the moment
TODO: verify if giving this permission can give access to the GPS by the camera and so to photo
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
-->
<!-- The following permission is needed only if your app targets Android 5.0 (API level 21) or
higher and uses GPS localization service.
<uses-feature android:name="android.hardware.location.gps" />
-->
<uses-feature
android:name="android.hardware.camera2"
android:required="true"/>
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity android:name=".MainMenu"
android:label="@string/app_name">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity
android:name=".SinglePhotoShooting"
android:theme="@style/PhotoTakingTheme"
android:label="Single photo mode"/>
<activity
android:name=".MultiPhotoShooting"
android:theme="@style/PhotoTakingTheme"
android:label="Multi photo mode"/>
</application>
</manifest>
提前致谢, 亚历山德罗
EDIT 20/06/2017:我认为问题与 URI 异常有关,因为我发现以下文章指出这些异常是从 android N 版本开始给出的,并且因为在调试我的作为回报,我得到了这个异常。 https://developer.android.com/reference/android/os/FileUriExposedException.html
【问题讨论】:
-
“如果我在使用 API 24 或 25 (Nougat) 的设备上运行应用程序会崩溃”——请编辑您的问题并提供完整的 Java 堆栈跟踪以及该堆栈中引用的代码跟踪(因此,当您的 GitHub 存储库中的代码发生更改时,该问题在将来很有用)。
-
这是个好主意,似乎是一个更好的解决方案。我已经更改了我的问题的名称,并且关于 Java 代码,我应该在发布之前对其进行编辑(比如删除一些“待办事项”和无用的 cmets)......我会尽快这样做。谢谢
-
我现在也添加了代码(并删除了存储库的链接),同时我发现一篇似乎描述了我的问题的文章......我已经添加了它到我原来的帖子。现在我正在努力寻找解决方案。
-
我没有看到完整的 Java 堆栈跟踪,这是我要求的最重要的事情。如果您收到
FileUriExposedException,则您运行的是Android 7.0+ 和targetSdkVersion为24 或更高的应用程序,以及thefileUrischeme is banned。您可以通过使用Uri.fromFile()获得此信息。使用FileProvider或其他东西给你一个contentUri来代替使用。 -
Here is a sample app 演示了
FileProvider与ACTION_IMAGE_CAPTURE的使用。
标签: android android-intent android-camera android-permissions avd