10 April 2015

รู้จักการใช้งาน Deep Link (URL Scheme) กับ Android Application

Updated on


        ผู้ที่หลงเข้ามาอ่านเคยเห็น URL แบบนี้กันมั้ย?

        • fb://page?id=xxxxxxxxxx
        • birdland://home
        • market://details?id=xxxxxx
        • oftp://xxxxxx

        จะเห็นว่า URL เหล่านี้ไม่ได้ขึ้นต้นด้วย http หรือ https และก็ไม่ได้มีไว้สำหรับเปิดหน้าเว็ปด้วย โดย URL เหล่านี้จะเรียกกันว่า Deep Link (อิงตาม Google) หรือ URL Scheme (อิงตามที่เจ้าของบล็อกเรียกกัน)

        แล้วมันมีไว้ทำอะไรกันล่ะ?

        ยกตัวอย่างเช่น
     
        market://details?id=com.google.android.youtube

        ให้ลองกดเปิด URL นี้บนอุปกรณ์แอนดรอยด์ดู (บนคอมไม่ได้รองรับนะจ๊ะ)

        หมายเหตุ - ให้คลิกจากลิ้งโดยตรง อย่าพิมพ์หรือแปะลงใน Address Bar แล้วค่อยกดเปิด เพราะว่าแอพ Web Browser อย่างเช่น Chrome มันจะดักการทำงานในส่วนนี้ไว้


        จะเห็นว่า URL ดังกล่าวเมื่อกดแล้วจะเปิดหน้าดาวน์โหลดของ YouTube บน Google Play Store ทันที โดยที่ com.google.android.youtube คือชื่อ Package ของ YouTube นั่นเอง

        โดย URL แบบนี้จะนิยมนำมาใช้งานกันอุปกรณ์พกพาเหล่านี้เพิ้อเป็นอีกวิธีหนึ่งที่ทำให้สามารถสั่งเปิดแอพนั้นๆผ่าน URL ได้ในทันที (เช่น หน้าเว็ปมีปุ่มให้กดเพื่อเปิดแอพบนมือถือ เป็นต้น) และสามารถส่งค่า Parameter แบบง่ายๆได้อีกด้วย


แล้วจะทำให้ให้แอพรองรับได้ยังไงล่ะ?

        การทำให้แอพรองรับ Deep Link หรือ URL Scheme บนแอนดรอยด์นั้นทำได้ไม่ยาก เพราะสามารถกำหนดใน Activity นั้นๆได้เลย เมื่อ URL ตรงกับที่กำหนดไว้ก็จะให้แอพนั้นๆเปิด Activity ตามที่กำหนดไว้ขึ้นมาทันที

        จากตัวอย่างในตอนแรก Google Play Store ก็จะดักเอา Parameter จาก URL มาดูว่าเป็นชื่อ Package ของแอพตัวไหนแล้วเปิดหน้าดาวน์โหลดแอพตัวนั้นๆ

        โดย Activity นั้นๆสามารถรับข้อมูล URL มาใช้งานได้ด้วยคำสั่ง


Uri uri = getIntent().getData();

        จะเห็นว่าเป็นคำสั่งจาก Activity โดยตรงเลย ดังนั้นผู้ที่หลงเข้ามาอ่านก็สามารถกำหนด Activity ใดๆก็ได้ให้รองรับ

        แต่เดี๋ยวก่อนนนน เจ้าของบล็อกไม่แนะนำให้กำหนด Activity หลักของแอพให้รองรับ URL โดยตรงนะ (เช่น MainActivity เป็นต้น)

        ทั้งนี้ก็เพราะว่า Activity เหล่านั้น อาจจะไม่ได้ถูกเปิดจาก Deep Link หรือ URL Scheme เสมอไป อาจจะทำงานจากการเปิดใช้งานแอพตามปกติก็ได้ ดังนั้นก็จะทำให้ Uri ที่ได้มีค่าเป็น Null ได้ ผู้ที่หลงเข้ามาอ่านจะ Handle เรื่อง Null เองเลยก็ได้

       แต่ถ้าอยากจะลดความวุ่นวายของโค๊ดใน Activity หลัก ก็ให้สร้าง Activity สำหรับ URL นั้นๆขึ้นมาโดยเฉพาะ แล้วจัดการกับ URL ให้เรียบร้อย แล้วค่อย Intent ไปยัง Activity หลักๆอีกที

        ดังนั้นเจ้าของบล็อกจึงสร้าง Activity ที่ชื่อว่า SchemeActivity ขึ้นมา โดยไฟล์นี้จะไม่มี Layout เพราะจะ Intent ทันทีที่ทำงาน

SchemeActivity.java
package com.akexorcist.urlscheme;

import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;

public class SchemeActivity extends Activity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        Intent intent = new Intent(this, MainActivity.class);
        startActivity(intent);

        finish();
    }
}


        และหัวใจสำคัญจะอยู่ที่ตอนประกาศ Activity ใน Android Manifest

AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.akexorcist.urlscheme" >

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:theme="@style/AppTheme" >
        <activity
            android:name=".MainActivity"
            android:label="@string/app_name" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

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

        <activity android:name=".SchemeActivity" >
            <intent-filter>
                <action android:name="android.intent.action.VIEW" />
                <category android:name="android.intent.category.DEFAULT" />
                <category android:name="android.intent.category.BROWSABLE" />
                <data android:scheme="yay" android:host="call" />
            </intent-filter>
        </activity>

    </application>

</manifest>



        ต้องกำหนดค่าใน <intent-filter> แบบนี้นะ ถึงจะใช้งานกับ Deep Link หรือ URL Scheme ได้ และสามารถกำหนดค่าต่างๆนอกเหนือจากนี้เพิ่มเติมได้ตามใจชอบ


มันดูยังไงล่ะเนี่ย?

        ให้สังเกตที่ android:scheme ที่เจ้าของบล็อกกำหนดไว้ว่า yay และ android:host ที่กำหนดไว้ว่า call

        จากตัวอย่างนี้เจ้าของบล็อกกำหนดให้ SchemeActivity ดัก URL ว่า yay://call นั่นเอง เมื่อเข้า URL ตัวนี้บนมือถือดู

        yay://call

        มันก็จะเด้งเข้า SchemeActivity ทันที ซึ่งในนั้นก็จะสั่งให้เปิดหน้า MainActivity อีกที จึงดูเสมือนว่าเปิด URL แล้วเปิดหน้า MainActivity ขึ้นมาทันที


        ถ้าไม่เชื่อว่ามันเข้า SchemeActivity ก่อน ก็ลองแสดง Log หรือ Toast ใน SchemeActivity ดูก็ได้


        และสำหรับ <data> ที่อยู่ใน Android Manifest จะกำหนดแค่ android:scheme อย่างเดียวก็ได้

<activity android:name=".SchemeActivity" >
    <intent-filter>
        <action android:name="android.intent.action.VIEW" />
        <category android:name="android.intent.category.DEFAULT" />
        <category android:name="android.intent.category.BROWSABLE" />
        <data android:scheme="yay" />
    </intent-filter>
</activity>

        เวลาที่เปิด URL อย่างเช่น yay://hello, yay://public หรือ yay://home ก็ตาม มันก็จะทำงานเสมอ เพราะมันอิงที่ android:scheme เพียงอย่างเดียว

        แต่เจ้าของบล็อกก็ไม่ค่อยแนะนำเท่าไร เพราะว่ามันมีโอกาสเกิดการซ้ำซ้อนกันได้ค่อนข้างสูง ดังนั้นการใช้ android:host ด้วยจะช่วยให้แยก  URL แต่ละชุดออกจากกันได้ เช่น

<activity android:name=".SchemeActivity" >
    <intent-filter>
        <action android:name="android.intent.action.VIEW" />
        <category android:name="android.intent.category.DEFAULT" />
        <category android:name="android.intent.category.BROWSABLE" />
        <data android:scheme="app" android:host="start" />
    </intent-filter>
</activity>

<activity android:name=".SchemeActivity2" >
    <intent-filter>
        <action android:name="android.intent.action.VIEW" />
        <category android:name="android.intent.category.DEFAULT" />
        <category android:name="android.intent.category.BROWSABLE" />
        <data android:scheme="app" android:host="delete" />
    </intent-filter>
</activity>

        จากตัวอย่างข้างบนนี้ก็จะเห็นได้ว่า URL สามารถใช้ได้มากกว่า 1 Activity ได้ ดังนั้นจึงประยุกต์ใช้ URL ที่แตกต่างกันเพื่อเปิด Activity คนละตัวได้ โดยใช้ android:host เป็นหัวใจสำคัญหลักในการแยกการทำงาน


รับค่าจาก Parameter ใน URL ยังไง


        จากตัวอย่าง Google Play Store ที่อธิบายในตอนแรกสุด จะเห็นว่า market คือ Scheme ส่วน detail คือ Host และก็จะเห็นว่ามี Parameter ต่อท้ายด้วยอยู่ด้วย เป็น id=com.google.android.youtube โดยที่ id คือ Key ส่วน com.google.android.youtube คือ Value


        ยกตัวอย่าง URL ว่าเป็น

        app://play?id=10&name=SleepingForLess&os=android

        เจ้าของบล็อกต้องกำหนด <activity> ใน Android Manifest ให้รองรับ URL จากตัวอย่างดังนี้

<activity android:name=".SchemeActivity2" >
    <intent-filter>
        <action android:name="android.intent.action.VIEW" />
        <category android:name="android.intent.category.DEFAULT" />
        <category android:name="android.intent.category.BROWSABLE" />
        <data android:scheme="app" android:host="play" />
    </intent-filter>
</activity>

        ทีนี้ดูที่ Parameter ต่อ จะเห็นว่ามีการส่งค่ามา 3 ตัว คือ

        Key : id
        Value : 10

        Key : name
        Value : SleepingForLess

        Key : OS
        Value : android

        เวลาจะดึงข้อมูลเหล่านี้ไปใช้งาน ก็จะต้องดึงจากใน Activity ที่ถูกเรียกด้วย URL จากที่ยกตัวอย่าง

        ดังนั้นเจ้าของบล็อกก็จะต้องเพิ่มคำสั่งใน SchemeActivity ดังนี้

Uri uri = getIntent().getData();


เมื่อลองแสดง uri ออกทาง Log ก็จะได้ข้อมูลดังนี้

Log.i("Check", "Data : " + uri);

        ผลลัพธ์ที่ได้

Data : app://play?id=10&name=SleepingForLess&os=android

ถ้าอยากรู้ว่า uri มี Key อะไรอยู่ข้างในบ้าง

for(String key : uri.getQueryParameterNames()) {
    Log.i("Check", "Key : " + key);
}

        ผลลัพธ์ที่ได้

Key : id
Key : name
Key : os

ถ้าอยากรู้ว่า Key ตัวนั้นๆมีข้อมูลอะไรอยู่บ้าง

String id = uri.getQueryParameter("id");
String name = uri.getQueryParameter("name");
String os = uri.getQueryParameter("os");

Log.i("Check", "ID : " + id);
Log.i("Check", "Name : " + name);
Log.i("Check", "OS : " + os);

        ผลลัพธ์ที่ได้

ID : 10
Name : SleepingForLess
OS : android

ถ้าอยากรู้ว่า Scheme, Host (Authority) และ Parameter มีค่าเป็นอะไร

Log.i("Check", "Parameter : " + uri.getQuery() );
Log.i("Check", "Authority : " + uri.getAuthority());
Log.i("Check", "Scheme : " + uri.getScheme());

        ผลลัพธ์ที่ได้

Scheme : app
Authority : play
Parameter : id=10&name=SleepingForLess&os=android

กรณีที่ Parameter เป็น String Array ล่ะ?

        ยกตัวอย่างเช่น

        app://play?ver=JellyBean&ver=KitKat&ver=Lollipop

        จะเห็นว่ามี Key ที่ชื่อว่า version ซ้ำกันอยู่ 3 ตัวด้วยกัน ซึ่ง Key ตัวนี้จะถูกมองเป็น Array ดังนั้นเวลาเอาค่ามาใช้ก็จะต้องเรียกออกมาในรูปของ Array เช่นกัน

List<String> listVersion = uri.getQueryParameters("ver");
for(String version : listVersion) {
    Log.i("Check", "Version : " + version);
}

        ผลลัพธ์ที่ได้

Version : JellyBean
Version : KitKat
Version : Lollipop

ถ้าอยากใช้ใน Activity หลักโดยตรง

<activity
    android:name=".MainActivity"
    android:label="@string/app_name" >
    <intent-filter>
        <action android:name="android.intent.action.MAIN" />
        <action android:name="android.intent.action.VIEW" />
    
        <category android:name="android.intent.category.LAUNCHER" />
        <category android:name="android.intent.category.DEFAULT" />
    
        <category android:name="android.intent.category.BROWSABLE" />
        <data android:scheme="app" android:host="play" />
    </intent-filter>
</activity>

        จากเดิม MainActivity จะมีแค่ Action Main กับ Category Launcher เจ้าของบล็อกก็ได้เพิ่มตัวอื่นๆที่ต้องใช้สำหรับ URL นั้นๆเข้าไปจนครบ

        แต่อย่างที่บอกในตอนแรกว่า ถ้าจะทำใน Activity หลักโดยตรงแบบนี้ ให้จัดการกับ Uri ให้ดีๆ เพราะมันจะมีโอกาสที่มีค่าเป็น Null ได้

public class MainActivity extends ActionBarActivity {

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

        Uri uri = getIntent().getData();
        if(uri != null) {
            // Do something here when this activity was called by url scheme
        }
    }
}


ประยุกต์ไปใช้เป็น Secret Code เวลากดเบอร์โทรออกได้ด้วยนะ

        เรื่องนี้เจ้าของบล็อกได้เขียนบทความไปพักใหญ่ๆแล้ว เป็นการใช้ Deep Link หรือ URL Scheme เพื่อดักการกดรหัสโทรออกพิเศษๆ อย่าง *#*#1234#*#* เป็นต้น โดยจะดัก Event ที่เกิดขึ้นบน Broadcast Receiver แทน

        [Android Code] ใส่รหัสลับสำหรับเปิดแอปพลิเคชันด้วย Secret Code

สรุป

        Deep Link หรือ URL Scheme เป็นอีกวิธีหนึ่งที่จะช่วยให้สามารถสั่งเปิดแอพภายในเครื่องได้โดยผ่าน URL บนหน้าเว็ป อีกทั้งยังสามารถส่งข้อมูลเบื้องต้นได้ด้วย จึงทำให้สามารถนำไปประยุกต์ใช้งานได้ แต่ทว่าเมื่อใดที่แอพนั้นๆไม่ได้ติดตั้ง ก็จะไม่มีผลใดๆกับเครื่องนั้นๆเลย


        สำหรับผู้ที่หลงเข้ามาอ่านคนใดต้องการไฟล์ตัวอย่าง สามารถดาวน์โหลดได้จาก
        • Sleeping For Less
        • Google Drive