雙服務喚醒解決android系統在鎖屏條件下無法繼續定位

一、簡介

之前公司在app中以android5.0爲適用版本添加了軌跡功能用於記錄用戶健走路線。由於現在用戶手機系統普遍更新到android8.0甚至更高。有一些問題就凸顯出來,最突出的問題便是android系統現有版本在鎖屏條件下無法繼續定位。用戶如果使用必須要保持手機前臺亮屏。這樣用戶體驗就非常差了。所以新一輪的優化也很有必然了。

道長看過一些方案後,最後選擇使用的雙服務喚醒方法實現。道長這篇博客重點是雙服務喚醒的實現以及一些需要注意的地方。大佬的博客列舉了幾種方案以及實現方法,十分詳細,希望可以轉發擴散,讓更多小夥伴解決自己的問題:https://www.jianshu.com/p/956cbba64c53

二、實現步驟

2.1創建AIDL文件

首先這兩個AIDL文件必須是創建的(copy是沒有靈魂的),然後創建位置要對。如下圖所示:
在這裏插入圖片描述

內容如下:

interface ILocationHelperServiceAIDL {

    /**
    * 定位service綁定完畢後通知helperservice自己綁定的notiId
    * @param notiId 定位service的notiId
    */
    void onFinishBind(int notiId);
}
interface ILocationServiceAIDL {

    /** 當其他服務已經綁定時調起 */
    void onFinishBind();
}

2.2回調接口

public interface IWifiAutoCloseDelegate {

    boolean isUseful(Context context);

    void initOnServiceStarted(Context context);

    /**
     * 定位成功時,如果移動網絡無法訪問,而且屏幕是點亮狀態,則對狀態進行保存
     */
    void onLocateSuccess(Context context, boolean isScreenOn, boolean isMobileable);

    /**
     * 對定位失敗情況的處理
     */
    void onLocateFail(Context context, int errorCode, boolean isScreenOn, boolean isWifiable);

}

2.3定位服務實現

實現的邏輯大概爲:啓動LocationService開啓定位,定位成功後在手機添加一個前臺的通知,讓通知的優先級儘可能的提高。在鎖屏後,powermanager判斷獲取如果定位失敗喚醒服務。
1.定位服務基類,代碼如下:

public class NotificationService extends Service {


    private static int NOTICE_ID = 12138;
    private final String mHelperServiceName = "com.yushan.background.locationservice.LocationHelperService";
    private Utils.CloseServiceReceiver mCloseReceiver;
    public Binder mBinder;
    private ILocationHelperServiceAIDL mHelperAIDL;
    private ServiceConnection connection;

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        mCloseReceiver = new Utils.CloseServiceReceiver(this);
        registerReceiver(mCloseReceiver, Utils.getCloseServiceFilter());
        return START_STICKY;
    }

    @Override
    public void onDestroy() {
        if (mCloseReceiver != null) {
            unregisterReceiver(mCloseReceiver);
            mCloseReceiver = null;
        }

        super.onDestroy();
    }

    /**
     * 觸發利用notification增加進程優先級
     */
    protected void applyNotiKeepMech() {
        startForeground(NOTICE_ID, Utils.buildNotification(getBaseContext()));
        startBindHelperService();
    }

    public void unApplyNotiKeepMech() {
        stopForeground(true);
    }

    public class LocationServiceBinder extends ILocationServiceAIDL.Stub {

        @Override
        public void onFinishBind(){
        }
    }

    private void startBindHelperService() {
        connection = new ServiceConnection() {

            @Override
            public void onServiceDisconnected(ComponentName name) {
                //doing nothing
            }

            @Override
            public void onServiceConnected(ComponentName name, IBinder service) {
                ILocationHelperServiceAIDL l = ILocationHelperServiceAIDL.Stub.asInterface(service);
                mHelperAIDL = l;
                try {
                    l.onFinishBind(NOTICE_ID);
                } catch (RemoteException e) {
                    e.printStackTrace();
                }
            }
        };
        Intent intent = new Intent();
        intent.setAction(mHelperServiceName);
        intent.setPackage("com.wanbu.dascom");
        Intent explicitIntent = Utils.getExplicitIntent(getApplicationContext(), intent);
        bindService(explicitIntent, connection, Service.BIND_AUTO_CREATE);
    }

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        if (mBinder == null) {
            mBinder = new LocationServiceBinder();
        }
        return mBinder;
    }

}

2.定位服務類,代碼如下:

public class LocationService extends NotificationService {

    private AMapLocationClient mLocationClient;
    private AMapLocationClientOption mLocationOption;
    private IWifiAutoCloseDelegate mWifiAutoCloseDelegate = new WifiAutoCloseDelegate();
    private boolean mIsWifiCloseable = false;

    AMapLocationListener locationListener = new AMapLocationListener() {

        @Override
        public void onLocationChanged(AMapLocation aMapLocation) {
            //發送結果的通知
            sendLocationBroadcast(aMapLocation);

            if (!mIsWifiCloseable) {
                return;
            }

            if (aMapLocation.getErrorCode() == AMapLocation.LOCATION_SUCCESS) {
                mWifiAutoCloseDelegate.onLocateSuccess(getApplicationContext(), PowerManagerUtil.getInstance().isScreenOn(getApplicationContext()), NetUtil.getInstance().isMobileAva(getApplicationContext()));
            } else {
                mWifiAutoCloseDelegate.onLocateFail(getApplicationContext() , aMapLocation.getErrorCode() , PowerManagerUtil.getInstance().isScreenOn(getApplicationContext()), NetUtil.getInstance().isWifiCon(getApplicationContext()));
            }
        }

        private void sendLocationBroadcast(AMapLocation aMapLocation) {
            if (null != aMapLocation) {
                Intent mIntent = new Intent(LocationChangBroadcastReceiver.RECEIVER_ACTION);
                mIntent.putExtra(LocationChangBroadcastReceiver.RECEIVER_DATA, aMapLocation);
                sendBroadcast(mIntent);
            }
        }
    };

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        super.onStartCommand(intent, flags, startId);
        //開啓利用notification提高進程優先級的機制
        applyNotiKeepMech();
        if (mWifiAutoCloseDelegate.isUseful(getApplicationContext())) {
            mIsWifiCloseable = true;
            mWifiAutoCloseDelegate.initOnServiceStarted(getApplicationContext());
        }

        startLocation();
        return START_STICKY;
    }

    @Override
    public void onDestroy() {
        unApplyNotiKeepMech();
        stopLocation();
        super.onDestroy();
    }

    void startLocation() {
        stopLocation();

        if (null == mLocationClient) {
            mLocationClient = new AMapLocationClient(this.getApplicationContext());
        }

        mLocationOption = new AMapLocationClientOption();
        mLocationOption.setLocationMode(AMapLocationClientOption.AMapLocationMode.Device_Sensors);
        // 使用連續
        mLocationOption.setOnceLocation(false);
        mLocationOption.setLocationCacheEnable(false);
        // 每2秒定位一次
        mLocationOption.setInterval(2 * 1000);
        // 地址信息
        mLocationOption.setNeedAddress(true);
        mLocationClient.setLocationOption(mLocationOption);
        mLocationClient.setLocationListener(locationListener);
        mLocationClient.startLocation();
    }

    void stopLocation() {
        if (null != mLocationClient) {
            mLocationClient.stopLocation();
        }
    }
  
}

3.定位守護類,代碼如下

public class LocationHelperService extends Service {
    private Utils.CloseServiceReceiver mCloseReceiver;
    private HelperBinder mBinder;
    private ServiceConnection mInnerConnection;
    
    @Override
    public void onCreate() {
        super.onCreate();
        startBind();
        mCloseReceiver = new Utils.CloseServiceReceiver(this);
        registerReceiver(mCloseReceiver, Utils.getCloseServiceFilter());
    }

    @Override
    public void onDestroy() {
        if (mInnerConnection != null) {
            unbindService(mInnerConnection);
            mInnerConnection = null;
        }

        if (mCloseReceiver != null) {
            unregisterReceiver(mCloseReceiver);
            mCloseReceiver = null;
        }

        super.onDestroy();
    }

    
    private void startBind() {
        final String locationServiceName = "com.yushan.background.locationservice.LocationService";
        mInnerConnection = new ServiceConnection() {

            @Override
            public void onServiceDisconnected(ComponentName name) {
                Intent intent = new Intent();
                intent.setAction(locationServiceName);
                startService(Utils.getExplicitIntent(getApplicationContext(), intent));
            }

            @Override
            public void onServiceConnected(ComponentName name, IBinder service) {
                ILocationServiceAIDL l = ILocationServiceAIDL.Stub.asInterface(service);
                try {
                    l.onFinishBind();
                } catch (RemoteException e) {
                    e.printStackTrace();
                }
            }
        };

        Intent intent = new Intent();
        intent.setAction(locationServiceName);
        intent.setPackage("com.wanbu.dascom");
        bindService(Utils.getExplicitIntent(getApplicationContext(), intent), mInnerConnection, Service.BIND_AUTO_CREATE);
    }



    private class HelperBinder extends ILocationHelperServiceAIDL.Stub {

        @Override
        public void onFinishBind(int notiId) throws RemoteException {
            startForeground(notiId, Utils.buildNotification(LocationHelperService.this.getApplicationContext()));
            stopForeground(true);
        }
    }

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        if (mBinder == null) {
            mBinder = new HelperBinder();
        }

        return mBinder;
    }
}

4.添加全局廣播
由於道長公司app是組件化框架,在當前模塊的Application註冊全局廣播就可以實現喚醒服務。

public class HealthApplication extends BaseApplication {

    private static LocationChangBroadcastReceiver locationChangBroadcastReceiver;

    public HealthApplication(Application application, int tinkerFlags, boolean tinkerLoadVerifyFlag, long applicationStartElapsedTime, long applicationStartMillisTime, Intent tinkerResultIntent) {
        super(application, tinkerFlags, tinkerLoadVerifyFlag, applicationStartElapsedTime, applicationStartMillisTime, tinkerResultIntent);
    }

    public static LocationChangBroadcastReceiver getlocationChangeBoardcase() {
        if (null == locationChangBroadcastReceiver) {
            locationChangBroadcastReceiver = new LocationChangBroadcastReceiver();
        }
        Log.e("yushan","一切有我");
        return locationChangBroadcastReceiver;
    }
}

這個方案由道長親測可以。小米手機後臺運行鎖屏可能會被殺死,但是打開自啓動後可以長時間運行,但是重新喚醒期間不能定位,期間會丟失部分數據。華爲手機沒有問題。其他機型小夥伴們可以測試一下。

小米手機(6.0.1)app後臺息屏運行時間30s(未打開自啓動)
	app後臺息屏運行時間不確定(5小時)(打開自啓動)
	app前臺息屏運行時間超長(大於5個小時,具體未測出)(未打開自啓動)

華爲手機(8.0.0)app後臺息屏運行時間大於20小時(未上軟件鎖)
	app後臺息屏運行時間(打開軟件鎖)
	app前臺息屏運行時間(未上軟件鎖)

由於道長是在公司app上實現的,代碼無法分享。如果有人需要的話可以去上面大佬博客下載Demo,如果還是不明白的話可以留言。希望這篇博客給小夥伴們解決問題的思路。(這個方案暫時無法在android9.0及上實現)

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章