Android鎖屏無法繼續(xù)定位問題

產(chǎn)生問題的原因:
手機(jī)鎖屏后财饥,Android系統(tǒng)為了省電以及減少CPU消耗案铺,在一段時(shí)間后會將手機(jī)進(jìn)入休眠狀態(tài)。此時(shí)的服務(wù)以及線程等都會停止闸迷。

最近就這個(gè)問題,閱讀了很多代碼以及官方文檔俘枫,下面就說下最近都嘗試過的方式腥沽,可能其中有些您實(shí)現(xiàn)了,我這邊沒實(shí)現(xiàn)鸠蚪,望見諒今阳。本文采用的高德定位。

一茅信、PowerManager.WakeLock
(1)直接強(qiáng)制當(dāng)前頁面cpu運(yùn)行

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

private PowerManager pm;
    private PowerManager.WakeLock wakeLock;

    @Override
    public void onCreate() {
        super.onCreate();
        //創(chuàng)建PowerManager對象
        pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
        //保持cpu一直運(yùn)行盾舌,不管屏幕是否黑屏
        wakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "CPUKeepRunning");
        wakeLock.acquire();
    }

    @Override
    public void onDestroy() {
        wakeLock.release();
        super.onDestroy();
    }

這個(gè)寫法我表示并沒有什么用,并不能強(qiáng)制cpu持續(xù)運(yùn)行汹押。

(2)WakefulBroadcastReceiver

public class WLWakefulReceiver extends WakefulBroadcastReceiver {
    @Override
    public void onReceive(Context context, Intent intent) {
        //
    String extra = intent.getStringExtra("msg");

     Intent serviceIntent = new Intent(context, MyIntentService.class);
     serviceIntent.putExtra("msg", extra);
     startWakefulService(context, serviceIntent);
    }
}

 <receiver android:name=".WLWakefulReceiver" >
            <intent-filter>
                <action android:name="startlocation" />
            </intent-filter>
        </receiver>

WakefulBroadcastReceiver 內(nèi)部的原理也是PowerManager矿筝,注冊廣播時(shí)8.0的請動(dòng)態(tài)注冊,靜態(tài)沒有用棚贾。廣播注冊完了之后窖维,寫一個(gè)服務(wù)用來與廣播互動(dòng)。

public class MyIntentService extends IntentService {
    public MyIntentService() {
        super("MyIntentService");
    }

    @Override
    public void onCreate() {
        super.onCreate();
    }

    @Override
    protected void onHandleIntent(@Nullable final Intent intent) {
        //子線程中執(zhí)行
       Log.i("MyIntentService", "onHandleIntent");
              String extra = intent.getStringExtra("msg");
              new Thread(new Runnable() {
                  @Override
                  public void run() {
                      LocationUtil.getInstance().startLocation(LocationUtil.NULL, new LocationUtil.OnLocationBack() {
                          @Override
                          public void back(AMapLocation aMapLocation, String backString) {
                              Log.e("定位結(jié)果", aMapLocation.getAddress()+"");
                               定位結(jié)果操作妙痹,我這邊是把定位的時(shí)候保存到數(shù)據(jù)庫里面铸史。
                          }
                      });
                  }
              }).start();

              Log.i("MyIntentService", "onHandleIntent:"+extra);
       //調(diào)用completeWakefulIntent來釋放喚醒鎖。
//       WLWakefulReceiver.completeWakefulIntent(intent);
    }
}


   <service android:name=".MyIntentService"></service>
  注冊

LocationUtil.getInstance().startLocation是我封裝的高德的定位怯伊。保證完整性琳轿,我把高德定位的封裝也貼上。

public class LocationUtil implements AMapLocationListener {
    private static LocationUtil locationUtil;
    private AMapLocationClient mLocationClient = null;
    private AMapLocationClientOption mLocationOption = null;
    private OnLocationBack onLocationBack;
    private OnLocationTrain onLocationTrain;
    public static final String NULL = "null";
    private String differenceFlag = "";
    private String latitude, longitude, cityNameString, HotelCityCode;

    private LocationUtil() {

    }

    public static LocationUtil getInstance() {
        if (locationUtil == null) {
            synchronized (LocationUtil.class) {
                if (locationUtil == null) {
                    locationUtil = new LocationUtil();
                }
            }
        }
        return locationUtil;
    }

    private void init() {
        mLocationClient = new AMapLocationClient(HMMyApplication.context);
        mLocationOption = new AMapLocationClientOption();
        //設(shè)置定位模式為AMapLocationMode.Hight_Accuracy耿芹,高精度模式崭篡。
        mLocationOption.setLocationMode(AMapLocationMode.Hight_Accuracy);
        //設(shè)置定位間隔,單位毫秒,默認(rèn)為2000ms
        mLocationOption.setInterval(5000);
        //設(shè)置是否只定位一次,默認(rèn)為false
        mLocationOption.setOnceLocation(false);
        //返回最近3s內(nèi)精度最高的一次定位結(jié)果。
        mLocationOption.setOnceLocationLatest(false);
        //設(shè)置是否返回地址信息(默認(rèn)返回地址信息)
        mLocationOption.setNeedAddress(true);
        //單位是毫秒吧秕,默認(rèn)30000毫秒琉闪,建議超時(shí)時(shí)間不要低于8000毫秒。
        mLocationOption.setHttpTimeOut(20000);
        //關(guān)閉緩存機(jī)制
        mLocationOption.setLocationCacheEnable(false);
        //給定位客戶端對象設(shè)置定位參數(shù)
        mLocationClient.setLocationOption(mLocationOption);
        mLocationClient.setLocationListener(this);
    }

    public void startLocation(String differenceFlag, OnLocationBack onLocationBack) {
        init();
        mLocationClient.startLocation();//開始
        this.onLocationBack = onLocationBack;
        this.differenceFlag = differenceFlag;
        Log.e("開始定位","開始定位");
    }

    public void startLocationTrain(String differenceFlag, OnLocationTrain onLocationTrain) {
        init();
        mLocationClient.startLocation();//開始
        this.onLocationTrain = onLocationTrain;
        this.differenceFlag = differenceFlag;
        Log.e("開始定位","開始定位");
    }

    public void stopLocation() {
        if (null == mLocationClient) {
            return;
        }
        mLocationClient.unRegisterLocationListener(this);
        mLocationClient.stopLocation();//關(guān)閉
        mLocationClient.onDestroy();//銷毀
        mLocationClient = null;
        Log.e("開始定位","開始定位");
    }


    @Override
    public void onLocationChanged(AMapLocation aMapLocation) {
        Log.e("定位到當(dāng)前位置:  " , aMapLocation.getAddress());
        if (aMapLocation == null) {
            onLocationTrain.LocationFail("定位失敗");
            return;
        }
        if (null != aMapLocation.getCity()
                && !"null".equals(aMapLocation.getCity())
                && !"".equals(aMapLocation.getCity())
                && 0 != aMapLocation.getLatitude()) {
            cityNameString = aMapLocation.getCity();
            latitude = "" + aMapLocation.getLatitude();
            longitude = "" + aMapLocation.getLongitude();
            saveLocation(aMapLocation);
        } else {
            onLocationTrain.LocationFail("定位失敗");
            return;
        }

    }

    public interface OnLocationBack {
        void back(AMapLocation aMapLocation, String backString);
    }

    public interface OnLocationTrain {
        void back(AMapLocation aMapLocation, String backString);
        void LocationFail(String error);
    }

    private void saveLocation(AMapLocation aMapLocation) {
        switch (differenceFlag) {
            case NULL:
                onLocationBack.back(aMapLocation, "返回的是定位到的所有信息");
                break;
        }
    }
}

使用 記得把權(quán)限加上砸彬。

 Intent intent = new Intent("startlocation");
                intent.putExtra("msg", "定位定位定位");
                sendBroadcast(intent);

就這個(gè)寫法而言颠毙,我拿demo測試的時(shí)候,小米砂碉,oppo蛀蜜,高精度gps都可以,gps需要室外定位增蹭,室內(nèi)沒有信號滴某。在適配華為手機(jī)的時(shí)候,有點(diǎn)坑。華為并不行壮池,只支持亮屏偏瓤,之后我用的雙服務(wù)喚醒通訊方式實(shí)現(xiàn)的華為手機(jī),適配8.0椰憋,但其實(shí)我覺得就是因?yàn)橐粋€(gè)電量管理中保持鎖屏后繼續(xù)運(yùn)行權(quán)限導(dǎo)致的厅克, 但這個(gè)我沒有測試過開了權(quán)限這個(gè)方式行不行,小伙伴測試的時(shí)候橙依,可以給我反饋一下证舟,謝謝。

二窗骑、AlarmManager
這個(gè)我一開始看的時(shí)候女责,覺得不錯(cuò),設(shè)置重復(fù)的鬧鐘喚醒服務(wù)创译,讓服務(wù)持續(xù)抵知。鬧鐘設(shè)置重復(fù)定時(shí)的方法變化還是蠻大的,閱讀官方api的時(shí)候软族,不同的版本基本都有不同的設(shè)置方式刷喜。

使用的服務(wù):

public class PollingService extends Service {
    public static final String ACTION = "com.hdsx.service.PollingService";
    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }

    @Override
    public void onCreate() {
        Log.e(TAG, "onCreate:onCreate ");
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        Log.e(TAG, "Service:onStartCommand ");
        return super.onStartCommand(intent, flags, startId);
    }

    @Override
    public void onStart(Intent intent, int startId) {
        new PollingThread().start();
    }

    int count = 0;
    class PollingThread extends Thread {
        @Override
        public void run() {
            count ++;
            Log.e("polling", count+"" );
        }
    }
    
    @Override
    public void onDestroy() {
        super.onDestroy();
        startService(new Intent(PollingService.this,PollingService.class));
        Log.e(TAG, "Service:onDestroy ");
    }
}

注冊
<service android:name=".PollingService">
            <intent-filter>
                <action android:name="com.hdsx.service.PollingService"/>
            </intent-filter>
        </service>

服務(wù)里面起個(gè)線程,就是為了測試是否能持續(xù)的log輸出日志立砸。這塊提醒一下掖疮,調(diào)試狀態(tài)的時(shí)候手機(jī)是處于充電狀態(tài)的,cpu不會休眠颗祝,所以會一直log浊闪,不過經(jīng)過我后面添加代碼測試,是可行的螺戳。 我這塊只是提供了原始的demo搁宾,后面的加的代碼找不到了。

鬧鐘:

  public static void startPollingService(Context context, int seconds, Class<?> cls,String action) {
        long triggerAtTime = SystemClock.elapsedRealtime();
        AlarmManager manager = (AlarmManager) context
                .getSystemService(Context.ALARM_SERVICE);
        Intent intent = new Intent(context, cls);
        intent.setAction(action);
        PendingIntent pendingIntent = PendingIntent.getService(context, 0,
                intent, PendingIntent.FLAG_UPDATE_CURRENT);
        manager.setRepeating(AlarmManager.ELAPSED_REALTIME_WAKEUP, triggerAtTime,
                seconds, pendingIntent);
    }

使用
PollingUtils.startPollingService(MainActivity.this, 2000, PollingService.class, PollingService.ACTION);

這種方式倔幼,也可以實(shí)現(xiàn)盖腿, 但是測試的過程中有個(gè)問題,就是鬧鐘重復(fù)定時(shí)它的間隔有時(shí)候并不能完全按照我設(shè)置的時(shí)間來運(yùn)行凤藏,周期感覺有點(diǎn)不太確定,而且大多時(shí)間返回間隔較長堕伪。最主要的是揖庄,這個(gè)我華為手機(jī)并不行。欠雌。蹄梢。。。

三禁炒、JobScheduler執(zhí)行任務(wù)調(diào)度倍兀活


JobScheduler.png

它的宗旨是把一些不是特別緊急的任務(wù)放到更合適的時(shí)機(jī)批量處理,這樣可以有效的節(jié)省電量幕袱。這個(gè)我自己也寫了個(gè)demo啥的測試暴备,應(yīng)該是我寫的有問題,也可能是別的们豌,我覺得這個(gè)不太適合定位這個(gè)事涯捻,最終沒有實(shí)現(xiàn)。有實(shí)現(xiàn)的小伙伴可以分享一下望迎,謝謝障癌。

四、WorkManager
一開始看這個(gè)的時(shí)候辩尊,我表示這個(gè)的描述有點(diǎn)牛X而且還是新特性
不管是斷網(wǎng)涛浙,還是將進(jìn)程殺死,他都會執(zhí)行摄欲。有一些需求用這個(gè)來實(shí)現(xiàn)是相當(dāng)?shù)牟诲e(cuò)轿亮,比如 手機(jī)端向服務(wù)器請求數(shù)據(jù),當(dāng)沒有網(wǎng)絡(luò)時(shí)蒿涎,不要請求哀托,有網(wǎng)絡(luò)時(shí)自動(dòng)恢復(fù)請求。如果網(wǎng)絡(luò)處于斷開狀態(tài)劳秋,則將請求保存在隊(duì)列當(dāng)中仓手,監(jiān)聽網(wǎng)絡(luò)狀態(tài),一旦網(wǎng)絡(luò)連接上玻淑,則將隊(duì)列當(dāng)中的請求一個(gè)一個(gè)的執(zhí)行嗽冒。 很強(qiáng),主要是這個(gè)可以設(shè)置重復(fù)的任務(wù)补履,我主要看這塊添坊。

簡單介紹:WorkManager 可以輕松地讓異步任務(wù)延遲執(zhí)行以及何時(shí)運(yùn)行它們,API需要我們創(chuàng)建個(gè)worker任務(wù)并將其交給WorkManager箫锤,用來控制在什么時(shí)間運(yùn)行這個(gè)任務(wù)贬蛙。

        .setRequiredNetworkType(NetworkType.CONNECTED)  // 網(wǎng)絡(luò)狀態(tài)
        .setRequiresBatteryNotLow(true)                 // 不在電量不足時(shí)執(zhí)行
        .setRequiresCharging(true)                      // 在充電時(shí)執(zhí)行
        .setRequiresStorageNotLow(true)                 // 不在存儲容量不足時(shí)執(zhí)行
        .setRequiresDeviceIdle(true)                    // 在待機(jī)狀態(tài)下執(zhí)行,需要 API 23
我就不做深究了谚攒, 有興趣的朋友可以自己研究阳准, 最終我沒有用這個(gè)來實(shí)現(xiàn)我的需求,因?yàn)橹貜?fù)的任務(wù)執(zhí)行時(shí)間間隔最少15分鐘馏臭。

五野蝇、實(shí)現(xiàn)了的方式。雙服務(wù)喚醒
經(jīng)過測試,小米绕沈,oppo锐想,華為等手機(jī) 6.0 - 8.0版本沒問題,可完美實(shí)現(xiàn)乍狐。
先把AIDL寫了:

interface ILocationHelperServiceAIDL {

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

interface ILocationServiceAIDL {

    /** 當(dāng)其他服務(wù)已經(jīng)綁定時(shí)調(diào)起 */
    void onFinishBind();
}

一共兩個(gè)赠摇。
定義的回調(diào)接口,也先寫了澜躺。

/**
 * 代理類蝉稳,用于處理息屏造成wifi被關(guān)掉時(shí)再重新點(diǎn)亮屏幕的邏輯
 */
public interface IWifiAutoCloseDelegate {

    /**
     * 判斷在該機(jī)型下此邏輯是否有效。目前已知的系統(tǒng)是小米系統(tǒng)存在(用戶自助設(shè)置的)息屏斷掉wifi的功能掘鄙。
     *
     * @param context
     * @return
     */
    boolean isUseful(Context context);


    /**
     * 點(diǎn)亮屏幕的服務(wù)有可能被重啟耘戚。此處進(jìn)行初始化
     *
     * @param context
     * @return
     */
    void initOnServiceStarted(Context context);


    /**
     * 定位成功時(shí),如果移動(dòng)網(wǎng)絡(luò)無法訪問操漠,而且屏幕是點(diǎn)亮狀態(tài)收津,則對狀態(tài)進(jìn)行保存
     */
    void onLocateSuccess(Context context, boolean isScreenOn, boolean isMobileable);

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

1.定位服務(wù)

/**
 *
 *  后臺服務(wù)定位
 *
 *  1. 只有在由息屏造成的網(wǎng)絡(luò)斷開造成的定位失敗時(shí)才點(diǎn)亮屏幕
 *  2. 利用notification機(jī)制增加進(jìn)程優(yōu)先級
 *
 */
public class LocationService extends NotificationService {
    private AMapLocationClient mLocationClient;
    private AMapLocationClientOption mLocationOption;

    /**
     * 處理息屏關(guān)掉wifi的delegate類
     */
    private IWifiAutoCloseDelegate mWifiAutoCloseDelegate = new WifiAutoCloseDelegate();

    /**
     * 記錄是否需要對息屏關(guān)掉wifi的情況進(jìn)行處理
     */
    private boolean mIsWifiCloseable = false;


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

        startLocation();

        return START_STICKY;
    }


    @Override
    public void onDestroy() {
        unApplyNotiKeepMech();
        stopLocation();

        super.onDestroy();
    }

    /**
     * 啟動(dòng)定位
     */
    void startLocation() {
        stopLocation();

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

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

    /**
     * 停止定位
     */
    void stopLocation() {
        if (null != mLocationClient) {
            mLocationClient.stopLocation();
        }
    }

    AMapLocationListener locationListener = new AMapLocationListener() {

        @Override
        public void onLocationChanged(AMapLocation aMapLocation) {
            //發(fā)送結(jié)果的通知
            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);
                ToastUtils.show("獲取到定位信息");
                String string =  System.currentTimeMillis() + ","+aMapLocation.getLatitude() + "," + aMapLocation.getLongitude();
                Utils.saveFile(string, "backlocation.txt", true);

            }
        }
    };
}

定位服務(wù)的基類

/**
 * 利用雙service進(jìn)行notification綁定,進(jìn)而將Service的OOM_ADJ提高到1
 * 同時(shí)利用LocationHelperService充當(dāng)守護(hù)進(jìn)程浊伙,在NotificationService被關(guān)閉后撞秋,重啟他。
 * 如果LocationHelperService被停止嚣鄙,NotificationService不負(fù)責(zé)喚醒
 */
public class NotificationService extends Service {
    /**
     * startForeground的 noti_id
     */
    private static int NOTI_ID = 123321;

    private Utils.CloseServiceReceiver mCloseReceiver;

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        Log.e("background location", "遠(yuǎn)程服務(wù)調(diào)用成功");
        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();
    }


    private final String mHelperServiceName = "com.hdsx.background.locationservice.LocationHelperService";
    /**
     * 觸發(fā)利用notification增加進(jìn)程優(yōu)先級
     */
    protected void applyNotiKeepMech() {
        startForeground(NOTI_ID, Utils.buildNotification(getBaseContext()));
        startBindHelperService();
    }

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

    public Binder mBinder;

    public class LocationServiceBinder extends ILocationServiceAIDL.Stub {

        @Override
        public void onFinishBind(){
        }
    }

    private ILocationHelperServiceAIDL mHelperAIDL;

    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(NOTI_ID);
                } catch (RemoteException e) {
                    e.printStackTrace();
                }
            }
        };
        Intent intent = new Intent();
        intent.setAction(mHelperServiceName);
        bindService(Utils.getExplicitIntent(getApplicationContext(), intent), connection, Service.BIND_AUTO_CREATE);
    }

    private ServiceConnection connection;

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

另外一個(gè)服務(wù):

public class LocationHelperService extends Service {
    private Utils.CloseServiceReceiver mCloseReceiver;
    @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 ServiceConnection mInnerConnection;
    private void startBind() {
        final String locationServiceName = "com.hdsx.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);
        bindService(Utils.getExplicitIntent(getApplicationContext(), intent), mInnerConnection, Service.BIND_AUTO_CREATE);
    }

    private HelperBinder mBinder;

    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;
    }
}

雙服務(wù)進(jìn)行捆綁吻贿,
之前測試的過程中,發(fā)現(xiàn)這個(gè)powermanager配合服務(wù)他是可實(shí)現(xiàn)部分手機(jī)的哑子,這邊也做一個(gè)power的封裝舅列。

/**
 * 獲得PARTIAL_WAKE_LOCK  , 保證在息屏狀體下卧蜓,CPU可以正常運(yùn)行
 */

public class PowerManagerUtil {

    private static class Holder {
        public static PowerManagerUtil instance = new PowerManagerUtil();
    }
    private PowerManager pm = null;
    private PowerManager.WakeLock pmLock = null;

    /**
     * 上次喚醒屏幕的觸發(fā)時(shí)間
     */
    private long mLastWakupTime = System.currentTimeMillis();

    /**
     * 最小的喚醒時(shí)間間隔帐要,防止頻繁喚醒。默認(rèn)5分鐘
     */
    private long mMinWakupInterval = 10 * 1000;

    private InnerThreadFactory mInnerThreadFactory = null;

    public static PowerManagerUtil getInstance() {
        return Holder.instance;
    }

    /**
     * 判斷屏幕是否處于點(diǎn)亮狀態(tài)
     *
     * @param context
     */
    public boolean isScreenOn(final Context context) {
        try {
            Method isScreenMethod = PowerManager.class.getMethod("isScreenOn",
                    new Class[]{});
            if (pm == null) {
                pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
            }
            boolean screenState = (Boolean) isScreenMethod.invoke(pm);
            return screenState;
        } catch (Exception e) {
            return true;
        }
    }

    /**
     * 喚醒屏幕
     */
    public void wakeUpScreen(final Context context) {

        try {
            acquirePowerLock(context, PowerManager.ACQUIRE_CAUSES_WAKEUP | PowerManager.SCREEN_DIM_WAKE_LOCK);
            String string =  System.currentTimeMillis() +"喚醒";
            Utils.saveFile(string, "huanxinglocation.txt", true);

        } catch (Exception e) {
            throw e;
        }
    }

    /**
     * 根據(jù)levelAndFlags弥奸,獲得PowerManager的WaveLock
     * 利用worker thread去獲得鎖榨惠,以免阻塞主線程
     * @param context
     * @param levelAndFlags
     */
    private void acquirePowerLock(final Context context, final int levelAndFlags) {
        if (context == null) {
            throw new NullPointerException("when invoke aquirePowerLock ,  context is null which is unacceptable");
        }

        long currentMills = System.currentTimeMillis();

        if (currentMills - mLastWakupTime < mMinWakupInterval) {
            return;
        }


        mLastWakupTime = currentMills;

        if (mInnerThreadFactory == null) {
            mInnerThreadFactory = new InnerThreadFactory();
        }

        mInnerThreadFactory.newThread(new Runnable() {

            @Override
            public void run() {
                if (pm == null) {
                    pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
                }
                if (pmLock != null) {
                    // release
                    pmLock.release();
                    pmLock = null;
                }
                pmLock = pm.newWakeLock(levelAndFlags, "MyTag");
                pmLock.acquire();
                pmLock.release();
            }
        }).start();
    }

    private class InnerThreadFactory implements ThreadFactory {

        @Override
        public Thread newThread(Runnable runnable) {
            return new Thread(runnable);
        }
    }
}

整體的邏輯呢就是說, 啟動(dòng)LocationService開啟定位盛霎,定位成功后在手機(jī)添加一個(gè)前臺的通知赠橙,讓通知的優(yōu)先級盡可能的提高。在鎖屏后愤炸,powermanager判斷獲取如果定位失敗喚醒服務(wù)期揪。

public void onLocateFail(Context context, int errorCode, boolean isScreenOn, boolean isWifiable) {
        //如果屏幕點(diǎn)亮情況下,因?yàn)閿嗑W(wǎng)失敗摇幻,則表示不是屏幕點(diǎn)亮造成的斷網(wǎng)失敗横侦,并修改參照值
        if (isScreenOn && errorCode == AMapLocation.ERROR_CODE_FAILURE_CONNECTION && !isWifiable) {
            LocationStatusManager.getInstance().resetToInit(context);
            return;
        }

        if (!LocationStatusManager.getInstance().isFailOnScreenOff(context, errorCode, isScreenOn, isWifiable)) {
            return;
        }
        PowerManagerUtil.getInstance().wakeUpScreen(context);
    }

代碼有點(diǎn)多,就不一一介紹了绰姻,下面把我包含的工具類都發(fā)出來枉侧。

  • LocationStatusManager
/**
 * 在定位失敗的情況下,用于判斷當(dāng)前定位錯(cuò)誤是否是由于息屏導(dǎo)致的網(wǎng)絡(luò)關(guān)閉引起的狂芋。
 * 判斷邏輯僅限于處理設(shè)備僅有wifi信號的情況下
 */

public class LocationStatusManager {
    /**
     * 上一次的定位是否成功
     */
    private boolean mPriorSuccLocated = false;

    /**
     * 屏幕亮?xí)r可以定位
     */
    private boolean mPirorLocatableOnScreen = false;

    static class Holder {
        public static LocationStatusManager instance = new LocationStatusManager();
    }

    public static LocationStatusManager getInstance() {
        return Holder.instance;
    }

    /**
     * 由于僅僅處理只有wifi連接的情況下榨馁,如果用戶手機(jī)網(wǎng)絡(luò)可連接,那么忽略帜矾。
     * 定位成功時(shí)翼虫,重置為定位成功的狀態(tài)
     *
     * @param isScreenOn 當(dāng)前屏幕是否為點(diǎn)亮狀態(tài)
     * @param isMobileable 是否有手機(jī)信號
     */
    public void onLocationSuccess(Context context, boolean isScreenOn, boolean isMobileable) {
        if (isMobileable) {
            return;
        }

        mPriorSuccLocated = true;
        if (isScreenOn) {
            mPirorLocatableOnScreen = true;
            saveStateInner(context, true);
        }
    }

    /**
     * reset到默認(rèn)狀態(tài)
     *
     * @param context
     */
    public void resetToInit(Context context) {
        this.mPirorLocatableOnScreen = false;
        this.mPriorSuccLocated = false;
        saveStateInner(context, false);
    }

    /**
     * 由preference初始化。特別是在定位服務(wù)重啟的時(shí)候會進(jìn)行初始化
     */
    public void initStateFromPreference(Context context) {
        if (!isLocableOnScreenOn(context)) {
            return;
        }
        this.mPriorSuccLocated = true;
        this.mPirorLocatableOnScreen = true;
    }

    /**
     * 判斷是否由屏幕關(guān)閉導(dǎo)致的定位失敗屡萤。
     * 只有在 網(wǎng)絡(luò)可訪問&&errorCode==4&&(priorLocated&&locatableOnScreen) && !isScreenOn 才認(rèn)為是有息屏引起的定位失敗
     * 如果判斷條件較為嚴(yán)格珍剑,請按需要適當(dāng)修改
     *
     * @param errorCode  定位錯(cuò)誤碼, 0=成功, 4=因?yàn)榫W(wǎng)絡(luò)原因造成的失敗
     * @param isScreenOn 當(dāng)前屏幕是否為點(diǎn)亮狀態(tài)
     */
    public boolean isFailOnScreenOff(Context context, int errorCode, boolean isScreenOn, boolean isWifiable) {
        return !isWifiable && errorCode == AMapLocation.ERROR_CODE_FAILURE_CONNECTION && (mPriorSuccLocated && mPirorLocatableOnScreen) && !isScreenOn;
    }

    /**
     * 是否存在屏幕亮而且可以定位的情況的key
     */
    private String IS_LOCABLE_KEY = "is_locable_key";

    /**
     * IS_LOCABLE_KEY 的過期時(shí)間
     */
    private String LOCALBLE_KEY_EXPIRE_TIME_KEY = "localble_key_expire_time_key";

    /**
     * 過期時(shí)間為10分鐘
     */
    private static final long MINIMAL_EXPIRE_TIME = 30 * 60 * 1000;
    private static final String PREFER_NAME = LocationStatusManager.class.getSimpleName();
    private static final long DEF_PRIOR_TIME_VAL = -1;

    /**
     * 如果isLocable死陆,則存入正確的過期時(shí)間招拙,否則存默認(rèn)值
     *
     * @param context
     * @param isLocable
     */
    public void saveStateInner(Context context, boolean isLocable) {
        SharedPreferences sharedPreferences = context.getSharedPreferences(PREFER_NAME, MODE_PRIVATE);
        SharedPreferences.Editor editor = sharedPreferences.edit();
        editor.putBoolean(IS_LOCABLE_KEY, isLocable);
        editor.putLong(LOCALBLE_KEY_EXPIRE_TIME_KEY, isLocable ? System.currentTimeMillis() : DEF_PRIOR_TIME_VAL);
        editor.commit();
    }

    /**
     * 從preference讀取,判斷是否存在網(wǎng)絡(luò)狀況ok措译,而且亮屏情況下别凤,可以定位的情況
     */
    public boolean isLocableOnScreenOn(Context context) {
        SharedPreferences sharedPreferences = context.getSharedPreferences(PREFER_NAME, MODE_PRIVATE);
        boolean res = sharedPreferences.getBoolean(IS_LOCABLE_KEY, false);
        long priorTime = sharedPreferences.getLong(LOCALBLE_KEY_EXPIRE_TIME_KEY, DEF_PRIOR_TIME_VAL);
        if (System.currentTimeMillis() - priorTime > MINIMAL_EXPIRE_TIME) {
            saveStateInner(context, false);
            return false;
        }

        return res;
    }
}
  • LocationChangBroadcastReceiver
    做最后定位的結(jié)果,保存到數(shù)據(jù)庫里
public class LocationChangBroadcastReceiver extends BroadcastReceiver {
    public static final String RECEIVER_ACTION = "location_in_background";
    public static final String RECEIVER_DATA = "location_data";

    @Override
    public void onReceive(Context context, Intent intent) {
        String action = intent.getAction();
        if (action.equals(RECEIVER_ACTION)) {
            AMapLocation location = (AMapLocation) intent.getParcelableExtra(RECEIVER_DATA);
            if (null != location) {
                String string =  System.currentTimeMillis() + ","+location.getLatitude() + "," + location.getLongitude();
                Utils.saveFile(string, "broadcastlocation.txt", true);

                Log.v("定位數(shù)據(jù)", "經(jīng)度:" + location.getLongitude() + " 緯度:" + location.getLatitude());
                SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
                java.util.Date date = new Date(location.getTime());
                String tracktime = sdf.format(date);
                Map map = new HashMap();
                String userid = SpUtils.getString(USER_ID);
                map.put("userid", userid);
                double[] loc = CoordinateTransformUtil.gcj02towgs84(location.getLongitude(),location.getLatitude());
                map.put("tracktime", tracktime);
                map.put("latitude", loc[1]);
                map.put("lontitude", loc[0]);
                Frame.getInstance().getDao().insert("trackbean.insert_track", map);
            }
        }
    }
}
  • NetUtil
    用于判斷設(shè)備是否可以訪問網(wǎng)絡(luò)领虹。
public class NetUtil {

    private static class Holder {
        public static NetUtil instance = new NetUtil();
    }

    public static NetUtil getInstance() {
        return Holder.instance;
    }

    /**
     * 是否手機(jī)信號可連接
     * @param context
     * @return
     */
    public boolean isMobileAva(Context context) {
        boolean hasMobileCon = false;
        ConnectivityManager cm = (ConnectivityManager) context.getSystemService(context.CONNECTIVITY_SERVICE);
        NetworkInfo[] netInfos = cm.getAllNetworkInfo();
        for (NetworkInfo net : netInfos) {

            String type = net.getTypeName();
            if (type.equalsIgnoreCase("MOBILE")) {
                if (net.isConnected()) {
                    hasMobileCon = true;
                }
            }
        }
        return hasMobileCon;
    }


    /**
     * 是否wifi可連接
     * @param context
     * @return
     */
    public boolean isWifiCon(Context context) {
        boolean hasWifoCon = false;
        ConnectivityManager cm = (ConnectivityManager) context.getSystemService(context.CONNECTIVITY_SERVICE);
        NetworkInfo[] netInfos = cm.getAllNetworkInfo();
        for (NetworkInfo net : netInfos) {

            String type = net.getTypeName();
            if (type.equalsIgnoreCase("WIFI")) {
                if (net.isConnected()) {
                    hasWifoCon = true;
                }
            }

        }
        return hasWifoCon;
    }
}
  • Utils
    輔助工具類
public class Utils {
    private static String CLOSE_BRODECAST_INTENT_ACTION_NAME="com.hdsx.background.locationservice.CloseService";
    /**
     * 開始定位
     */
    public final static int MSG_LOCATION_START = 0;
    /**
     * 定位完成
     */
    public final static int MSG_LOCATION_FINISH = 1;
    /**
     * 停止定位
     */
    public final static int MSG_LOCATION_STOP = 2;

    public final static String KEY_URL = "URL";
    public final static String URL_H5LOCATION = "file:///android_asset/location.html";
    private static SimpleDateFormat sdf = null;
    private static NotificationManager mNotificationManager;
    private final static String PRIMARY_CHANNEL = "default";

    /**
     * 根據(jù)定位結(jié)果返回定位信息的字符串
     *
     * @param location
     * @return
     */
    public synchronized static String getLocationStr(AMapLocation location) {
        if (null == location) {
            return null;
        }
        StringBuffer sb = new StringBuffer();
        //errCode等于0代表定位成功规哪,其他的為定位失敗,具體的可以參照官網(wǎng)定位錯(cuò)誤碼說明
        if (location.getErrorCode() == 0) {
            sb.append("定位成功" + "\n");
            sb.append("定位類型: " + location.getLocationType() + "\n");
            sb.append("經(jīng)    度    : " + location.getLongitude() + "\n");
            sb.append("緯    度    : " + location.getLatitude() + "\n");
            sb.append("精    度    : " + location.getAccuracy() + "米" + "\n");
            sb.append("提供者    : " + location.getProvider() + "\n");

            sb.append("海    拔    : " + location.getAltitude() + "米" + "\n");
            sb.append("速    度    : " + location.getSpeed() + "米/秒" + "\n");
            sb.append("角    度    : " + location.getBearing() + "\n");
            if (location.getProvider().equalsIgnoreCase(
                    android.location.LocationManager.GPS_PROVIDER)) {
                // 以下信息只有提供者是GPS時(shí)才會有
                // 獲取當(dāng)前提供定位服務(wù)的衛(wèi)星個(gè)數(shù)
                sb.append("星    數(shù)    : "
                        + location.getSatellites() + "\n");
            }

            //逆地理信息
            sb.append("國    家    : " + location.getCountry() + "\n");
            sb.append("省            : " + location.getProvince() + "\n");
            sb.append("市            : " + location.getCity() + "\n");
            sb.append("城市編碼 : " + location.getCityCode() + "\n");
            sb.append("區(qū)            : " + location.getDistrict() + "\n");
            sb.append("區(qū)域 碼   : " + location.getAdCode() + "\n");
            sb.append("地    址    : " + location.getAddress() + "\n");
            sb.append("興趣點(diǎn)    : " + location.getPoiName() + "\n");
            //定位完成的時(shí)間
            sb.append("定位時(shí)間: " + formatUTC(location.getTime(), "yyyy-MM-dd HH:mm:ss") + "\n");

        } else {
            //定位失敗
            sb.append("定位失敗" + "\n");
            sb.append("錯(cuò)誤碼:" + location.getErrorCode() + "\n");
            sb.append("錯(cuò)誤信息:" + location.getErrorInfo() + "\n");
            sb.append("錯(cuò)誤描述:" + location.getLocationDetail() + "\n");
        }
        //定位之后的回調(diào)時(shí)間
        sb.append("回調(diào)時(shí)間: " + formatUTC(System.currentTimeMillis(), "yyyy-MM-dd HH:mm:ss") + "\n");
        return sb.toString();
    }

    public synchronized static String formatUTC(long l, String strPattern) {
        if (TextUtils.isEmpty(strPattern)) {
            strPattern = "yyyy-MM-dd HH:mm:ss";
        }
        if (sdf == null) {
            try {
                sdf = new SimpleDateFormat(strPattern, Locale.CHINA);
            } catch (Throwable e) {
            }
        } else {
            sdf.applyPattern(strPattern);
        }
        return sdf == null ? "NULL" : sdf.format(l);
    }

    public static Intent getExplicitIntent(Context context, Intent implicitIntent) {
        if (context.getApplicationInfo().targetSdkVersion < Build.VERSION_CODES.LOLLIPOP) {
            return implicitIntent;
        }

        // Retrieve all services that can match the given intent
        PackageManager pm = context.getPackageManager();
        List<ResolveInfo> resolveInfo = pm.queryIntentServices(implicitIntent, 0);
        // Make sure only one match was found
        if (resolveInfo == null || resolveInfo.size() != 1) {
            return null;
        }
        // Get component info and create ComponentName
        ResolveInfo serviceInfo = resolveInfo.get(0);
        String packageName = serviceInfo.serviceInfo.packageName;
        String className = serviceInfo.serviceInfo.name;
        ComponentName component = new ComponentName(packageName, className);
        // Create a new intent. Use the old one for extras and such reuse
        Intent explicitIntent = new Intent(implicitIntent);
        // Set the component to be explicit
        explicitIntent.setComponent(component);
        return explicitIntent;
    }

    public static void saveFile(String toSaveString, String fileName, boolean append) {
        try {
            String sdCardRoot = Environment.getExternalStorageDirectory()
                    .getAbsolutePath();
            File saveFile = new File(sdCardRoot + "/" + fileName);
            if (!saveFile.exists()) {
                File dir = new File(saveFile.getParent());
                dir.mkdirs();
                saveFile.createNewFile();
            }
            FileOutputStream outStream = new FileOutputStream(saveFile, append);
            outStream.write(toSaveString.getBytes());
            outStream.write("\r\n".getBytes());
            outStream.close();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public static Notification buildNotification(Context context) {
        Notification notification = null;
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            NotificationChannel channel = new NotificationChannel(PRIMARY_CHANNEL,
                    context.getString(R.string.default_channel), NotificationManager.IMPORTANCE_DEFAULT);
            channel.setLightColor(Color.GREEN);
            channel.setLockscreenVisibility(Notification.VISIBILITY_PRIVATE);
            getNotificationManager(context).createNotificationChannel(channel);
            Notification.Builder builder = new Notification.Builder(context,
                    PRIMARY_CHANNEL)
                    .setContentText("水源地軌跡記錄中...")
                    .setSmallIcon(R.drawable.ic_launcher)
                    .setAutoCancel(true);
            notification = builder.build();
        } else {
            Notification.Builder builder = new Notification.Builder(context);
            builder.setSmallIcon(R.drawable.ic_launcher);
            builder.setContentText("水源地軌跡記錄中..." )
                    .setWhen(System.currentTimeMillis());
            notification = builder.build();
        }

        return notification;
    }

    private static NotificationManager getNotificationManager(Context context) {
        if (mNotificationManager == null) {
            mNotificationManager = (NotificationManager)context.getSystemService(
                    Context.NOTIFICATION_SERVICE);
        }
        return mNotificationManager;
    }

    public static void startWifi(Context context) {
        WifiManager wm = (WifiManager) context
                .getSystemService(Context.WIFI_SERVICE);
        wm.setWifiEnabled(true);
        wm.reconnect();
    }

    public static boolean isWifiEnabled(Context context) {
        WifiManager wm = (WifiManager) context
                .getSystemService(Context.WIFI_SERVICE);
        return wm.isWifiEnabled();
    }

    public static String getManufacture(Context context) {
        return Build.MANUFACTURER;
    }

    public static Intent getCloseBrodecastIntent() {
        return new Intent(CLOSE_BRODECAST_INTENT_ACTION_NAME);
    }

    public static IntentFilter getCloseServiceFilter() {
        return new IntentFilter(CLOSE_BRODECAST_INTENT_ACTION_NAME);
    }

    public static class CloseServiceReceiver extends BroadcastReceiver {

        Service mService;

        public CloseServiceReceiver(Service service) {
            this.mService = service;
        }

        @Override
        public void onReceive(Context context, Intent intent) {
            if (mService == null) {
                return;
            }
            mService.onDestroy();
        }
    }
}
  • WifiAutoCloseDelegate
    接口實(shí)現(xiàn)類塌衰,回調(diào)一下結(jié)果
public class WifiAutoCloseDelegate implements IWifiAutoCloseDelegate {

    /**
     * 請根據(jù)后臺數(shù)據(jù)自行添加诉稍。此處只針對小米手機(jī)
     * @param context
     * @return
     */
    @Override
    public boolean isUseful(Context context) {
        String manName = Utils.getManufacture(context);
        Pattern pattern = Pattern.compile("xiaomi", Pattern.CASE_INSENSITIVE);
        Matcher m = pattern.matcher(manName);
        return m.find();
    }

    @Override
    public void initOnServiceStarted(Context context) {
        LocationStatusManager.getInstance().initStateFromPreference(context);
    }

    @Override
    public void onLocateSuccess(Context context, boolean isScreenOn, boolean isMobileable) {
        LocationStatusManager.getInstance().onLocationSuccess(context, isScreenOn, isMobileable);
    }

    @Override
    public void onLocateFail(Context context, int errorCode, boolean isScreenOn, boolean isWifiable) {
        //如果屏幕點(diǎn)亮情況下,因?yàn)閿嗑W(wǎng)失敗猾蒂,則表示不是屏幕點(diǎn)亮造成的斷網(wǎng)失敗均唉,并修改參照值
        if (isScreenOn && errorCode == AMapLocation.ERROR_CODE_FAILURE_CONNECTION && !isWifiable) {
            LocationStatusManager.getInstance().resetToInit(context);
            return;
        }

        if (!LocationStatusManager.getInstance().isFailOnScreenOff(context, errorCode, isScreenOn, isWifiable)) {
            return;
        }
        PowerManagerUtil.getInstance().wakeUpScreen(context);
    }
}

基本就這些, 代碼比較多肚菠, 有興趣的朋友可以自行閱讀舔箭,注解基本都有介紹。
記錄一下此類問題蚊逢。

更新:2019-01-03
附張效果圖


yes.png

上述圖片僅用設(shè)備GPS定位的层扶。
demo鏈接如下:
https://download.csdn.net/download/binbinxiaoz/10892551
https://download.csdn.net/download/binbinxiaoz/10890248

測試前,請檢查手機(jī) 對app的電量管理權(quán)限以及限制后臺運(yùn)行權(quán)限及鎖屏后針對app的權(quán)限烙荷。若無可手動(dòng)更改請忽略镜会。
測試時(shí),請室外測試终抽,室內(nèi)gps信號差戳表。若室內(nèi)測試請切換別的定位模式桶至,保證可以定位但不保證定位的精準(zhǔn)度,本身流量等定位偏差就相當(dāng)?shù)拇蟆?br> demo中只需關(guān)注養(yǎng)護(hù)巡查模塊即可匾旭,別的模塊練手加的镣屹,無關(guān)聯(lián)。
android studio版本3.1.4

下載demo后价涝,評個(gè)分謝謝女蜈。

小米9.0手機(jī)處理: 禁止電池優(yōu)化, 華為手機(jī)會因?yàn)楹碾娺^強(qiáng)直接終止進(jìn)程色瘩,
別的手機(jī)暫時(shí)還為測試伪窖。 推薦接推送喚醒·

 Intent intent = new Intent();

                String packageName = getActivity().getPackageName();

                PowerManager pm = (PowerManager) getActivity().getSystemService(Context.POWER_SERVICE);

                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
                    if (pm.isIgnoringBatteryOptimizations(packageName))

                        intent.setAction(Settings.ACTION_IGNORE_BATTERY_OPTIMIZATION_SETTINGS);

                    else {

                        intent.setAction(Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS);

                        intent.setData(Uri.parse("package:" + packageName));

                    }
                }
                getActivity().startActivity(intent);
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市居兆,隨后出現(xiàn)的幾起案子覆山,更是在濱河造成了極大的恐慌,老刑警劉巖泥栖,帶你破解...
    沈念sama閱讀 218,858評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件汹买,死亡現(xiàn)場離奇詭異,居然都是意外死亡聊倔,警方通過查閱死者的電腦和手機(jī)晦毙,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,372評論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來耙蔑,“玉大人见妒,你說我怎么就攤上這事〉槟埃” “怎么了须揣?”我有些...
    開封第一講書人閱讀 165,282評論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長钱豁。 經(jīng)常有香客問我耻卡,道長,這世上最難降的妖魔是什么牲尺? 我笑而不...
    開封第一講書人閱讀 58,842評論 1 295
  • 正文 為了忘掉前任卵酪,我火速辦了婚禮,結(jié)果婚禮上谤碳,老公的妹妹穿的比我還像新娘溃卡。我一直安慰自己,他們只是感情好蜒简,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,857評論 6 392
  • 文/花漫 我一把揭開白布瘸羡。 她就那樣靜靜地躺著,像睡著了一般搓茬。 火紅的嫁衣襯著肌膚如雪犹赖。 梳的紋絲不亂的頭發(fā)上队他,一...
    開封第一講書人閱讀 51,679評論 1 305
  • 那天,我揣著相機(jī)與錄音峻村,去河邊找鬼漱挎。 笑死,一個(gè)胖子當(dāng)著我的面吹牛雀哨,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播私爷,決...
    沈念sama閱讀 40,406評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼雾棺,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了衬浑?” 一聲冷哼從身側(cè)響起捌浩,我...
    開封第一講書人閱讀 39,311評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎工秩,沒想到半個(gè)月后尸饺,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,767評論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡助币,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,945評論 3 336
  • 正文 我和宋清朗相戀三年浪听,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片眉菱。...
    茶點(diǎn)故事閱讀 40,090評論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡迹栓,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出俭缓,到底是詐尸還是另有隱情克伊,我是刑警寧澤,帶...
    沈念sama閱讀 35,785評論 5 346
  • 正文 年R本政府宣布华坦,位于F島的核電站愿吹,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏惜姐。R本人自食惡果不足惜犁跪,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,420評論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望歹袁。 院中可真熱鬧耘拇,春花似錦、人聲如沸宇攻。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,988評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽逞刷。三九已至嘉涌,卻和暖如春妻熊,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背仑最。 一陣腳步聲響...
    開封第一講書人閱讀 33,101評論 1 271
  • 我被黑心中介騙來泰國打工扔役, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人警医。 一個(gè)月前我還...
    沈念sama閱讀 48,298評論 3 372
  • 正文 我出身青樓亿胸,卻偏偏與公主長得像,于是被迫代替她去往敵國和親预皇。 傳聞我的和親對象是個(gè)殘疾皇子侈玄,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,033評論 2 355

推薦閱讀更多精彩內(nèi)容

  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,164評論 25 707
  • 回歸使用byword了,極簡的書寫方式吟温,也算是一種紓解的途經(jīng)序仙,靜靜淡淡的,很適用鲁豪∨说浚看著文字一個(gè)個(gè)跳上屏幕,心情是恬...
    wen_a閱讀 253評論 1 1
  • 01 今天是后學(xué)爸爸的生日 也是我們國學(xué)館夏令營最后的一天爬橡。 中午的時(shí)候治唤,后學(xué)姐姐就發(fā)微信過來,叫后學(xué)記得打電話回...
    袁袁_45fc閱讀 407評論 2 2
  • 朋友圈與公眾號內(nèi)容,徹底封殺百度的存在郭宝。當(dāng)然辞槐,包括阿里系。
    北漂廿年閱讀 204評論 0 0
  • 2017.6.25 周日 晴 本應(yīng)該是周六爬山粘室,無奈老公出差未歸榄檬,連續(xù)的陰雨,自己也擔(dān)心路上下雨衔统,臨時(shí)改成周日爬山...
    蝸牛小于閱讀 265評論 0 0