单击以开始位置更新时应用程序崩溃
Posted
技术标签:
【中文标题】单击以开始位置更新时应用程序崩溃【英文标题】:App Crashes On Clicking To Start Location Update 【发布时间】:2017-09-15 20:59:00 【问题描述】:我正在开发一个应用程序,该应用程序使用指纹身份验证在一定时间内以 10 分钟间隔的形式发送用户的位置,例如 30 分钟。我已经使用某些代码来开发应用程序。但是应用程序在单击开始更新按钮时崩溃。
注意:应用程序崩溃摘要显示 java.lang.SecurityException:客户端必须具有 ACCESS_Fine_LOCATION 权限才能请求 PRIORITY_HIGH_ACCURACY 位置。
以下是代码
清单
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.google.android.gms.location.sample.locationupdates">
<uses-permission android:name="android.permission.USE_FINGERPRINT" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-feature android:name="android.hardware.location.gps" />
<application
android:allowBackup="true"
android:icon="@drawable/ic_launcher"
android:label="@string/app_name"
android:theme="@style/AppTheme.NoActionBar">
<meta-data
android:name="com.google.android.gms.version"
android:value="@integer/google_play_services_version" />
<activity
android:name=".FingerprintActivity"
android:label="Fingerprint">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity android:name=".MainActivity">
</activity>
</application>
</manifest>
FingerprintActivity.java
import android.Manifest;
import android.annotation.TargetApi;
import android.app.KeyguardManager;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.hardware.fingerprint.FingerprintManager;
import android.os.Build;
import android.security.keystore.KeyGenParameterSpec;
import android.security.keystore.KeyPermanentlyInvalidatedException;
import android.security.keystore.KeyProperties;
import android.support.v4.app.ActivityCompat;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.TextView;
import java.io.IOException;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.UnrecoverableKeyException;
import java.security.cert.CertificateException;
import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.SecretKey;
public class FingerprintActivity extends AppCompatActivity
private KeyStore keyStore;
// Variable used for storing the key in the Android Keystore container
private static final String KEY_NAME = "androidHive";
private Cipher cipher;
private TextView textView;
public void sendMessage(View view)
Intent intent = new Intent(FingerprintActivity.this, MainActivity.class);
startActivity(intent);
@Override
protected void onCreate(Bundle savedInstanceState)
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_fingerprint);
// Initializing both Android Keyguard Manager and Fingerprint Manager
KeyguardManager keyguardManager = (KeyguardManager) getSystemService(KEYGUARD_SERVICE);
FingerprintManager fingerprintManager = (FingerprintManager) getSystemService(FINGERPRINT_SERVICE);
textView = (TextView) findViewById(R.id.errorText);
// Check whether the device has a Fingerprint sensor.
if(!fingerprintManager.isHardwareDetected())
/**
* An error message will be displayed if the device does not contain the fingerprint hardware.
* However if you plan to implement a default authentication method,
* you can redirect the user to a default authentication activity from here.
* Example:
* Intent intent = new Intent(this, DefaultAuthenticationActivity.class);
* startActivity(intent);
*/
textView.setText("Your Device does not have a Fingerprint Sensor");
else
// Checks whether fingerprint permission is set on manifest
if (ActivityCompat.checkSelfPermission(this, Manifest.permission.USE_FINGERPRINT) != PackageManager.PERMISSION_GRANTED)
textView.setText("Fingerprint authentication permission not enabled");
else
// Check whether at least one fingerprint is registered
if (!fingerprintManager.hasEnrolledFingerprints())
textView.setText("Register at least one fingerprint in Settings");
else
// Checks whether lock screen security is enabled or not
if (!keyguardManager.isKeyguardSecure())
textView.setText("Lock screen security not enabled in Settings");
else
generateKey();
if (cipherInit())
FingerprintManager.CryptoObject cryptoObject = new FingerprintManager.CryptoObject(cipher);
FingerprintHandler helper = new FingerprintHandler(this);
helper.startAuth(fingerprintManager, cryptoObject);
@TargetApi(Build.VERSION_CODES.M)
protected void generateKey()
try
keyStore = KeyStore.getInstance("AndroidKeyStore");
catch (Exception e)
e.printStackTrace();
KeyGenerator keyGenerator;
try
keyGenerator = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES, "AndroidKeyStore");
catch (NoSuchAlgorithmException | NoSuchProviderException e)
throw new RuntimeException("Failed to get KeyGenerator instance", e);
try
keyStore.load(null);
keyGenerator.init(new
KeyGenParameterSpec.Builder(KEY_NAME,
KeyProperties.PURPOSE_ENCRYPT |
KeyProperties.PURPOSE_DECRYPT)
.setBlockModes(KeyProperties.BLOCK_MODE_CBC)
.setUserAuthenticationRequired(true)
.setEncryptionPaddings(
KeyProperties.ENCRYPTION_PADDING_PKCS7)
.build());
keyGenerator.generateKey();
catch (NoSuchAlgorithmException |
InvalidAlgorithmParameterException
| CertificateException | IOException e)
throw new RuntimeException(e);
@TargetApi(Build.VERSION_CODES.M)
public boolean cipherInit()
try
cipher = Cipher.getInstance(KeyProperties.KEY_ALGORITHM_AES + "/" + KeyProperties.BLOCK_MODE_CBC + "/" + KeyProperties.ENCRYPTION_PADDING_PKCS7);
catch (NoSuchAlgorithmException | NoSuchPaddingException e)
throw new RuntimeException("Failed to get Cipher", e);
try
keyStore.load(null);
SecretKey key = (SecretKey) keyStore.getKey(KEY_NAME,
null);
cipher.init(Cipher.ENCRYPT_MODE, key);
return true;
catch (KeyPermanentlyInvalidatedException e)
return false;
catch (KeyStoreException | CertificateException | UnrecoverableKeyException | IOException | NoSuchAlgorithmException | InvalidKeyException e)
throw new RuntimeException("Failed to init Cipher", e);
指纹处理程序
import android.Manifest;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.hardware.fingerprint.FingerprintManager;
import android.os.CancellationSignal;
import android.support.v4.app.ActivityCompat;
import android.widget.TextView;
public class FingerprintHandler extends FingerprintManager.AuthenticationCallback
private Context context;
// Constructor
public FingerprintHandler(Context mContext)
context = mContext;
public void startAuth(FingerprintManager manager, FingerprintManager.CryptoObject cryptoObject)
CancellationSignal cancellationSignal = new CancellationSignal();
if (ActivityCompat.checkSelfPermission(context, Manifest.permission.USE_FINGERPRINT) != PackageManager.PERMISSION_GRANTED)
return;
manager.authenticate(cryptoObject, cancellationSignal, 0, this, null);
@Override
public void onAuthenticationError(int errMsgId, CharSequence errString)
this.update("Fingerprint Authentication error\n" + errString);
@Override
public void onAuthenticationHelp(int helpMsgId, CharSequence helpString)
this.update("Fingerprint Authentication help\n" + helpString);
@Override
public void onAuthenticationFailed()
this.update("Fingerprint Authentication failed.");
@Override
public void onAuthenticationSucceeded(FingerprintManager.AuthenticationResult result)
((Activity) context).finish();
Intent intent = new Intent(context, MainActivity.class);
context.startActivity(intent);
private void update(String e)
TextView textView = (TextView) ((Activity)context).findViewById(R.id.errorText);
textView.setText(e);
MainActivity
import android.app.Activity;
import android.content.Intent;
import android.content.IntentSender;
import android.location.Location;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.Toolbar;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
import android.widget.Toast;
import com.google.android.gms.common.ConnectionResult;
import com.google.android.gms.common.api.GoogleApiClient;
import com.google.android.gms.common.api.GoogleApiClient.ConnectionCallbacks;
import com.google.android.gms.common.api.GoogleApiClient.OnConnectionFailedListener;
import com.google.android.gms.common.api.ResultCallback;
import com.google.android.gms.common.api.Status;
import com.google.android.gms.location.LocationListener;
import com.google.android.gms.location.LocationRequest;
import com.google.android.gms.location.LocationServices;
import com.google.android.gms.location.LocationSettingsRequest;
import com.google.android.gms.location.LocationSettingsResult;
import com.google.android.gms.location.LocationSettingsStatusCodes;
import java.text.DateFormat;
import java.util.Date;
public class MainActivity extends AppCompatActivity implements
ConnectionCallbacks,
OnConnectionFailedListener,
LocationListener
protected static final String TAG = "MainActivity";
/**
* Constant used in the location settings dialog.
*/
protected static final int REQUEST_CHECK_SETTINGS = 0x1;
public static final long UPDATE_INTERVAL_IN_MILLISECONDS = 10000;
public static final long FASTEST_UPDATE_INTERVAL_IN_MILLISECONDS =
UPDATE_INTERVAL_IN_MILLISECONDS / 2;
// Keys for storing activity state in the Bundle.
protected final static String KEY_REQUESTING_LOCATION_UPDATES = "requesting-location-updates";
protected final static String KEY_LOCATION = "location";
protected final static String KEY_LAST_UPDATED_TIME_STRING = "last-updated-time-string";
protected GoogleApiClient mGoogleApiClient;
protected LocationRequest mLocationRequest;
protected LocationSettingsRequest mLocationSettingsRequest;
protected Location mCurrentLocation;
// UI Widgets.
protected Button mStartUpdatesButton;
protected Button mStopUpdatesButton;
protected TextView mLastUpdateTimeTextView;
protected TextView mLatitudeTextView;
protected TextView mLongitudeTextView;
protected TextView mLocationInadequateWarning;
// Labels.
protected String mLatitudeLabel;
protected String mLongitudeLabel;
protected String mLastUpdateTimeLabel;
protected Boolean mRequestingLocationUpdates;
protected String mLastUpdateTime;
@Override
public void onCreate(Bundle savedInstanceState)
super.onCreate(savedInstanceState);
setContentView(R.layout.main_activity);
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
// Locate the UI widgets.
mStartUpdatesButton = (Button) findViewById(R.id.start_updates_button);
mStopUpdatesButton = (Button) findViewById(R.id.stop_updates_button);
mLatitudeTextView = (TextView) findViewById(R.id.latitude_text);
mLongitudeTextView = (TextView) findViewById(R.id.longitude_text);
mLastUpdateTimeTextView = (TextView) findViewById(R.id.last_update_time_text);
mLocationInadequateWarning = (TextView) findViewById(R.id.location_inadequate_warning);
// Set labels.
mLatitudeLabel = getResources().getString(R.string.latitude_label);
mLongitudeLabel = getResources().getString(R.string.longitude_label);
mLastUpdateTimeLabel = getResources().getString(R.string.last_update_time_label);
mRequestingLocationUpdates = false;
mLastUpdateTime = "";
// Update values using data stored in the Bundle.
updateValuesFromBundle(savedInstanceState);
// Kick off the process of building the GoogleApiClient, LocationRequest, and
// LocationSettingsRequest objects.
buildGoogleApiClient();
createLocationRequest();
buildLocationSettingsRequest();
private void updateValuesFromBundle(Bundle savedInstanceState)
if (savedInstanceState != null)
// Update the value of mRequestingLocationUpdates from the Bundle, and make sure that
// the Start Updates and Stop Updates buttons are correctly enabled or disabled.
if (savedInstanceState.keySet().contains(KEY_REQUESTING_LOCATION_UPDATES))
mRequestingLocationUpdates = savedInstanceState.getBoolean(
KEY_REQUESTING_LOCATION_UPDATES);
// Update the value of mCurrentLocation from the Bundle and update the UI to show the
// correct latitude and longitude.
if (savedInstanceState.keySet().contains(KEY_LOCATION))
// Since KEY_LOCATION was found in the Bundle, we can be sure that mCurrentLocation
// is not null.
mCurrentLocation = savedInstanceState.getParcelable(KEY_LOCATION);
// Update the value of mLastUpdateTime from the Bundle and update the UI.
if (savedInstanceState.keySet().contains(KEY_LAST_UPDATED_TIME_STRING))
mLastUpdateTime = savedInstanceState.getString(KEY_LAST_UPDATED_TIME_STRING);
updateUI();
/**
* Builds a GoogleApiClient. Uses the @code #addApi method to request the
* LocationServices API.
*/
protected synchronized void buildGoogleApiClient()
Log.i(TAG, "Building GoogleApiClient");
mGoogleApiClient = new GoogleApiClient.Builder(this)
.addConnectionCallbacks(this)
.addOnConnectionFailedListener(this)
.addApi(LocationServices.API)
.build();
protected void createLocationRequest()
mLocationRequest = new LocationRequest();
// Sets the desired interval for active location updates. This interval is
// inexact. You may not receive updates at all if no location sources are available, or
// you may receive them slower than requested. You may also receive updates faster than
// requested if other applications are requesting location at a faster interval.
mLocationRequest.setInterval(UPDATE_INTERVAL_IN_MILLISECONDS);
// Sets the fastest rate for active location updates. This interval is exact, and your
// application will never receive updates faster than this value.
mLocationRequest.setFastestInterval(FASTEST_UPDATE_INTERVAL_IN_MILLISECONDS);
mLocationRequest.setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY);
/**
* Uses a @link com.google.android.gms.location.LocationSettingsRequest.Builder to build
* a @link com.google.android.gms.location.LocationSettingsRequest that is used for checking
* if a device has the needed location settings.
*/
protected void buildLocationSettingsRequest()
LocationSettingsRequest.Builder builder = new LocationSettingsRequest.Builder();
builder.addLocationRequest(mLocationRequest);
mLocationSettingsRequest = builder.build();
/**
* The callback invoked when
* @link com.google.android.gms.location.SettingsApi#checkLocationSettings(GoogleApiClient,
* LocationSettingsRequest) is called. Examines the
* @link com.google.android.gms.location.LocationSettingsResult object and determines if
* location settings are adequate. If they are not, begins the process of presenting a location
* settings dialog to the user.
*/
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data)
switch (requestCode)
// Check for the integer request code originally supplied to startResolutionForResult().
case REQUEST_CHECK_SETTINGS:
switch (resultCode)
case Activity.RESULT_OK:
Log.i(TAG, "User agreed to make required location settings changes.");
// Nothing to do. startLocationupdates() gets called in onResume again.
break;
case Activity.RESULT_CANCELED:
Log.i(TAG, "User chose not to make required location settings changes.");
mRequestingLocationUpdates = false;
updateUI();
break;
break;
/**
* Handles the Start Updates button and requests start of location updates. Does nothing if
* updates have already been requested.
*/
public void startUpdatesButtonHandler(View view)
if (!mRequestingLocationUpdates)
mRequestingLocationUpdates = true;
setButtonsEnabledState();
startLocationUpdates();
/**
* Handles the Stop Updates button, and requests removal of location updates.
*/
public void stopUpdatesButtonHandler(View view)
// It is a good practice to remove location requests when the activity is in a paused or
// stopped state. Doing so helps battery performance and is especially
// recommended in applications that request frequent location updates.
stopLocationUpdates();
/**
* Requests location updates from the FusedLocationApi.
*/
protected void startLocationUpdates()
LocationServices.SettingsApi.checkLocationSettings(
mGoogleApiClient,
mLocationSettingsRequest
).setResultCallback(new ResultCallback<LocationSettingsResult>()
@Override
public void onResult(LocationSettingsResult locationSettingsResult)
final Status status = locationSettingsResult.getStatus();
switch (status.getStatusCode())
case LocationSettingsStatusCodes.SUCCESS:
Log.i(TAG, "All location settings are satisfied.");
LocationServices.FusedLocationApi.requestLocationUpdates(
mGoogleApiClient, mLocationRequest, MainActivity.this);
break;
case LocationSettingsStatusCodes.RESOLUTION_REQUIRED:
Log.i(TAG, "Location settings are not satisfied. Attempting to upgrade " +
"location settings ");
try
// Show the dialog by calling startResolutionForResult(), and check the
// result in onActivityResult().
status.startResolutionForResult(MainActivity.this, REQUEST_CHECK_SETTINGS);
catch (IntentSender.SendIntentException e)
Log.i(TAG, "PendingIntent unable to execute request.");
break;
case LocationSettingsStatusCodes.SETTINGS_CHANGE_UNAVAILABLE:
String errorMessage = "Location settings are inadequate, and cannot be " +
"fixed here. Fix in Settings.";
Log.e(TAG, errorMessage);
Toast.makeText(MainActivity.this, errorMessage, Toast.LENGTH_LONG).show();
mRequestingLocationUpdates = false;
updateUI();
);
/**
* Updates all UI fields.
*/
private void updateUI()
setButtonsEnabledState();
updateLocationUI();
/**
* Disables both buttons when functionality is disabled due to insuffucient location settings.
* Otherwise ensures that only one button is enabled at any time. The Start Updates button is
* enabled if the user is not requesting location updates. The Stop Updates button is enabled
* if the user is requesting location updates.
*/
private void setButtonsEnabledState()
if (mRequestingLocationUpdates)
mStartUpdatesButton.setEnabled(false);
mStopUpdatesButton.setEnabled(true);
else
mStartUpdatesButton.setEnabled(true);
mStopUpdatesButton.setEnabled(false);
/**
* Sets the value of the UI fields for the location latitude, longitude and last update time.
*/
private void updateLocationUI()
if (mCurrentLocation != null)
mLatitudeTextView.setText(String.format("%s: %f", mLatitudeLabel,
mCurrentLocation.getLatitude()));
mLongitudeTextView.setText(String.format("%s: %f", mLongitudeLabel,
mCurrentLocation.getLongitude()));
mLastUpdateTimeTextView.setText(String.format("%s: %s", mLastUpdateTimeLabel,
mLastUpdateTime));
/**
* Removes location updates from the FusedLocationApi.
*/
protected void stopLocationUpdates()
// It is a good practice to remove location requests when the activity is in a paused or
// stopped state. Doing so helps battery performance and is especially
// recommended in applications that request frequent location updates.
LocationServices.FusedLocationApi.removeLocationUpdates(
mGoogleApiClient,
this
).setResultCallback(new ResultCallback<Status>()
@Override
public void onResult(Status status)
mRequestingLocationUpdates = false;
setButtonsEnabledState();
);
@Override
protected void onStart()
super.onStart();
mGoogleApiClient.connect();
@Override
public void onResume()
super.onResume();
// Within @code onPause(), we pause location updates, but leave the
// connection to GoogleApiClient intact. Here, we resume receiving
// location updates if the user has requested them.
if (mGoogleApiClient.isConnected() && mRequestingLocationUpdates)
startLocationUpdates();
updateUI();
@Override
protected void onPause()
super.onPause();
// Stop location updates to save battery, but don't disconnect the GoogleApiClient object.
if (mGoogleApiClient.isConnected())
stopLocationUpdates();
@Override
protected void onStop()
super.onStop();
mGoogleApiClient.disconnect();
/**
* Runs when a GoogleApiClient object successfully connects.
*/
@Override
public void onConnected(Bundle connectionHint)
// If the initial location was never previously requested, we use
// FusedLocationApi.getLastLocation() to get it. If it was previously requested, we store
// its value in the Bundle and check for it in onCreate(). We
// do not request it again unless the user specifically requests location updates by pressing
// the Start Updates button.
//
// Because we cache the value of the initial location in the Bundle, it means that if the
// user launches the activity,
// moves to a new location, and then changes the device orientation, the original location
// is displayed as the activity is re-created.
if (mCurrentLocation == null)
mCurrentLocation = LocationServices.FusedLocationApi.getLastLocation(mGoogleApiClient);
mLastUpdateTime = DateFormat.getTimeInstance().format(new Date());
updateLocationUI();
if (mRequestingLocationUpdates)
Log.i(TAG, "in onConnected(), starting location updates");
startLocationUpdates();
/**
* Callback that fires when the location changes.
*/
@Override
public void onLocationChanged(Location location)
mCurrentLocation = location;
mLastUpdateTime = DateFormat.getTimeInstance().format(new Date());
updateLocationUI();
@Override
public void onConnectionSuspended(int cause)
Log.i(TAG, "Connection suspended");
@Override
public void onConnectionFailed(ConnectionResult result)
// Refer to the javadoc for ConnectionResult to see what error codes might be returned in
// onConnectionFailed.
Log.i(TAG, "Connection failed: ConnectionResult.getErrorCode() = " + result.getErrorCode());
/**
* Stores activity data in the Bundle.
*/
public void onSaveInstanceState(Bundle savedInstanceState)
savedInstanceState.putBoolean(KEY_REQUESTING_LOCATION_UPDATES, mRequestingLocationUpdates);
savedInstanceState.putParcelable(KEY_LOCATION, mCurrentLocation);
savedInstanceState.putString(KEY_LAST_UPDATED_TIME_STRING, mLastUpdateTime);
super.onSaveInstanceState(savedInstanceState);
@Override
public boolean onCreateOptionsMenu(Menu menu)
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.menu_main, menu);
return true;
@Override
public boolean onOptionsItemSelected(MenuItem item)
// Handle action bar item clicks here. The action bar will
// automatically handle clicks on the Home/Up button, so long
// as you specify a parent activity in AndroidManifest.xml.
int id = item.getItemId();
if (id == R.id.action_settings)
return true;
return super.onOptionsItemSelected(item);
【问题讨论】:
而不是附加大量不相关的代码,您应该在崩溃后阅读堆栈跟踪 (LogCat),查找“Caused by ....”行,然后检查那里提到的文件和行。跨度> 阅读注释。我已经在崩溃日志中提到了原因。 这个应用的 targetSdk 是什么? minSdkVersion 23 targetSdkVersion 24 ACCESS_FINE_LOCATION permission error emulator only的可能重复 【参考方案1】:minSdkVersion 23 目标SdkVersion 24
面向 SDK 23+ 要求您支持运行时权限。您的清单是 <uses-permission>
s 在这种情况下无关紧要(如果您不针对较低的 SDK,可以将其删除)。由于您不支持运行时权限,因此您的应用未获得 GPS 访问权限,因此与 SecurityException
发生崩溃。
文档:https://developer.android.com/training/permissions/requesting.html
快速解决方案:如果可以,请将 targetSdk(和 minSdk)降低到 23 以下。如果出于任何原因不能,则必须实施运行时权限。有一些库可以帮助完成这项任务 - 只需 google 即可。
【讨论】:
以上是关于单击以开始位置更新时应用程序崩溃的主要内容,如果未能解决你的问题,请参考以下文章