サービスの作成 ~常時GPSで緯度経度取得
サービスを作成します
[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体験記」に戻る
