サービスの作成 ~常時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体験記」に戻る