Android BLE-iBeacon系列(四)iBeacon區(qū)域的進(jìn)入和退出監(jiān)聽(tīng)


干貨分享:Android BLE 框架,簡(jiǎn)單易用,可能是迄今為止功能最全面的
https://github.com/a1anwang/okble


iBeacon系列文章:

\color{red}{注:iBeacon是蘋(píng)果在BLE基礎(chǔ)上的封裝,android系統(tǒng)沒(méi)有提供Beacon相關(guān)的封裝}
\color{red}{本文iBeacon系列代碼是完全按照ios的api來(lái)設(shè)計(jì)的,在APP開(kāi)發(fā)時(shí)android和ios的邏輯是一模一樣的}
Android BLE-iBeacon系列(一)iBeacon介紹
Android BLE-iBeacon系列(二)掃描識(shí)別iBeacon設(shè)備
Android BLE-iBeacon系列(三)iBeacon區(qū)域介紹
Android BLE-iBeacon系列(四)iBeacon區(qū)域的進(jìn)入和退出監(jiān)聽(tīng)
Android BLE-iBeacon系列(五)手機(jī)模擬為iBeacon(待續(xù))


正文

上篇文章說(shuō)道iBeacon區(qū)域,可以由具有相同的uuid來(lái)構(gòu)成;也可以由相同的uuid和相同的major來(lái)構(gòu)成;還可以由相同的uuid,major,minor來(lái)構(gòu)成。這就意味著iBeacon區(qū)域有3個(gè)構(gòu)造方法

public class BeaconRegion {
    private int major=-1;
    private int minor=-1;
    private String uuid;
    @Override
    public String toString() {
        return "BeaconRegion:[uuid:"+uuid+" major:"+major+" minor:"+minor+"]";
    }

    public static BeaconRegion getInstance(String uuid){
        return new BeaconRegion(uuid);
    }


    public static BeaconRegion getInstance(String uuid, int major){
        return new BeaconRegion(uuid,major);
    }


    public static BeaconRegion getInstance(String uuid ,int major,int minor){
        return new BeaconRegion(uuid,major,minor);
    }


    private BeaconRegion(String uuid) {
        super();
        this.uuid = uuid;
    }

    private BeaconRegion(String uuid, int major) {
        super();

        this.uuid = uuid;
        this.major=major;
    }
    private BeaconRegion(String uuid ,int major,int minor){
        super();
        this.major=major;
        this.minor=minor;
        this.uuid=uuid;
    }

    //區(qū)域的唯一標(biāo)識(shí)
    public String getIdentifier(){
        return this.uuid+"_"+this.major+"_"+this.minor;
    }

    public String getUuid() {
        return uuid;
    }

    public int getMajor() {
        return major;
    }

    public int getMinor() {
        return minor;
    }
}

接下來(lái)我定義一個(gè)BeaconRegionManager用來(lái)監(jiān)聽(tīng)Beacon區(qū)域。再定義一個(gè)接口BeaconRegionListener用來(lái)回調(diào)狀態(tài)。

    public interface OKBLEBeaconRegionListener{
        void onEnterBeaconRegion(BeaconRegion beaconRegion);

        void onExitBeaconRegion(BeaconRegion beaconRegion);
    }


public class BeaconRegionManager implements BeaconScanManager.BeaconListener {
    private Context mContext;

    private BeaconScanManager scanManager;//這是系列二文章里寫(xiě)好的Beacon設(shè)備掃描器

    private BeaconListener  scanCallback;//beacon掃描回調(diào)

    private BeaconRegionListener regionListener;
    public BeaconManager(Context context){
        super();
        this.mContext=context;
        scanManager=new BeaconScanManager(mContext);
        scanManager.setBeaconListener(this);
    }
    public void setRegionListener(BeaconRegionListener regionListener){
        this.regionListener=regionListener;
    }

    //開(kāi)始監(jiān)控Beacon區(qū)域的方法
    public void startMonitoringForRegion(BeaconRegion region) {
    }
  
    //停止監(jiān)控Beacon區(qū)域的方法
    public void stopMonitoringForRegion(OKBLEBeaconRegion region){
 
    }
    
    //掃描回調(diào)
    @Override
    public void onScanBeacon(Beacon beacon) {
        Log.e(TAG, " 掃描到beacon設(shè)備:" + beacon.toString());
        //在這里判斷區(qū)域的退出和進(jìn)入
    }

}

定義好之后,接下來(lái)該是怎么實(shí)現(xiàn)了。當(dāng)開(kāi)啟監(jiān)控Beacon區(qū)域的時(shí)候,需要一直掃描周?chē)腂eacon設(shè)備,然后判斷掃描到的Beacon設(shè)備是否屬于 所監(jiān)控區(qū)域的某一個(gè),是的話(huà)就回調(diào)進(jìn)入該區(qū)域,這個(gè)有一個(gè)注意點(diǎn)就是:掃描Beacon設(shè)備的時(shí)候,是一直回調(diào)的,也就是說(shuō)同一個(gè)Beacon設(shè)備會(huì)重復(fù)掃描到,所以要做好進(jìn)入?yún)^(qū)域的回調(diào)判斷,如果已經(jīng)進(jìn)入過(guò)該區(qū)域且未退出,那么不需要再回調(diào)進(jìn)入?yún)^(qū)域。
如何判斷退出某一個(gè)區(qū)域呢?那就是持續(xù)一段時(shí)間之內(nèi)一直沒(méi)有掃描到該區(qū)域里的Beacon設(shè)備,視為退出區(qū)域。我們暫定這個(gè)持續(xù)時(shí)間為20秒,就是說(shuō)當(dāng)進(jìn)入一個(gè)區(qū)域后,在連續(xù)的20s內(nèi)都沒(méi)有再掃描到該區(qū)域的Beacon設(shè)備,則表示我們離開(kāi)了該區(qū)域。
蘋(píng)果ios系統(tǒng)也是采用的20s,這個(gè)時(shí)間是我實(shí)測(cè)的,我用蘋(píng)果手機(jī)監(jiān)控一個(gè)區(qū)域,給Beacon設(shè)備上電,回調(diào)進(jìn)入?yún)^(qū)域,然后我立刻把Beacon設(shè)備電池拔掉,等了有20s左右,ios回調(diào)退出區(qū)域。

接下來(lái)看代碼實(shí)現(xiàn):
首先封裝一層BeaconRegion,命名為RegionObject:

    private class RegionObject{
        boolean hasEntered;//用來(lái)判斷是否已經(jīng)進(jìn)入過(guò)區(qū)域,避免重復(fù)回調(diào)
        BeaconRegion region;
        int regionID;//id,不同的區(qū)域id不一樣

        public RegionObject(OKBLEBeaconRegion region, int regionID) {
            super();
            this.region=region;
            this.regionID=regionID;
        }
    }

實(shí)現(xiàn)監(jiān)控方法:

    private Map<String, RegionObject> monitoringBeaconRegions = new HashMap<String, RegionObject>();//監(jiān)控區(qū)域集合
    private int monitoringBeaconRegionID = 0;//監(jiān)控的iBeacon區(qū)域的id
    public void startMonitoringForRegion(BeaconRegion region) {
        String key = region.getIdentifier();//獲取需要監(jiān)控的區(qū)域的唯一標(biāo)識(shí)
        if (!monitoringBeaconRegions.containsKey(key)) {
            //避免重復(fù)監(jiān)控同一個(gè)區(qū)域
            monitoringBeaconRegionID++;//用一個(gè)遞增的int作為所監(jiān)控區(qū)域的id
            RegionObject regionObject=new RegionObject(region,monitoringBeaconRegionID);

            monitoringBeaconRegions.put(key, regionObject);
            if(!isScanning()){
                startScanBeacon();
            }
        }
    }
    /**
     * 開(kāi)始掃描iBeacon
     */
    public void startScanBeacon(){
        scanManager.stopScan();
        scanManager.startScan();
    }

    public boolean isScanning(){
        return scanManager.isScanning();
    }

當(dāng)掃描到Beacon設(shè)備時(shí),我們就判斷該設(shè)備是否屬于某一個(gè)區(qū)域

    //掃描回調(diào)
    @Override
    public void onScanBeacon(Beacon beacon) {
        Log.e(TAG, " 掃描到beacon設(shè)備:" + beacon.toString());
        //在這里判斷區(qū)域的退出和進(jìn)入
       //這里請(qǐng)大家再次注意一下上面的BeaconRegion的唯一標(biāo)識(shí)的定義,是由uuid,major,minor組成。
       // major,minor默認(rèn)為-1,大家知道BeaconRegion有三個(gè)構(gòu)造方法
      //可以由單獨(dú)的uuid構(gòu)成
      //也可以由uuid+major構(gòu)成
      //還可以由uuid+major+minor構(gòu)成。
        String key=beacon.uuid+"_-1_-1";//這個(gè)對(duì)應(yīng)了由單獨(dú)的uuid構(gòu)成的區(qū)域
        if (monitoringBeaconRegions.containsKey(key)) {
          //正在監(jiān)控這個(gè)區(qū)域
            RegionObject regionObject=monitoringBeaconRegions.get(key);
            handleEnterRegion(regionObject);
        }
        key=beacon.uuid+beacon.major+"_-1";//這個(gè)對(duì)應(yīng)了由uuid+major構(gòu)成的區(qū)域
        if (monitoringBeaconRegions.containsKey(key)) {
          //正在監(jiān)控這個(gè)區(qū)域
            RegionObject regionObject=monitoringBeaconRegions.get(key);
            handleEnterRegion(regionObject);
        }
        key=beacon.uuid+"_"+beacon.major+"_"+beacon.minor;//這個(gè)對(duì)應(yīng)了由uuid+major+minor構(gòu)成的區(qū)域
        if (monitoringBeaconRegions.containsKey(key)) {
          //正在監(jiān)控這個(gè)區(qū)域
            RegionObject regionObject=monitoringBeaconRegions.get(key);
            handleEnterRegion(regionObject);
        }
    }
    private void handleEnterRegion(RegionObject regionObject) {
         if(!regionObject.hasEntered){//避免重復(fù)回調(diào)
            regionObject.hasEntered = true;
            if(regionListener!=null){
                if(isScanning()){
                    regionListener.onEnterBeaconRegion(regionObject.region);
                }
            }
        }   
    }

這樣監(jiān)控區(qū)域的進(jìn)入已經(jīng)完成。監(jiān)控區(qū)域的退出稍微復(fù)雜點(diǎn),因?yàn)樾枰掷m(xù)一段時(shí)間沒(méi)有掃描到才認(rèn)為是退出。那么想法就是,當(dāng)判斷到進(jìn)入?yún)^(qū)域時(shí),立刻開(kāi)啟一個(gè)延時(shí)20秒的方法,當(dāng)這個(gè)延時(shí)方法執(zhí)行到的時(shí)候就認(rèn)為退出了這個(gè)區(qū)域,這里需要注意的是:當(dāng)處于一個(gè)區(qū)域時(shí),是會(huì)不斷的掃描到該區(qū)域的Beacon設(shè)備的,所以一但掃描到Beacon設(shè)備并且Beacon設(shè)備屬于某個(gè)區(qū)域,那么需要重置這個(gè)20秒延遲,取消上一個(gè)延遲,重新開(kāi)一個(gè)延遲。我是用Hanler來(lái)實(shí)現(xiàn)的,請(qǐng)看

    private int regionExitOverTime = 20 * 1000;//20秒,退出區(qū)域的超時(shí)時(shí)間,持續(xù)regionExitOverTime這么長(zhǎng)的時(shí)間內(nèi)沒(méi)有再次掃描到這個(gè)區(qū)域,則視為退出區(qū)域

    private void handleEnterRegion(RegionObject regionObject) {
        handler.removeMessages(regionObject.regionID);//移除超時(shí)時(shí)間后回調(diào)退出區(qū)域的消息
        Message msg = new Message();
        msg.what = regionObject.regionID;
        msg.obj = regionObject.region.getIdentifier();
        handler.sendMessageDelayed(msg,regionExitOverTime);//重新發(fā)送一個(gè)延時(shí)消息,

        if(!regionObject.hasEntered){
            regionObject.hasEntered = true;
            if(regionListener!=null){
                if(okbleScanManager.isScanning()){
                    regionListener.onEnterBeaconRegion(regionObject.region);
                }
            }
        }
    }
    Handler handler= new Handler(Looper.getMainLooper()){
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            String key = (String) msg.obj;
            //收到消息,表示已經(jīng)持續(xù)了一段時(shí)間沒(méi)有掃描到區(qū)域內(nèi)的beacon了,視為退出區(qū)域
            if (monitoringBeaconRegions.containsKey(key)) {
                RegionObject regionObject= monitoringBeaconRegions.get(key);
                BeaconRegion beaconRegion =regionObject.region;

                regionObject.hasEntered = false;
                if(regionListener!=null){
                    if(okbleScanManager.isScanning()){
                        regionListener.onExitBeaconRegion(beaconRegion);
                    }
                }
            }
        }
    };

到此,完成iBeacon區(qū)域的進(jìn)入和退出監(jiān)聽(tīng),所有的功能可以直接看文章開(kāi)頭提到的框架,直接使用。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容