Github:https://github.com/MADMAX110/Odometer
一、使用位置服务
之前的Odometer应用是显示一个随机数,现在要使用Android的位置服务返回走过的距离。
修改getDiatance方法使其返回走过的距离,为此要用Android的位置服务。这些服务允许你得到用户的当前位置,请求定期更新,并在用户进入一个特定位置指定半径范围内时请求触发一个意图。
步骤:
1、声明需要又使用位置服务的权限
2、创建服务时建立一个位置监听器
3、请求位置更新
4、计算走过的距离
5、在撤销服务之前删除位置更新
1、声明你需要的权限
Android默认地允许你完成很多动作,不过有些动作需要的得到用户的许可才能完成。这可能是因为它们使用了用户的个人信息,或者可能会影响存储的数据,也可能会影响其他应用的工作。
位置服务就是需要得到用户授予应用权限才能使用的服务之一。
修改AndroidManifest.xml增加以下声明:
分别是请求精确位置和模糊位置
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"xmlns:tools="http://schemas.android.com/tools"><uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" /><uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/><application>.......</application></manifest>
2、为OdometerService增加位置监听器
要实现LocationListener接口来创建位置监听器。
这个接口有4个方法需要你定义:
onLocationChanged, onPreviderEnabled, onProviderDisabled, onStatusChanged。
要在第一次创建OdometerService时建立一个位置监听器,所以要在OdometerService的onCreate方法中实现这个接口。
将下列代码加入OdometerService
private LocationListener listener;@Overridepublic void onCreate() {super.onCreate();listener = new LocationListener() {@Overridepublic void onLocationChanged(@NonNull Location location) {}};}
3、请求位置更新
要得到位置更新需要做三件事:
创建一个位置管理器来访问Android的位置服务;
指定一个位置提供器;
请求这个位置提供者将用户当前位置的定期更新发送到我们在上一页增加的位置监听器。
创建位置管理器
要使用getSystemService方法来创建
locManager = (LocationManager) getSystemService(Context.LOCALE_SERVICE);
指定位置提供者
指定位置提供者用来确定用户的位置。有两个主要选项GPS和network。
GPS选项使用设备的GPS传感器确定用户的位置,网络选项会利用Wifi、蓝牙或移动网络。
并不是所有设备都同时又这两类位置提供者,所以可以使用位置管理器的getBestProvider方法得到设备上最准确的位置提供者,这个方法有两个参数:一个Criteria对象(可以用来指定电量需求之类的标准),以及一个标志(指示当前是否应当在设备上启用)。
String provider = locManager.getBestProvider(new Criteria(), true);
请求位置更新
要用位置管理器的requestLocationUpdates方法让位置提供者将更新发送到位置监听器。
这个方法有4个参数:位置提供者、更新的最小时间间隔,位置更新之间的最小距离,你希望接收这些更新的位置监听器。
//检查是否有权限
if (ContextCompat.checkSelfPermission(this, PERMISSION_STRIMG)== PackageManager.PERMISSION_GRANTED){String provider = locManager.getBestProvider(new Criteria(), true);if (provider != null)locManager.requestLocationUpdates(provider, 1000, 1, listener);}
更新的OdometerService代码
package com.hafd.odometer;import android.Manifest;
import android.annotation.SuppressLint;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.location.Criteria;
import android.location.Location;
import android.location.LocationListener;
import android.location.LocationManager;
import android.os.Binder;
import android.os.IBinder;import androidx.annotation.NonNull;
import androidx.core.content.ContextCompat;
import androidx.core.content.PackageManagerCompat;import java.util.Random;public class OdometerService extends Service {private final IBinder binder = new OdometerBinder();private final Random random = new Random();//创建一个绑定式服务时,需要提供一个Binder实现public class OdometerBinder extends Binder {//活动将使用这个方法得到OdometerService的一个引用OdometerService getOdometer() {return OdometerService.this;}}@Overridepublic IBinder onBind(Intent intent) {return binder;}public double getDistance() {return random.nextDouble();}private LocationListener listener;private LocationManager locManager;public static final String PERMISSION_STRIMG =Manifest.permission.ACCESS_FINE_LOCATION;@SuppressLint("ServiceCast")@Overridepublic void onCreate() {super.onCreate();listener = new LocationListener() {@Overridepublic void onLocationChanged(@NonNull Location location) {}};locManager = (LocationManager) getSystemService(Context.LOCALE_SERVICE);if (ContextCompat.checkSelfPermission(this, PERMISSION_STRIMG)== PackageManager.PERMISSION_GRANTED){String provider = locManager.getBestProvider(new Criteria(), true);if (provider != null)locManager.requestLocationUpdates(provider, 1000, 1, listener);}}
}
4、计算走过的距离
可以使用Location对象的diatanceTo方法来得到两个位置之间的距离
private static double distanceInMeters;private static Location lastLocation = null;public void onCreate() {super.onCreate();listener = new LocationListener() {@Overridepublic void onLocationChanged(@NonNull Location location) {if (location == null)lastLocation = location;distanceInMeters += location.distanceTo(lastLocation);lastLocation = location;}};}public double getDistance() {return this.distanceInMeters;}
5、停止监听器继续获取位置更新
public void onDestroy() {super.onDestroy();if (locManager != null && listener != null) {if (ContextCompat.checkSelfPermission(this, PERMISSION_STRIMG) == PackageManager.PERMISSION_GRANTED){locManager.removeUpdates(listener);}locManager = null;listener = null;}}
完整的OdometerService.java
package com.hafd.odometer;import android.Manifest;
import android.annotation.SuppressLint;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.location.Criteria;
import android.location.Location;
import android.location.LocationListener;
import android.location.LocationManager;
import android.os.Binder;
import android.os.IBinder;
import androidx.annotation.NonNull;
import androidx.core.content.ContextCompat;public class OdometerService extends Service {private static double distanceInMeters;private static Location lastLocation = null;private final IBinder binder = new OdometerBinder();private LocationListener listener;private LocationManager locManager;public static final String PERMISSION_STRIMG =Manifest.permission.ACCESS_FINE_LOCATION;//创建一个绑定式服务时,需要提供一个Binder实现public class OdometerBinder extends Binder {//活动将使用这个方法得到OdometerService的一个引用OdometerService getOdometer() {return OdometerService.this;}}@Overridepublic IBinder onBind(Intent intent) {return binder;}public double getDistance() {return this.distanceInMeters;}@SuppressLint("ServiceCast")@Overridepublic void onCreate() {super.onCreate();listener = new LocationListener() {@Overridepublic void onLocationChanged(@NonNull Location location) {if (location == null)lastLocation = location;distanceInMeters += location.distanceTo(lastLocation);lastLocation = location;}};locManager = (LocationManager) getSystemService(Context.LOCALE_SERVICE);if (ContextCompat.checkSelfPermission(this, PERMISSION_STRIMG)== PackageManager.PERMISSION_GRANTED){String provider = locManager.getBestProvider(new Criteria(), true);if (provider != null)locManager.requestLocationUpdates(provider, 1000, 1, listener);}}@Overridepublic void onDestroy() {super.onDestroy();if (locManager != null && listener != null) {if (ContextCompat.checkSelfPermission(this, PERMISSION_STRIMG)== PackageManager.PERMISSION_GRANTED){locManager.removeUpdates(listener);}locManager = null;listener = null;}}
}
二、让应用请求权限
现在的应用必须在系统设置里面手动为这个应用开启位置权限,如何要让应用自己请求权限呢?
检查并请求权限
修改代码使得只有当用户授予了必要的权限时MainActivity才绑定到这个服务,这个权限由OdometerService中定义的PERMISSION_STRING常量指定。如果没有授予权限就请求这个权限。
在MainActivity更新onStart方法。
@Overrideprotected void onStart() {super.onStart();//如果没有授权就请求权限if (ContextCompat.checkSelfPermission(this, OdometerService.PERMISSION_STRIMG) != PackageManager.PERMISSION_GRANTED){ActivityCompat.requestPermissions(this,new String[]{OdometerService.PERMISSION_STRIMG}, PERMISSION_REQUEST_CODE);}else {Intent intent = new Intent(this, OdometerService.class);bindService(intent, connection, Context.BIND_AUTO_CREATE); }}
检查用户对权限请求的响应
使用requestPermissions方法请求用户授予一个权限时,不能通过检查它的返回值来确定是否授予了权限。这是因为权限请求是异步发生的,当你等待用户响应时并不会阻塞当前线程。
正确的做法是要检查用户的响应,需要覆盖活动的onRequestPermissionsResult方法。这个方法有3个参数:
一个标识权限请求的int请求代码、
一个权限String数组、
以及一个对应请求结果的int数组。
使用这个方法时,首先检查int请求代码是否与requestPermissions方法中使用的代码一致。如果一致则检查是否已经授予这个权限。
@SuppressLint("MissingSuperCall")public void onRequestPermissionsResult(int requestCode, String permissions[], int[] grantResults){switch (requestCode) {case PERMISSION_REQUEST_CODE: {if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {Intent intent = new Intent(this, OdometerService.class);bindService(intent, connection, Context.BIND_AUTO_CREATE);}else {}}}}
如果拒绝授予就发出一个通知
如果用户不授予权限,不允许使用他们当前位置,OdometerService就无法得出他们走了多远。这里使用通知告知用户。
先增加两个字符串:
<string name="app_name">Odometer</string><string name="permission_denied">Location permission required</string>
再将下列代码增加到上一节空白的else语句中:
NotificationCompat.Builder builder = new NotificationCompat.Builder(this).setSmallIcon(android.R.drawable.ic_menu_compass).setContentTitle(getResources().getString(R.string.app_name)).setContentText(getResources().getString(R.string.permission_denied)).setPriority(NotificationCompat.PRIORITY_HIGH).setVibrate(new long[] {1000, 1000}).setAutoCancel(true);Intent actionIntent = new Intent(this, MainActivity.class);PendingIntent actionPendingIntent = PendingIntent.getActivity(this,0,actionIntent,PendingIntent.FLAG_UPDATE_CURRENT);builder.setContentIntent(actionPendingIntent);NotificationManager notificationManager =(NotificationManager) getSystemService(NOTIFICATION_SERVICE);notificationManager.notify(NOTIFICATION_ID, builder.build());
完整的MainActivity
package com.hafd.odometer;import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;
import androidx.core.app.NotificationCompat;
import androidx.core.content.ContextCompat;import android.annotation.SuppressLint;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.widget.TextView;import java.util.Locale;public class MainActivity extends AppCompatActivity {private OdometerService odometer;private boolean bound = false;private final int PERMISSION_REQUEST_CODE = 698;private final int NOTIFICATION_ID = 423;private ServiceConnection connection = new ServiceConnection() {@Overridepublic void onServiceConnected(ComponentName componentName, IBinder iBinder) {OdometerService.OdometerBinder odometerBinder = (OdometerService.OdometerBinder) iBinder;odometer = odometerBinder.getOdometer(); //使用IBinder得到服务的一个引用bound = true;}@Overridepublic void onServiceDisconnected(ComponentName componentName) {bound = false;}};@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);displayDistance();}@SuppressLint({"MissingSuperCall", "NotificationPermission"})public void onRequestPermissionsResult(int requestCode, String permissions[], int[] grantResults){switch (requestCode) {case PERMISSION_REQUEST_CODE: {if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {Intent intent = new Intent(this, OdometerService.class);bindService(intent, connection, Context.BIND_AUTO_CREATE);}else {NotificationCompat.Builder builder = new NotificationCompat.Builder(this).setSmallIcon(android.R.drawable.ic_menu_compass).setContentTitle(getResources().getString(R.string.app_name)).setContentText(getResources().getString(R.string.permission_denied)).setPriority(NotificationCompat.PRIORITY_HIGH).setVibrate(new long[] {1000, 1000}).setAutoCancel(true);Intent actionIntent = new Intent(this, MainActivity.class);PendingIntent actionPendingIntent = PendingIntent.getActivity(this,0,actionIntent,PendingIntent.FLAG_UPDATE_CURRENT);builder.setContentIntent(actionPendingIntent);NotificationManager notificationManager =(NotificationManager) getSystemService(NOTIFICATION_SERVICE);notificationManager.notify(NOTIFICATION_ID, builder.build());}}}}@Overrideprotected void onStart() {super.onStart();//如果没有授权就请求权限if (ContextCompat.checkSelfPermission(this, OdometerService.PERMISSION_STRIMG) != PackageManager.PERMISSION_GRANTED){ActivityCompat.requestPermissions(this,new String[]{OdometerService.PERMISSION_STRIMG}, PERMISSION_REQUEST_CODE);}else {Intent intent = new Intent(this, OdometerService.class);bindService(intent, connection, Context.BIND_AUTO_CREATE);}}protected void onStop() {super.onStop();if (bound) {unbindService(connection);bound = false;}}private void displayDistance() {final TextView distanceView = (TextView) findViewById(R.id.distance);final Handler handler = new Handler();handler.post(new Runnable() {@Overridepublic void run() {double distance = 0.0;//如果得到OdometerService的一个引用,而且绑定到这个服务,则调用getDistance。if (bound && odometer != null)distance = odometer.getDistance();String distanceStr = String.format(Locale.getDefault(), "%1$,.2f miles", distance);distanceView.setText(distanceStr);//每秒运行一次handler.postDelayed(this, 1000);}});}
}