初めてのAndroid Studio体験記

3.サービスの作成


サービスを作成します。

[File]⇒[New]⇒[Service]⇒[Service]をクリックします。


Class Nameに「LocationService」と入力して「Finish」ボタンをクリックします。

LocationService.javaにソースコードを記述します。

package net.mam_mam.loggps;

import android.Manifest;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.location.Location;
import android.os.Build;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.IBinder;
import android.os.Looper;
import android.provider.Settings;
import android.text.format.DateFormat;
import android.util.Log;
import androidx.core.app.NotificationCompat;
import androidx.core.content.ContextCompat;
import com.google.android.gms.location.FusedLocationProviderClient;
import com.google.android.gms.location.LocationCallback;
import com.google.android.gms.location.LocationRequest;
import com.google.android.gms.location.LocationResult;
import com.google.android.gms.location.LocationServices;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.ProtocolException;
import java.net.URL;
import java.util.Date;
import java.util.Map;
import android.app.NotificationChannel;
import android.graphics.Color;
import java.io.OutputStream;
import java.nio.charset.*;

public class LocationService extends Service {
    private Context context;
    private FusedLocationProviderClient fusedLocationClient;
    protected Location mLastLocation;
    private static String TAG ;
    private LocationRequest mLocationRequest;
    private LocationCallback mLocationCallback;
    private Handler mServiceHandler;
    private NotificationManager mNotificationManager;
    private String androidId;
    //ここではスタティックなメンバ以外は値を入れないほうがいい
    // サービスに対して表示される通知の識別子。
    private static final int NOTIFICATION_ID = 12345432;
    String channelId = "GPS Log Channel Id0";

    public LocationService() {
    }

    @Override
    public void onCreate() {
        super.onCreate();
        TAG = LocationService.class.getSimpleName();//このアプリのパッケージ名?
        Log.i(TAG, "onCreate");
        androidId=Settings.Secure.getString(this.getContentResolver(), Settings.Secure.ANDROID_ID);
        fusedLocationClient = LocationServices.getFusedLocationProviderClient(this);
        Integer pmLocation = ContextCompat.checkSelfPermission(
                this, Manifest.permission.ACCESS_FINE_LOCATION
        );
        HandlerThread handlerThread = new HandlerThread(TAG);
        handlerThread.start();
        mServiceHandler = new Handler(handlerThread.getLooper());

        mNotificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
        Intent intent = new Intent(this, LocationService.class);

        mLocationRequest = LocationRequest.create();

        //出来る限り高精度の位置情報を表示する 電力をかなり消費
            //mLocationRequest.setInterval(10000);//おおよそ10秒毎
            //mLocationRequest.setFastestInterval(5000);//最短で5秒毎
            // mLocationRequest.setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY);
        //省電力でまあまあの精度で定期的に位置取得にしたい場合
            mLocationRequest.setInterval(5*60*1000);//おおよそ5分毎
            mLocationRequest.setFastestInterval(3*60*1000);//最短で3分毎
            mLocationRequest.setPriority(LocationRequest.PRIORITY_BALANCED_POWER_ACCURACY);
        //受動的(自分からはリクエストしない)で電力が限りなくゼロで、できるだけ高精度を得たい場合
            //mLocationRequest.setPriority(LocationRequest.PRIORITY_NO_POWER);
        //精度が都市レベル(大雑把な位置精度)で省電力での場合
            //mLocationRequest.setPriority(LocationRequest.PRIORITY_LOW_POWER);

        mLocationCallback = new LocationCallback() {
            @Override
            public void onLocationResult(LocationResult locationResult) {
                super.onLocationResult(locationResult);
                Log.i(TAG, "onLocationResult");
                Thread thread = new Thread(new Runnable(){
                    @Override
                    public void run() {
                        Log.i(TAG, "onLocationResult-Thread-run");
                        Date date = new Date(System.currentTimeMillis());
                        //現在日時の取得
                        CharSequence nowDateTime = DateFormat.format("yyyy/MM/dd,kk:mm:ss", date);
                        // PostするURL設定
                        URL url=null;
                        try {
                            url = new URL("POST先のURLを指定します");
                        }catch(MalformedURLException e){
                            e.printStackTrace();
                        }
                        if(url != null){
                            HttpURLConnection httpConn =null;
                            OutputStream outStream=null;
                            try {
                                httpConn = (HttpURLConnection) url.openConnection();
                            }catch(IOException e){
                                e.printStackTrace();
                            }
                            if(httpConn !=null) {
                                //POSTするデータの準備
                                String postData =
                                    "id="+androidId+
                                    "&date=" + String.valueOf(nowDateTime) +
                                    "&lat=" + locationResult.getLastLocation().getLatitude() +
                                    "&lng=" + locationResult.getLastLocation().getLongitude();
                                httpConn.setInstanceFollowRedirects(false);
                                httpConn.setDoOutput(true);
                                httpConn.setReadTimeout(8 * 1000);
                                httpConn.setConnectTimeout(10 * 1000);
                                try {
                                    httpConn.setRequestMethod("POST");
                                } catch (ProtocolException e) {
                                    e.printStackTrace();
                                }
                                try {
                                    httpConn.connect();
                                    outStream = httpConn.getOutputStream();
                                    outStream.write(postData.getBytes(StandardCharsets.UTF_8));
                                    outStream.flush();
                                    int status = httpConn.getResponseCode();
                                    if(status==HttpURLConnection.HTTP_OK){
                                        Log.d(TAG,"HTTP OK");
                                    }
                                } catch (IOException e) {
                                    e.printStackTrace();
                                }
                            }
                        }
                    }
                });
                thread.start();
            }
        };
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        Log.i(TAG, "onStartCommand");

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            String channelName = "My Background Service";
            NotificationChannel chan = new NotificationChannel(channelId, channelName, NotificationManager.IMPORTANCE_NONE);
            chan.setLightColor(Color.BLUE);
            chan.setLockscreenVisibility(Notification.VISIBILITY_PRIVATE);
            NotificationManager manager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
            assert manager != null;
            manager.createNotificationChannel(chan);
        }
        NotificationCompat.Builder builder = new NotificationCompat.Builder(this,channelId)
                .setContentText("本アプリは定期的に位置情報を取得しています")
                .setContentTitle("位置情報の取得")
                .setOngoing(true)
                .setPriority(Notification.PRIORITY_HIGH)
                .setSmallIcon(R.mipmap.ic_launcher)
                .setWhen(System.currentTimeMillis())
                .setWhen(0);
        Notification nt=builder.build();
        startForeground(NOTIFICATION_ID,nt);
        try {
            fusedLocationClient.requestLocationUpdates(mLocationRequest,
                    mLocationCallback, Looper.myLooper());
        } catch (SecurityException unlikely) {
            Log.e(TAG, "Lost location permission. Could not request updates. " + unlikely);
        }
        //return START_NOT_STICKY;
        return START_STICKY;//サービスが落ちたら再起動する
    }

    public void StopCommand(){
        fusedLocationClient.removeLocationUpdates(mLocationCallback);
        stopSelf();
    }

    public IBinder onBind(Intent intent) {
        // TODO: Return the communication channel to the service.
        return null;
    }
}

AndroidManifest.xmlに権限許可を設定します。

LocationService.javaの177行目の文字が赤いのは、権限許可設定がないためです。
AndroidManifest.xml以下行を追加しましょう。
Android 8(API Level 26)以降はサービスをフォアグラウンドとして起動しなければならないそうです。
また、Android 11(API Level 30)以降はOS再起動時でも「位置情報」を取得する場合は、ユーザーに手動で「位置情報の権限:常に許可」を設定してもらう必要があるそうです。
更に不思議なのが(仕様なので仕方ないですが)「位置情報の権限:常に許可」が手動で設定できるようにするには「android.permission.ACCESS_BACKGROUND_LOCATION」が必要らしいです。
これが無いと、「位置情報の権限:常に許可」の項目がそもそも存在しないようです。

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="net.mam_mam.loggps">
    <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
    <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
    <uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
    <uses-permission android:name="android.permission.INTERNET"/>
    <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />

    <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/Theme.LogGPS">
        <service
            android:name=".LocationService"
            android:enabled="true"
            android:exported="true"></service>
        <service
            android:name=".LocationService"
            android:enabled="true"
            android:exported="true" />

        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>

「初めてのAndroid Studio体験記」に戻る

Copyright 2021 Mam