51歳からのプログラミング 備忘 FusedLocationProviderClientをForegroundServiceで常駐させる

#FusedLocationProviderClientをForegroundSeriviceで常駐させる

androidDeveloperとgoogleApi様を参照してます。
あと、[<font color=#f55>nyan様</font>のサイト](https://akira-watson.com/android/fusedlocationprovider.html)も、いっつも参照しています。ありがとうございます。
<br>
以下のサンプルコードは、androidのビルドバージョンはapi26以上です。

##Manifest gradleの確認
```java:Manifest
    <uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>

```
```java:builed.gradle
dependencies {
    ...
    implementation 'com.google.android.gms:play-services-location:17.0.0'
```
<font color="#f00">**この後、必ずgradleを同期させましょう！**</font>

## NotificationしないクラッシュCode
Notificationするとコードが長くなるので、まずはシンプルに動作確認するためのコードです。
起動すると位置情報を取得し始め、ボタンプッシュでサービスと位置情報取得を破棄。
5秒経つとクラッシュ

```java:MainActivity
public class MainActivity extends AppCompatActivity {

    static MainActivity activity;
    static TextView textView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        activity = MainActivity.this;
        textView = findViewById(R.id.textView);
        Button button = findViewById(R.id.button);

        final Intent intent = new Intent(this,MyService.class);
        startForegroundService(intent);

        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                stopService(intent);
            }
        });
    }
}

```
```java:MyService
public class MyService extends Service {

    MainActivity activity;
    TextView textView;
    FusedLocationProviderClient fusedLocationClient;
    LocationCallback myCallback;

    @Override
    public void onCreate() {
        super.onCreate();
        activity = MainActivity.activity;
        textView = MainActivity.textView;
        fusedLocationClient = LocationServices.getFusedLocationProviderClient(activity);
    }

    @Override
    public int onStartCommand(Intent intent,int flags,int startId){
        // 本当ならここでNotificationを設定してstartForeground
        // でも見辛くなるので今回は省略
        myCallback = new myCallback(); // LocationCallback()インスタンス
        myCheckPermission();           // ActivityCompat.checkSelfPermission()
        myGetLocation();               // FusedLocationProviderClient.requestLocationUpdates(); 
        return START_STICKY;
    }

    // Manifestで設定したpermissonの ACCESS_FINE_LOCATIONってば
    // ユーザーデンジャラス なので、permissionの許諾が必要！
    public void myCheckPermission(){
        if(ActivityCompat.checkSelfPermission(activity, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED){
            ActivityCompat.requestPermissions(activity,new String[]{Manifest.permission.ACCESS_FINE_LOCATION},100);
            // requestPermissions()で確認した内容は
            // 本当ならonRequestPermissionsResult()で処理するのですが省略です
        }else{
            return;
        }
    }

    public void myGetLocation(){
        // fusedLocationProviderClientの動作を設定
        LocationRequest locationRequest = LocationRequest.create();
            locationRequest.setInterval(1000*3);
            locationRequest.setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY);

        // では！位置情報を取得するよ！取得した情報はCallback()関数で扱います！
        fusedLocationClient.requestLocationUpdates(locationRequest,myCallback,null);
    }

    // MainActivityで、ストップボタンを押すと、Serviceが破棄されるようにしたので
    // その時に、onDestroyが呼ばれます
    // onDestroy()でfusedLocationProviderClientを破棄するよ！
    @Override
    public void onDestroy() {
        super.onDestroy();
        fusedLocationClient.removeLocationUpdates(myCallback);
    }

    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }

    // LocationCallback()です。
    // 取得した位置情報を扱うのはこのクラス！
    class myCallback extends LocationCallback{
          @Override
          public void onLocationResult(LocationResult locationResult){
              super.onLocationResult(locationResult);
              // locationResultに位置情報があるので、それを使おう！
              double latitude = locationResult.getLastLocation().getLatitude();
              String strLatitude = String.valueOf(latitude);
              textView.setText(strLatitude);
        }
    }
}
```


## 常駐させ、緯度経度高度を取得するCode
Startボタンで位置情報取得と表示を常駐させます。
Stopボタンでサービスと位置情報取得を破棄します。
レッツゴー！

```java:MainActivity
public class MainActivity extends AppCompatActivity {

    // Serviceから参照するUI部品をstaticに
    static MainActivity activity;
    static TextView textAlutitude;
    static TextView textLatitude;
    static TextView textLongitude;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        // オブジェクトを取得！
        activity = MainActivity.this;
        textAlutitude = findViewById(R.id.textAltitude);
        textLatitude = findViewById(R.id.textLatitude);
        textLongitude = findViewById(R.id.textLongitude);
        Button btnStart= findViewById(R.id.btnStart);
        Button btnStop = findViewById(R.id.btnStop);


        // StartボタンでForagroundServiceスタート
        // StopボタンでForegroundServiceストップ
        //    ForegroundServiceストップでMyService.onDestroy()でFusedLocationProviderClientも破棄
        final Intent intent = new Intent(activity,MyService.class);
        btnStart.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                startForegroundService(intent);
            }
        });

        btnStop.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                stopService(intent);
            }
        });

    }
}

```
```java:MyService
public class MyService extends Service {

    // オブジェクトをグローバルに
    MainActivity activity;
    TextView textAltitude;
    TextView textLatitude;
    TextView textLongitude;
    FusedLocationProviderClient fusedLocationClient;
    LocationRequest locationRequest;
    LocationCallback locationCallback;

    @Override
    public void onCreate() {
        super.onCreate();

        // オブジェクトを取得！
        activity = MainActivity.activity;
        textAltitude = MainActivity.textAlutitude;
        textLatitude = MainActivity.textLatitude;
        textLongitude = MainActivity.textLongitude;
        fusedLocationClient = LocationServices.getFusedLocationProviderClient(this);

        // LocationRequestを設定しときます
        // 5秒ごとに詳細な位置情報を取得するように設定
        locationRequest = LocationRequest.create();
            locationRequest.setInterval(1000*5);
            locationRequest.setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY);
    }

    public int onStartCommand(Intent intent, int flags, int startId){
        // 常駐させたいので通知を出します

        // 通知コンテンツ、重要度、チャンネルを設定
        Notification notification;
        NotificationCompat.Builder builder = new NotificationCompat.Builder(this,"id");
        Intent pIntent = new Intent(this,MainActivity.class);
        PendingIntent pendingIntent = PendingIntent.getActivity(this,0,pIntent,0);
        notification = builder.setSmallIcon(R.drawable.notification_icon)
                              .setContentTitle("NotificationTitle")
                              .setContentText("NotificationText")
                              .setContentIntent(pendingIntent)
                              .build();
        int importance = NotificationManager.IMPORTANCE_LOW;
        NotificationChannel channel = new NotificationChannel("id","name",importance);
        NotificationManager manager = getSystemService(NotificationManager.class);
        manager.createNotificationChannel(channel);

        // 5秒以内に startForeground !! 急げ！
        startForeground(1,notification);


        // LocationCallback()は、requestLocation,Update,removeで使うので、ここで作成しとく
        locationCallback = new LocationCallback(){
            @Override
            public void onLocationResult(LocationResult locationResult){
                super.onLocationResult(locationResult);
                String alti = String.valueOf(locationResult.getLastLocation().getAltitude());
                String lati = String.valueOf(locationResult.getLastLocation().getLatitude());
                String longi = String.valueOf(locationResult.getLastLocation().getLongitude());
                textAltitude.setText(alti);
                textLatitude.setText(lati);
                textLongitude.setText(longi);
            }
        };

        checkLocationPermission();
        return START_STICKY;
    }

    // 位置情報を取得
    private void locationUpdate(){
        fusedLocationClient.requestLocationUpdates(locationRequest,locationCallback,null);
    }

    // permission ACCESS_FINE_LOCATION はユーザーデンジャラスな設定なので許可を確認します
    public void checkLocationPermission(){
        if(ActivityCompat.checkSelfPermission(activity,Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED){
            // ACCESS_FINE_LOCATIONの許可がなければダイアログでユーザーに確認
            ActivityCompat.requestPermissions(activity,new String[]{Manifest.permission.ACCESS_FINE_LOCATION},100);
        }else{
            locationUpdate();
        }
    }

    // checkLocationPermission()のrequestPermissionsでユーザに確認した内容を処理します
    public void onRequestPermissionsResult(int requestCode,String[] permissions,int[] grantResults){
        if(requestCode == 100){
            if(grantResults[0] == PackageManager.PERMISSION_GRANTED){
                // 許可が得られたら
                locationUpdate();
            }else{
                // 許可が得られなかったら
                textAltitude.setText("No permission");
            }
        }
    }

    // Serviceが破棄されるときに、FusedLocationProviderClientも破棄
    @Override
    public void onDestroy() {
        super.onDestroy();
        fusedLocationClient.removeLocationUpdates(locationCallback);
    }

    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }
}
```

今回も、かなり時間を掛けました。位置情報を常駐的に取得するのって、僕にはハードル高すぎました。このコードを作るまでに約2週間。行ったり来たりを繰り返して、やっとでした～。

今回も亀の歩みですね。

亀の歩みでも、諦めないでもがき続けると、前進できるんだなって感じたよ。
無理かな無理かなと思っても、前進できるんだね。
まだまだ全然実力不足なんだけれど、面白い。すごく楽しい！