มาใช้ Fused Location Provider API กันเถอะ

ทุกวันนี้ผู้ที่หลงเข้ามาอ่านยังเขียน Location Provider เองอยู่หรือป่าว? เจ้าของบล็อกเชื่อว่านักพัฒนาในบ้านเรามากกว่า 50% ยังคงทำแบบนั้นอยู่ แต่รู้หรือไม่? ว่าตอนนี้ในเว็ป Android Developer ได้แนะนำว่าให้เลิกใช้วิธีแบบนั้นได้แล้ว และเปลี่ยนไปใช้ Fused Location Provider API แทน

บทความนี้เจ้าของบล็อกจะมาแนะนำให้รู้จัก Fused Location Provider API เพื่อให้ผู้ที่หลงเข้ามาอ่านเลิกใช้วิธีแบบเดิมๆ และเปลี่ยนมาใช้วิธีที่ง่ายกว่าและมีประสิทธิภาพดีกว่าที่ทาง Google แนะนำนะครับ

พื้นฐานคร่าวๆเกี่ยวกับ Location Provider

ถึงแม้ว่าจะแนะนำให้ใช้ Fused Location Provider API ไปเลย แต่ก็ควรรู้พื้นฐานของ Location Provider บนแอนดรอยด์กันด้วย จึงขออธิบายคร่าวๆให้ได้อ่านกันก่อนจะไปเริ่มใช้งาน Fused Location Provider API

Location Provider ที่ใช้กันในแอนดรอยด์จะมีอยู่ด้วยกัน 2 แบบคือ GPS Provider และ Network Provider

GPS Provider

เป็นการใช้ GPS Module ที่อยู่ในอุปกรณ์แอนดรอยด์ โดย GPS จะอ้างอิงตำแหน่งด้วยดาวเทียมที่โคจรอยู่รอบๆโลก ซึ่งมีข้อดีคือมีความแม่นยำสูง (ถ้า GPS เครื่องไหนดีๆก็คลาดเคลื่อนไม่เกิน 10 เมตร) แต่ข้อเสียก็คือใช้เวลาในการค้นหาตำแหน่งค่อนข้างนาน ไม่สามารถใช้ภายในอาคารหรือที่อับสัญญาณได้ จะคลาดเคลื่อนได้ง่ายเพราะต้องรับสัญญาณจากดาวเทียม และใช้พลังงานเยอะ

Network Provider

เป็นการใช้สัญญาณจาก Cellular หรือ  WiFi ในการอ้างอิงตำแหน่ง เพราะเสาสัญญาณแต่ละตัวจะติดตั้งไว้ที่ตำแหน่งตายตัวและมีขอบเขตจำกัด จึงทำให้ระบุได้คร่าวๆว่าอยู่ที่บริเวณไหน ซึ่งมีข้อดีคือจับตำแหน่งได้ไวเพราะสื่อสารกับเสาสัญญาณ ณ จุดนั้นๆ แต่ข้อเสียคือความแม่นยำต่ำ มีความคลาดเคลื่อนสูง (100 เมตรขึ้นไป)

จะเห็นว่า Provider ทั้ง 2 แบบนั้นมีข้อเสียและข้อดีที่แตกต่างกัน ทั้งนี้ขึ้นอยู่กับงานว่าต้องการใช้งานแบบไหน แต่ก็สามารถใช้งานร่วมกันได้ ซึ่งจะเรียกว่า Fused Provider (เดี๋ยวอธิบายให้ทีหลัง)

Fused Provider = Network Provider + GPS Provider

เมื่อข้อดีและข้อเสียของทั้งคู่นั้นตรงข้ามกันอย่างสิ้นเชิง เพื่อให้ Location Provider ใช้งานได้อย่างมีประสิทธิภาพ จึงมีการจับ Provider ทั้ง 2 แบบมาทำงานร่วมกัน เพื่อให้ได้ตำแหน่งรวดเร็วที่สุดและแม่นยำที่สุด

โดยในช่วงแรกๆที่ GPS Provider ยังระบุตำแหน่งไม่ได้ ก็จะเป็นการระบุตำแหน่งด้วย Network Provider แทนไปก่อน และเมื่อ GPS Provider จับตำแหน่งได้แม่นยำแล้วก็จะใช้ตำแหน่งจาก GPS Provider แทน

ซึ่งวิธีนี้มีประสิทธิภาพมาก ติดแค่อย่างเดียวคือ "เปลืองแบตมาก"

ปัญหาเดิมๆกับ Location Provider ที่ยังคงเป็นกันอยู่

ทุกวันนี้การเรียกใช้งาน Location Provider หรือที่ชอบเรียกกันว่า GPS ก็ยังมีนักพัฒนาหลายๆคนยังคงใช้คลาส Location ที่อยู่ใน android.location เพื่อเรียกใช้งาน Location Provider

Location Provider เป็นการทำงานอีกอย่างหนึ่งที่ "กินแบต" และจะ "กินแบตมากขึ้น" ถ้าจัดการมันไม่ดีพอ ลองถามตัวเองดูว่าแอปพลิเคชันของคุณมีการใช้งาน Location Provider อยู่หรือไม่ และถ้าใช้อยู่ คิดว่าโค๊ดที่ใช้ในตอนนี้มันจัดการกับ Location Provider ได้ดีพอแล้วหรือป่าว?

ดีพอหรือป่าวไม่รู้ เพราะทำตามในอินเตอร์เน็ต แค่มันใช้งานได้ก็พอแล้วนี่?

นี่คือสาเหตุหลักที่ทำให้การใช้งาน Location Provider ไม่มีประสิทธิภาพ เพราะข้อมูลในอินเตอร์เน็ตแค่บอกว่าทำตามแล้วใช้งานได้ แต่ไม่ได้บอกว่ามันจะเป็น Best Practice ในการใช้งาน Location Provider

โค้ดของผู้ที่หลงเข้ามาอ่านแค่สั่งให้มันทำงาน แล้วไม่ได้จัดการอะไรต่อ และมันยังคงทำงานอยู่ถึงแม้จะปิดแอปพลิเคชันไปแล้วใช่มั้ย?

เลิกใช้งาน Location Provider ที่ไม่ถูกต้องกันเถอะ

ในการใช้งาน Location Provider ไม่ได้มีแค่เรียกใช้งานตามต้องการแล้วก็จบ แต่ควรจัดการกับมันให้ดีๆด้วย เรียกใช้งานเฉพาะเวลาที่จำเป็น กำหนดความแม่นยำให้เหมาะสมกับงาน และอื่นๆ เพื่อไม่ให้แอปพลิเคชันที่ผู้ที่หลงเข้ามาอ่านสร้างขึ้นมา กลายเป็นแค่ตัวสูบแบตที่ทำให้ผู้ใช้ต้องกดลบทิ้ง

จึงทำให้ทีมแอนดรอยด์เข็น API สำหรับเรียกใช้งาน Location Provider ออกมาให้นักพัฒนาได้ใช้งานกัน โดยยัดมันลงไปใน Google Play Services และมีชื่อเรียกว่า Fused Location Provider API

ทำเรื่องยากให้เป็นเรื่องง่ายด้วย Fused Location Provider API

โดยปกติแล้ว Google Play Services ที่ติดตั้งอยู่ในแอนดรอยด์แทบจะทุกเครื่อง ณ ตอนนี้ก็มีการเรียก Location Provider เป็นระยะๆ เพื่อใช้เป็นข้อมูลในการทำงานสำหรับแอปพลิเคชันของ Google อยู่แล้ว ไม่ว่าจะเป็น Google Maps, Google Fit, Google Plus และอื่นๆอีกหลายตัว

แต่ถ้าจะให้ทุกๆตัวเรียกใช้งาน Location Provider แยกกันก็คงทำให้สูบแบตไม่น้อย ดังนั้นเค้าจึงรวมเป็น API ไว้ใน Google Play Services ซะเลย เพื่อที่ว่าจะได้เรียกใช้งานจากที่เดียวเลย รวมไปถึงเปิดให้นักพัฒนาแอปฯต่างๆเข้าใช้งานได้ด้วย

ซึ่ง Fused Location Provider API มีข้อดีก็ตรงที่นักพัฒนาไม่ต้องจัดการเรื่อง Battery/Performance Optimizing เลย เพราะ API ตัวนี้จัดการให้หมดแล้ว

เริ่มใช้งาน Fused Location Provider API

ก่อนอื่นเริ่มจากเพิ่ม Dependencies ของ Fused Location Provider API ลงใน build.gradle ก่อนเลย

implementation 'com.google.android.gms:play-services-location:<latest_version>'

และ Permission ที่ต้องประกาศมีเพียงแค่ 2 ตัว ที่ไม่จำเป็นต้องประกาศทั้ง 2 ตัว

<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />

ประกาศแค่ตัวใดตัวหนึ่ง?

ใช่ครับ ขึ้นอยู่กับว่าต้องการความแม่นยำของตำแหน่งผู้ใช้งานมากน้อยแค่ไหน ซึ่งเดี๋ยวเจ้าของบล็อกจะอธิบายให้ฟังทีหลังนะ ตอนนี้ประกาศทั้ง 2 ตัวไปก่อนก็ได้

เพิ่มเติม ที่ต้องทำเพิ่มก็คือ Runtime Permission ซึ่งเป็นฟีเจอร์ของ Android 6.0 Marshmallow ที่นักพัฒนาต้องเขียนเพิ่มเพื่อขออนุญาตผู้ใช้เพื่อเข้าถึง Location Service ในตัวเครื่องได้ Google Play Services and Runtime Permissions [Google APIs for Android]

Google Play Services and Runtime Permissions | Google APIs for Android

เชื่อมต่อกับ Google API Client ก่อนทุกครั้ง

ในการใช้งาน API ใดๆใน Google Play Service ส่วนใหญ่จะต้องเชื่อมต่อกับ Google API Client ก่อนทุกครั้ง (แต่ Google Maps API ไม่ต้อง) โดยจะมีรูปแบบคำสั่งดังนี้

val googleApiClient = GoogleApiClient.Builder(context)
        .addApi(LocationServices.API)
        .addConnectionCallbacks(connectionCallback)
        .addOnConnectionFailedListener(connectionFailedListener)
        .build()
googleApiClient.connect()

นี่คือคำสั่งสำหรับการเชื่อมต่อ Google API Client เพื่อใช้งาน Fused Location Provider API และถ้าจะเชื่อมต่อกับบริการอย่างอื่นด้วยก็อาจจะต้องมีคำสั่งอื่นเพิ่มเข้ามาด้วย

เมื่อเอาไปใช้งานจริงๆก็จะเป็นแบบนี้ (ยาวหน่อยนะ)

// MainActivity.kt
class MainActivity : AppCompatActivity, GoogleApiClient.ConnectionCallbacks, GoogleApiClient.OnConnectionFailedListener {

    private lateinit var googleApiClient: GoogleApiClient

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

        googleApiClient = GoogleApiClient.Builder(this)
                .addApi(LocationServices.API)
                .addConnectionCallbacks(this)
                .addOnConnectionFailedListener(this)
                .build()
    }

    override fun onStart() {
        super.onStart()
        googleApiClient.connect()
    }

    override fun onStop() {
        super.onStop()
        if (googleApiClient.isConnected) {
            googleApiClient.disconnect()
        }
    }

    override fun onConnected(connectionHint: Bundle?) {
        // Do something when connected with Google API Client
    }

    override fun onConnectionSuspended(cause: Int) {
        // Do something when Google API Client connection was suspended
    }

    override fun onConnectionFailed(result: ConnectionResult) {
        // Do something when Google API Client connection failed
    }
}

ในการเชื่อมต่อกับ Google API Client จะต้องมีการกำหนดค่าต่างๆก่อน เช่น

  • addApi(...) กำหนดว่าจะเชื่อมต่อกับ API ตัวไหนของ Google Play Services
  • addConnectionCallbacks(...) กำหนด Callback สำหรับสถานะการเชื่อมต่อกับ Google API Client
  • addConnectionFailedListener(...) กำหนด Listener เมื่อเชื่อมต่อกับ Google API Client ไม่สำเร็จ

แล้วจบท้ายด้วยคำสั่ง build() เพื่อสร้าง Instance ของ Google API Client ขึ้นมา และเริ่มเชื่อมต่อด้วยคำสั่ง connect()

ส่วน Callback และ Listener เจ้าของบล็อกประกาศ Implement ไว้ที่ MainActivity ไปเลย ซึ่งจะได้ Method มาทั้งหมด 3 ตัวด้วยกัน

  • onConnected(...) เป็น Method ของ ConnectionCallback ทำงานเมื่อเชื่อมต่อกับ Google API Client ได้สำเร็จ
  • onConnectionSuspended(...) เป็น Method ของ ConnectionCallback ทำงานเมื่อการเชื่อมต่อกับ Google API Client ถูกยกเลิกกลางคัน
  • onConnectionFailed(...) เป็น Method ของ OnConnectionFailedListener ทำงานเมื่อไม่สามารถเชื่อมต่อกับ Google API Client

รวมไปถึง Handle การเชื่อมต่อกับ Google API Client ในเวลาที่แอปพลิเคชันเกิด onStart() และ onStop() ด้วย โดยย้ายคำสั่ง connect() ไปไว้ใน onStart() เพื่อให้เชื่อมต่อทุกครั้งเวลาที่กลับเข้ามาใช้งานแอปพลิเคชัน และใช้คำสั่ง disconnect() เพื่อยกเลิกการเชื่อมต่อทุกครั้งใน onStop() หรือ onDestroy() (ออกจากแอปพลิเคชัน)

เรียกใช้งาน Location Services เมื่อเชื่อมต่อกับ Google API Client ได้แล้ว

เรียกใช้งานตรงไหน?

ก็แหงสิ ต้องเรียกใช้งาน Location Services ใน onConnected(...) แล้วสิ ส่วน Method อีกสองตัวที่เหลือก็มีไว้ Handle เมื่อ Google API Client ใช้งานไม่ได้ (ตรงนี้เจ้าของบล็อกจะไม่พูดถึง)

ก่อนจะใช้งาน Location Services ทุกครั้งให้ตรวจสอบก่อนว่า Location Provider บนอุปกรณ์แอนดรอยด์เครื่องน้ันๆเปิดอยู่หรือไม่ เพราะผู้ใช้หลายๆคนชอบปิดเพื่อประหยัดแบตกัน

val locationAvailability = LocationServices.FusedLocationApi.getLocationAvailability(googleApiClient)
if(locationAvailability.isLocationAvailable) {
    // Call Location Services
} else {
    // Do something when Location Provider is not available
}

ถ้าพร้อมใช้งานแล้วก็ให้ใช้คำสั่ง Location Services ได้เลย แต่ถ้าไม่ได้เปิดให้ใช้งานก็ให้แจ้งผู้ใช้งานซะว่า Location Provider ใช้ไม่ได้นะ หรือจะทำให้กดเพื่อไปหน้าเปิดใช้งาน Location Provider ก็ได้นะ เดี๋ยวพอผู้ใช้กดเปิดแล้วกลับมาเข้าแอปพลิเคชัน onStart() ก็จะทำงานอีกครั้งและ Google API Client ก็จะเชื่อมต่อใหม่โดยอัตโนมัติเอง

เมื่อ Location Provider พร้อมใช้งานแล้วก็เรียก Location Services ด้วยคำสั่งประมาณนี้

val locationRequest = LocationRequest.create().apply {
    priority = LocationRequest.PRIORITY_HIGH_ACCURACY
    interval = 5000
}
LocationServices.FusedLocationApi.requestLocationUpdates(googleApiClient, locationRequest, locationListener)

สิ่งแรกที่ควรมีเลยคือ Location Request เพื่อตั้งค่าสำหรับ Location Provider เช่น ความแม่นยำ ระยะเวลาในการทำงาน อ่านพิกัดได้กี่ครั้งแล้วหยุดทำงาน เป็นต้น

fun setExpirationDuration(millis: Long) : LocationRequest
fun setExpirationTime(millis: Long) : LocationRequest
fun setFastestInterval(millis: Long) : LocationRequest
fun setInterval(millis: Long) : LocationRequest
fun setNumUpdates(numUpdates: Int) : LocationRequest
fun setPriority(priority: Int) : LocationRequest
fun setSmallestDisplacement(smallestDisplacementMeters: Float) : LocationRequest
  • setExpirationDuration(millis: Long) กำหนดว่าจะให้อ่านพิกัดเป็นระยะเวลานานเท่าไร (หน่วยเป็นมิลลิวินาที)
  • setExpirationTime(millis: Long) กำหนดว่าจะให้อ่านพิกัดจนถึงเมื่อไร โดยนับเวลาตั้งแต่เปิดเครื่องขึ้นมา (หน่วยเป็นมิลลิวินาที)
  • setFastestInterval(millis: Long) กำหนดระยะเวลาในการอ่านพิกัดแต่ละครั้งที่เร็วที่สุดเท่าที่ทำได้ (หน่วยเป็นมิลลิวินาที)
  • setInterval(millis: Long) กำหนดระยะเวลาในการอ่านพิกัดแต่ละครั้ง (หน่วยเป็นมิลลิวินาที)
  • setNumUpdates(numUpdates: Int) จำนวนครั้งในการอ่านพิกัด
  • setPriorty(priority: Int) กำหนดความสำคัญในการอ่านข้อมูล ซึ่งมีผลไปถึงการใช้แบตเตอรีของเครื่องด้วย
  • setSmallestDisplacement(smallestDisplacementMeters: Float) ระยะทางขั้นต่ำที่จะให้อ่านพิกัดใหม่อีกครั้ง

Location Request จะช่วยให้กำหนดขอบเขตการทำงานของ Location Provider ได้ เพราะในบางครั้งแอปพลิเคชันอาจจะไม่จำเป็นต้องรู้พิกัดแม่นยำมากก็ได้ เช่น แค่อยากรู้ว่าอยู่เขตไหนในกรุงเทพ เป็นต้น แต่คำสั่งสำคัญที่อยากให้ดูกันก็คือ setPriority(...) ที่เอาไว้กำหนดความแม่นยำในการทำงาน

  • LocationRequest.PRIORITY_BALANCED_POWER_ACCURACY เน้นการอ่านพิกัดโดยใช้พลังงานที่เหมาะสม แต่ก็ได้ความแม่นยำพอสมควร โดยพิกัดที่ได้จะมีความแม่นยำในระยะ 100 เมตร (รู้ว่าอยู่ถนนอะไร)
  • LocationRequest.PRIORITY_HIGH_ACCURACY เน้นการอ่านพิกัดโดยให้ได้ความแม่นยำสูงสุดเท่าที่ทำได้ ซึ่งจะเปลืองแบตมากที่สุด (รู้ว่ายืนอยู่ตรงไหน)
  • LocationRequest.PRIORITY_LOW_POWER เน้นการอ่านพิกัดโดยใช้พลังงานน้อยที่สุด ความแม่นยำจะต่ำมาก คลาดเคลื่อนได้มากถึง 10 กิโลเมตร (อยู่ว่าอยู่เขตไหน ตำบลไหน)
  • LocationRequest.PRIORITY_NO_POWER เป็นการอ่านพิกัดแบบ Passive คือไม่มีการสั่งให้ Google Play Services อ่านพิกัด แต่จะรอจนว่า Google Play Services จะอ่านพิกัดตามการทำงานปกติจึงค่อยนำข้อมูลนั้นมา Update ซึ่งการทำงานแบบนี้จะไม่ทำให้ใช้พลังงานเพิ่ม แต่ Interval ของข้อมูลจะนานมาก
var locationRequest = LocationRequest.create().apply {
    priority = LocationRequest.PRIORITY_HIGH_ACCURACY
    interval = 5000
    fastestInterval = 1000
}
ตัวอย่างการกำหนด Location Request

จากตัวอย่างนี้กำหนดว่าอ่านพิกัดแบบแม่นยำที่สุด โดยจะอ่านพิกัดทุกๆ 5 วินาที แต่ถ้าเครื่องสามารถจับพิกัดได้ก่อน ก็จะอ่านพิกัดทันทีทุกๆ 1 วินาที

และสมมติว่ากำหนดแค่ Priority ล่ะ?

LocationRequest mRequest = LocationRequest.create().apply {
    priority = LocationRequest.PRIORITY_LOW_POWER
}

ถ้ากำหนด Location Request แบบนี้จะทำให้ Location Provider อ่านพิกัดเพียงแค่ครั้งเดียวเท่านั้นแล้วก็หยุดทำงาน เพราะว่าไม่มีการกำหนด Interval

ดังนั้นควรถ้าต้องการให้อ่านพิกัดตลอดเวลาก็อย่าลืมกำหนด Interval ด้วยนะครับ

หลังจากกำหนด Location Request เสร็จเรียบร้อยแล้วก็จะเรียกใช้งาน Location Services ด้วยคำสั่ง

LocationServices.FusedLocationApi.requestLocationUpdates(client: GoogleApiClient, request: LocationRequest, listener: LocationListener)

จะเห็นว่าต้องกำหนด Google API Client และ Location Request ซึ่งประกาศไว้เรียบร้อยแล้ว เอามากำหนดในคำสั่งนี้ได้เลย และในการทำงานของ Location Services จะมี Listener ที่ชื่อว่า Location Listner เพื่อรับค่าพิกัดที่อ่านได้จากเครื่อง

โดย Location Listener จะมี Method ที่ชื่อว่า onLocationChange(...) ด้วย เพื่อบอกให้รู้ว่าได้พิกัดของเครื่องแล้ว โดยค่าที่ส่งมาจะอยู่ในคลาส Location อีกที

val listener = object : LocationListener {
    override fun onLocationChanged(location : Location?) {
        // Do something when got new current location
    }
}

กลับมาที่โค๊ดของเจ้าของบล็อก จะใช้วิธี Implement คลาส LocationListener ไว้ที่ MainActivity เลย ได้ออกมาประมาณนี้

// MainActivity.kt
class MainActivity: AppCompatActivity(), GoogleApiClient.ConnectionCallbacks, GoogleApiClient.OnConnectionFailedListener, LocationListener {
    /* ... */
    override fun onConnected(connectionHint: Bundle?) {
        var locationAvailability = LocationServices.FusedLocationApi.getLocationAvailability(googleApiClient)
        if(locationAvailability.isLocationAvailable) {
            val locationRequest = LocationRequest.create().apply {
                priority = LocationRequest.PRIORITY_HIGH_ACCURACY
                interval = 5000
            }
            LocationServices.FusedLocationApi.requestLocationUpdates(googleApiClient, locationRequest, this)
        } else {
            // Do something when location provider not available
        }
    }
    ...
    override fun onLocationChanged(location: Location?) {
        // Do something when got new current location
    }
}

จากตัวอย่างนี้จะอ่านพิกัดด้วยความแม่นยำสูงสุดทุกๆ 5 วินาที โดยจะส่งค่าที่อ่านไปที่ onLocationChanged(...)

และถ้าอยากให้ Location Services หยุดทำงานก็มีคำสั่ง

removeLocationUpdates(client: GoogleApiClient, listener: LocationListener)

จากตัวอย่างโค๊ดของเจ้าของบล็อก ถ้าอยากจะหยุดทำงานก็ใช้คำสั่งแบบนี้

removeLocationUpdates(googleApiClient, this)

ต้องหยุด Location Services เมื่อแอปพลิเคชันหยุดทำงานหรือไม่?

เนื่องจาก Location Services จะทำงานเรื่อยๆตามที่กำหนดไว้ ดังนั้นถ้าแอปพลิเคชันหยุดทำงาน Location Services ก็ควรจะหยุดทำงานเช่นกันใช่มั้ยล่ะครับ

แต่สำหรับ Location Services ไม่จำเป็นต้องทำแบบนั้น เพราะว่าตัวมันเองผูกเข้ากับ Google API Client อยู่แล้ว ถ้า Google API Client หยุดทำงาน มันก็จะหยุดทำงานเช่นกัน

ดังนั้นคำสั่ง disconnect ของ Google API Client ที่ใส่ไว้ใน onStop ก็จะเป็นการสั่งให้ Location Services หยุดทำงานไปในตัว

override fun onStart() {
    super.onStart()
    googleApiClient.connect()
}

override fun onStop() {
    super.onStop()
    if (googleApiClient.isConnected) {
        googleApiClient.disconnect()
    }
}

และเมื่ออยากให้กลับมาทำงานใหม่อีกครั้งก็ใช้แค่เพียงคำสั่ง connect() เท่านี้ Location Services ก็จะทำงานทันที เพราะในตัวอย่างนี้กำหนดให้ Location Services ทำงานเมื่อ Google API Client เชื่อมต่อเรียบร้อยแล้วนั่นเอง

ในกรณีที่อยากให้ Location Services ทำงานเมื่อต้องการ เช่น กดปุ่ม ก็แค่ย้ายคำสั่ง Location Request และ Location Services ไปไว้ในที่ที่ต้องการแทน

กำหนด Permission แค่ตัวไหนตัวหนึ่งตามที่จะใช้งานพอ

จากที่เจ้าของบล็อกบอกในตอนแรกว่า Permission สำหรับ Location Provider มีด้วยกัน 2 ตัว แต่ทว่าไม่ต้องประกาศทั้งคู่ ให้ดูว่า Priority ที่กำหนดใน Location Request เป็นแบบไหน

PRIORITY_HIGH_ACCURACY ให้ประกาศ Permission เป็น

<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />

PRIORITY_BALANCED_POWER_ACCURACY, PRIORITY_LOW_POWER และ PRIORITY_NO_POWER ให้ประกาศ Permission เป็น

<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />

ถ้าในแอปพลิเคชันมีการใช้งาน Location Provider ด้วย Priority ทั้งสองแบบก็สามารถประกาศ Permission คู่กันไว้ได้เลย

อ่านค่าพิกัดได้แล้วก็เอาไปใช้งานได้เลย

จากที่บอกไปว่าเมื่อได้ค่าพิกัดแล้ว Method ที่ชื่อว่า onLocationChanged(...) จะทำงานทันที โดยจะมีคลาส Location ให้ดึงข้อมูลไปใช้งาน

override fun onLocationChanged(location: Location?) {
    var provider: String = location.provider
    var latitude: Double = location.latitude
    var longitude: Double = location.longitude
    var altitude: Double = location.altitude
    var accuracy: Float = location.accuracy
    var bearing: Float = location.bearing
    var speed: Float = location.speed
    var time: Long = location.time
}

คลาส Location ไม่ได้มีแค่ค่าละติจูดหรือลองติจูดให้ใช้งานเท่านั้น แต่ยังมีข้อมูลอื่นๆที่ได้จาก Location Provider อีกด้วย เช่น ระดับความสูงจากผิวน้ำทะเล, ทิศทางอิงตามเข็มทิศ และความเร็วในการเคลื่อนที่ เป็นต้น

ดังนั้นโค้ดทั้งหมดก็จะออกมาประมาณนี้

// MainActivity.kt
class MainActivity : AppCompatActivity, GoogleApiClient.ConnectionCallbacks,
        GoogleApiClient.OnConnectionFailedListener, LocationListener {
    private lateinit val googleApiClient GoogleApiClient

    override fun onCreate(savedInstanceState: Bundle?) {
        /* ... */
        // Create Google API Client instance
        googleApiClient = GoogleApiClient.Builder(this)
                .addApi(LocationServices.API)
                .addConnectionCallbacks(this)
                .addOnConnectionFailedListener(this)
                .build()
    }

    override fun onStart() {
        /* ... */
        // Connect to Google API Client
        googleApiClient.connect()
    }

    override fun onStop() {
        /* ... */
        if (googleApiClient.isConnected) {
            // Disconnect Google API Client if available and connected
            googleApiClient.disconnect()
        }
    }

    @Override
    public void onConnected(Bundle bundle) {
        // Do something when connected with Google API Client
        val locationAvailability = LocationServices.FusedLocationApi.getLocationAvailability(googleApiClient)
        if (locationAvailability.isLocationAvailable) {
            // Call Location Services
            val locationRequest = LocationRequest.create().apply {
                priority = LocationRequest.PRIORITY_HIGH_ACCURACY
                interval = 5000
            }
            LocationServices.FusedLocationApi.requestLocationUpdates(googleApiClient, locationRequest, this)
        } else {
            // Do something when Location Provider not available
        }
    }

    override fun onConnectionSuspended(cause: Int) {
        // Do something when Google API Client connection was suspended
    }

    override fun onConnectionFailed(result: ConnectionResult) {
        // Do something when Google API Client connection failed
    }

    override fun onLocationChanged(location: Location) {
        // Do something when got new current location
        location?let {
            val location = """Latitude : ${location.latitude}
            |Longitude : ${location.longitude}
        """.trimMargin()
        }
    }
}

สรุป

สำหรับ Fused Location Provider API ถือว่าเป็นอีกหนึ่ง API ที่แนะนำให้ลองใช้ เพื่อที่การทำงานของ Location Provider จะได้มีประสิทธิภาพมากขึ้น โดยที่ผู้ที่หลงเข้ามาอ่านไม่จำเป็นต้อง Handle เอง ซึ่งในบทความนี้จะเป็นการแนะนำวิธีการใช้งานเบื้องต้น ดังนั้นลองศึกษาเพิ่มเติมได้จาก Fused Location Provider API [Google Developer] เพื่อให้ใช้งาน Fused Location Provider API ได้ตรงกับงานของผู้ที่หลงเข้ามาอ่าน

Fused Location Provider API | Google Developers
Get location data for your app based on combined signals from the device sensors using a battery-efficient API.

และก็อย่าลืมว่าอาจจะมีกรณีที่ Location Provider ไม่สามารถใช้งานได้ อาจจะเพราะปิดไว้อยู่ ก็ควรให้แอปพลิเคชันแจ้งผู้ใช้ด้วยว่าไม่สามารถใช้งานได้ และถ้าจะให้ดีก็ให้ผู้ใช้สามารถเปิดไปที่หน้าเปิดใช้งาน Location Provider ได้ทันที

ในกรณีที่ Google API Client ไม่สามารถเชื่อมต่อได้ อาจจะเพราะมาจากเครื่องนั้นๆไม่มี Google Play Services (เครื่องที่ไม่มีนี่หายากมากเลยนะ) ดังนั้นในกรณีที่ใช้งาน Fused Location Provider API ไม่ได้ แอปพลิเคชันก็ควรจะต้องทำงานได้อยู่ อาจจะใช้คำสั่ง Location Provider ที่เขียนเองมาใช้งานแทน แต่ถ้าจะให้แนะนำก็ลองใช้ไลบรารีมาช่วยจัดการให้ดีกว่านะ

คำศัพท์กันงง

เนื่องจากมีคำหลายคำที่เจ้าของบล็อกใช้ในบทความนี้ อาจจะทำให้ผู้ที่หลงเข้ามาอ่านบางคนงงว่ามันคืออะไร ดังนั้นเจ้าของบล็อกจึงขออธิบายคำศัพท์เหล่านี้ให้เข้าใจมากขึ้นนะครับ

  • Fused Location Provider API เป็น API จาก Google Play Services ที่ทาง Google ทำออกมาเพื่อให้เรียกใช้งาน Location Provider ได้สะดวกขึ้น
  • Location Provider การเรียกใช้งานให้เครื่องค้นหาพิกัดของตัวเครื่อง (เรียกกันบ้านๆว่า GPS)
  • Location Request  คลาสสำหรับกำหนดรูปแบบการทำงานของ Location Provider
  • Location Services คลาสสำหรับสั่งให้ Location Provider ทำงาน
  • Location Listener เป็น Event Listener สำหรับ Location Services