github源碼下載地址:https://github.com/geduo83/TrackDataCollect/blob/sync/app/src/main/java/com/geduo/datacollect/alive/
今天是2018年的最后一天牺勾,幸好元旦放假三天青柄,有時間來整理下這陣子研究的一點兒新東西冰评,把它記錄分享出來映胁,本來這篇文章應該是上個周要完成,但是由于工作忙沒有時間寫甲雅,就拖到今天了解孙,再過幾個小時就2019年了坑填,愿新的一年我們都能心想事成。
在上一篇文章Android車輛運動軌跡大數(shù)據(jù)采集最佳實踐(https://blog.csdn.net/geduo_83/article/details/84943984)這篇文章中我們講到弛姜,數(shù)據(jù)采集服務是一個持久的操作脐瑰,當采集服務進入后臺后,有可能被系統(tǒng)殺死的可能廷臼,可用通過賬號同步服務SysAdapter來提升進程的優(yōu)先級來降低采集服務被后臺殺死的幾率苍在。 本文共介紹了6種后臺服務保活方案荠商,下文將逐一介紹寂恬。
1.賬號同步服務SysAdapter保活
- 1.1 通過ContentProvider實現(xiàn)數(shù)據(jù)同步
public class SyncContentProvider extends ContentProvider {
public static final String CONTENT_URI_BASE = "content://" + SyncControl.CONTENT_AUTHORITY;
public static final String TABLE_NAME = "data";
public static final Uri CONTENT_URI = Uri.parse(CONTENT_URI_BASE + "/" + TABLE_NAME);
@Override
public boolean onCreate() {
return true;
}
@Nullable
@Override
public Cursor query(Uri uri, String[] projection, String selection,
String[] selectionArgs, String sortOrder) {
return null;
}
@Nullable
@Override
public String getType(Uri uri) {
return new String();
}
@Nullable
@Override
public Uri insert(Uri uri, ContentValues values) {
return null;
}
@Override
public int delete(Uri uri, String selection, String[] selectionArgs) {
return 0;
}
@Override
public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
return 0;
}
}
- 1.2 關(guān)聯(lián)SyncAdapter通信服務
public class SyncAdapterService extends Service {
private static final Object lock = new Object();
private static SyncAdapter mSyncAdapter = null;
private static int sync_count = 0;
@Override
public void onCreate() {
synchronized (lock) {
if (mSyncAdapter == null) {
mSyncAdapter = new SyncAdapter(getApplicationContext(), true);
}
}
}
@Override
public IBinder onBind(Intent intent) {
return mSyncAdapter.getSyncAdapterBinder();
}
@Override
public boolean onUnbind(Intent intent) {
return super.onUnbind(intent);
}
@Override
public void onTaskRemoved(Intent rootIntent) {
super.onTaskRemoved(rootIntent);
}
static class SyncAdapter extends AbstractThreadedSyncAdapter {
public SyncAdapter(Context context, boolean autoInitialize) {
super(context, autoInitialize);
}
@Override
public void onPerformSync(Account account, Bundle extras, String authority, ContentProviderClient provider,
SyncResult syncResult) {
// 具體的同步操作莱没,這里主要是為了提高進程優(yōu)先級
Log.v("MYTAG", "onPerformSync start...");
SyncSharedPreference.getInstance(getContext()).addSyncTime("sync_time"+(++sync_count));
}
}
}
- 1.3 使用Sync服務
public class SyncControl {
public static final long SYNC_FREQUENCY = 5; // 1 hour (in seconds)
public static final String CONTENT_AUTHORITY = "com.geduo.datacollect";
public static final String ACCOUNT_TYPE = "com.geduo.datacollect.account";
@TargetApi(Build.VERSION_CODES.FROYO)
public static void createSyncAccount(Context context) {
Log.v("MYTAG", "createSyncAccount start...");
Account account = SyncAccountService.getAccount(ACCOUNT_TYPE);
AccountManager accountManager = (AccountManager) context.getSystemService(Context.ACCOUNT_SERVICE);
if (accountManager.addAccountExplicitly(account, null, null)) {
ContentResolver.setIsSyncable(account, CONTENT_AUTHORITY, 1);
ContentResolver.setSyncAutomatically(account, CONTENT_AUTHORITY, true);
ContentResolver.addPeriodicSync(account, CONTENT_AUTHORITY, new Bundle(), SYNC_FREQUENCY);
}
}
public static void triggerRefresh() {
Log.v("MYTAG", "triggerRefresh start...");
Bundle b = new Bundle();
// Disable sync backoff and ignore sync preferences. In other words...perform sync NOW!
b.putBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, true);
b.putBoolean(ContentResolver.SYNC_EXTRAS_EXPEDITED, true);
ContentResolver.requestSync(
SyncAccountService.getAccount(ACCOUNT_TYPE), // Sync account
CONTENT_AUTHORITY, // Content authority
b); // Extras
}
}
2.Notication背跞猓活
- 2.1 創(chuàng)建前臺守護服務DaemonService
public class DaemonService extends Service {
private static final String TAG = DaemonService.class.getSimpleName();
public static final int NOTICE_ID = 100;
@Nullable
@Override
public IBinder onBind(Intent intent) {
return null;
}
@Override
public void onCreate() {
super.onCreate();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
Notification.Builder builder = new Notification.Builder(this);
builder.setSmallIcon(R.mipmap.ic_launcher);
builder.setContentTitle("DataCollect");
builder.setContentText("DataCollect runing...");
startForeground(NOTICE_ID, builder.build());
Intent intent = new Intent(this, CancelService.class);
startService(intent);
} else {
startForeground(NOTICE_ID, new Notification());
}
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
return START_STICKY;
}
@Override
public void onDestroy() {
super.onDestroy();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
NotificationManager mManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
mManager.cancel(NOTICE_ID);
}
Intent intent = new Intent(getApplicationContext(), DaemonService.class);
startService(intent);
}
}
- 2.2 創(chuàng)建一個取消通知圖標的服務CancelService,讓用戶在前臺無感知
public class CancelService extends Service {
@Nullable
@Override
public IBinder onBind(Intent intent) {
return null;
}
@Override
public void onCreate() {
super.onCreate();
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.JELLY_BEAN_MR2) {
Notification.Builder builder = new Notification.Builder(this);
builder.setSmallIcon(R.mipmap.ic_launcher);
startForeground(DaemonService.NOTICE_ID, builder.build());
new Thread(new Runnable() {
@Override
public void run() {
SystemClock.sleep(1000);
stopForeground(true);
NotificationManager manager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
manager.cancel(DaemonService.NOTICE_ID);
stopSelf();
}
}).start();
}
return super.onStartCommand(intent, flags, startId);
}
@Override
public void onDestroy() {
super.onDestroy();
}
}
- 2.3 服務的注冊
<service android:name=".alive.notifiy.DaemonService"
android:enabled="true"
android:exported="true"
android:process=":daemon_service"/>
<service android:name=".alive.notifiy.CancelService"
android:enabled="true"
android:exported="true"
android:process=":service"/>
- 2.4 服務開啟
private void startDaemonService() {
Intent intent = new Intent(this, DaemonService.class);
startService(intent);
}
3.通過1像素Activity笔味悖活
- 3.1 創(chuàng)建1像素透明Activity
public class SinglePixelActivity extends AppCompatActivity {
private static final String TAG = "SinglePixelActivity";
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Window mWindow = getWindow();
mWindow.setGravity(Gravity.LEFT | Gravity.TOP);
WindowManager.LayoutParams attrParams = mWindow.getAttributes();
attrParams.x = 0;
attrParams.y = 0;
attrParams.height = 300;
attrParams.width = 300;
mWindow.setAttributes(attrParams);
// 綁定SinglePixelActivity到ScreenManager
ScreenManager.getScreenManagerInstance(this).setSingleActivity(this);
}
@Override
protected void onDestroy() {
if(! SystemUtils.isAPPALive(this,"com.geduo.datacollect")){
Intent intentAlive = new Intent(this, MainActivity.class);
intentAlive.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intentAlive);
}
super.onDestroy();
}
}
- 3.2 創(chuàng)建屏幕鎖屏牙咏、開屏監(jiān)聽工具類
public class ScreenReceiverUtil {
private Context mContext;
// 鎖屏廣播接收器
private SreenBroadcastReceiver mScreenReceiver;
// 屏幕狀態(tài)改變回調(diào)接口
private SreenStateListener mStateReceiverListener;
public ScreenReceiverUtil(Context mContext){
this.mContext = mContext;
}
public void setScreenReceiverListener(SreenStateListener mStateReceiverListener){
mStateReceiverListener = mStateReceiverListener;
// 動態(tài)啟動廣播接收器
mScreenReceiver = new SreenBroadcastReceiver();
IntentFilter filter = new IntentFilter();
filter.addAction(Intent.ACTION_SCREEN_ON);
filter.addAction(Intent.ACTION_SCREEN_OFF);
filter.addAction(Intent.ACTION_USER_PRESENT);
mContext.registerReceiver(mScreenReceiver,filter);
}
public void stopScreenReceiverListener(){
mContext.unregisterReceiver(mScreenReceiver);
}
public class SreenBroadcastReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if(mStateReceiverListener == null){
return;
}
if(Intent.ACTION_SCREEN_ON.equals(action)){ // 開屏
mStateReceiverListener.onSreenOn();
}else if(Intent.ACTION_SCREEN_OFF.equals(action)){ // 鎖屏
mStateReceiverListener.onSreenOff();
}else if(Intent.ACTION_USER_PRESENT.equals(action)){ // 解鎖
mStateReceiverListener.onUserPresent();
}
}
}
// 監(jiān)聽sreen狀態(tài)對外回調(diào)接口
public interface SreenStateListener {
void onSreenOn();
void onSreenOff();
void onUserPresent();
}
}
4.雙服務保活
- 4.1 創(chuàng)建主服務MainService
public class MainService extends Service {
MyBinder binder;
MyConn conn;
@Override
public IBinder onBind(Intent intent) {
return binder;
}
@Override
public void onCreate() {
super.onCreate();
Log.v("MYTAG", "main onCreate start...");
binder = new MyBinder();
conn = new MyConn();
//設(shè)置該服務未前臺服務
Intent targetIntent = new Intent(this, MainActivity.class);
Notification notification = null;
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.JELLY_BEAN) {
PendingIntent intent = PendingIntent.getActivity(this, 0, targetIntent, 0);
notification = new Notification.Builder(this).setSmallIcon(R.mipmap.ic_launcher).setContentTitle("DataCollect").setContentText("正在進行數(shù)據(jù)采集").setContentIntent(intent).build();
} else {
notification = new Notification();
notification.flags = Notification.FLAG_ONGOING_EVENT;
notification.flags |= Notification.FLAG_NO_CLEAR;
notification.flags |= Notification.FLAG_FOREGROUND_SERVICE;
}
startForeground(1001, notification);
bindService(new Intent(MainService.this, RemoteService.class), conn, Context.BIND_IMPORTANT);
}
class MyBinder extends IMyAidlInterface.Stub {
@Override
public String getServiceName() throws RemoteException {
return MainService.class.getSimpleName();
}
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
return START_STICKY;
}
class MyConn implements ServiceConnection {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
Log.v("MYTAG", "main onServiceConnected start...");
}
@Override
public void onServiceDisconnected(ComponentName name) {
Log.v("MYTAG", "main onServiceDisconnected start...");
startService(new Intent(MainService.this, RemoteService.class));
bindService(new Intent(MainService.this, RemoteService.class), conn, Context.BIND_IMPORTANT);
}
}
@Override
public void onDestroy() {
Log.v("onDestroy", "main onDestroy start...");
startService(new Intent(MainService.this, RemoteService.class));
bindService(new Intent(MainService.this, RemoteService.class), conn, Context.BIND_IMPORTANT);
}
}
- 4.2 創(chuàng)建守護服務
public class RemoteService extends Service {
MyConn conn;
MyBinder binder;
@Override
public IBinder onBind(Intent intent) {
return binder;
}
@Override
public void onCreate() {
super.onCreate();
Log.v("MYTAG", "remote onCreate start...");
conn = new MyConn();
binder = new MyBinder();
bindService(new Intent(this, MainService.class), conn, Context.BIND_IMPORTANT);
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
return START_STICKY;
}
class MyBinder extends IMyAidlInterface.Stub {
@Override
public String getServiceName() throws RemoteException {
return RemoteService.class.getSimpleName();
}
}
class MyConn implements ServiceConnection {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
Log.v("MYTAG", "remote onServiceConnected start...");
}
@Override
public void onServiceDisconnected(ComponentName name) {
Log.v("MYTAG", "remote onServiceDisconnected start...");
startService(new Intent(RemoteService.this, MainService.class));
bindService(new Intent(RemoteService.this, MainService.class), conn, Context.BIND_IMPORTANT);
}
}
@Override
public void onDestroy() {
Log.v("MYTAG", "remote onDestroy start...");
}
}
- 4.3 注冊服務
<service
android:name=".alive.doubles.two.MainService"
android:enabled="true"
android:exported="true"
/>
<service
android:name=".alive.doubles.two.RemoteService"
android:enabled="true"
android:exported="true"
android:process=":remote"
/>
- 4.4 調(diào)用服務
startService (new Intent (this, MainService.class));
startService (new Intent (this, RemoteService.class));
5.定時服務JobService编诹眩活
- 5.1 創(chuàng)建定時服務AliveJobService
@TargetApi(21)
public class AliveJobService extends JobService {
private final static String TAG = AliveJobService.class.getSimpleName();
private volatile static Service mKeepAliveService = null;
public static boolean isJobServiceAlive() {
return mKeepAliveService != null;
}
private static final int MESSAGE_ID_TASK = 0x01;
private Handler mHandler = new Handler(new Handler.Callback() {
@Override
public boolean handleMessage(Message msg) {
if (SystemUtils.isAPPALive(getApplicationContext(), "com.geduo.datacollect")) {
Toast.makeText(getApplicationContext(), "app alive", Toast.LENGTH_SHORT).show();
} else {
Toast.makeText(getApplicationContext(), "app die", Toast.LENGTH_SHORT).show();
startActivity(new Intent(AliveJobService.this,MainActivity.class));
}
jobFinished((JobParameters) msg.obj, false);
return true;
}
});
@Override
public boolean onStartJob(JobParameters params) {
Log.d(TAG, "onStartJob start...");
mKeepAliveService = this;
Message msg = Message.obtain(mHandler, MESSAGE_ID_TASK, params);
mHandler.sendMessage(msg);
return true;
}
@Override
public boolean onStopJob(JobParameters params) {
Log.d(TAG, "onStopJob start...");
mHandler.removeMessages(MESSAGE_ID_TASK);
return false;
}
@Override
public void onDestroy() {
super.onDestroy();
Log.d(TAG, "onDestroy start...");
}
}
- 5.2 創(chuàng)建JobSchedulerManager管理定時服務
public class JobSchedulerManager {
private static final int JOB_ID = 1;
private static JobSchedulerManager mJobManager;
private JobScheduler mJobScheduler;
private static Context mContext;
private JobSchedulerManager(Context ctxt){
this.mContext = ctxt;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
mJobScheduler = (JobScheduler)ctxt.getSystemService(Context.JOB_SCHEDULER_SERVICE);
}
}
public final static JobSchedulerManager getJobSchedulerInstance(Context ctxt){
if(mJobManager == null){
mJobManager = new JobSchedulerManager(ctxt);
}
return mJobManager;
}
@SuppressLint("MissingPermission")
public void startJobScheduler(){
// 如果JobService已經(jīng)啟動或API<21妄壶,返回
if(AliveJobService.isJobServiceAlive() || isBelowLOLLIPOP()){
return;
}
// 構(gòu)建JobInfo對象,傳遞給JobSchedulerService
int id = JOB_ID;
mJobScheduler.cancel(id);
JobInfo.Builder builder = new JobInfo.Builder(JOB_ID,new ComponentName(mContext, AliveJobService.class));
if (Build.VERSION.SDK_INT >= 24) {
builder.setMinimumLatency(JobInfo.DEFAULT_INITIAL_BACKOFF_MILLIS); //執(zhí)行的最小延遲時間
builder.setOverrideDeadline(JobInfo.DEFAULT_INITIAL_BACKOFF_MILLIS); //執(zhí)行的最長延時時間
builder.setMinimumLatency(JobInfo.DEFAULT_INITIAL_BACKOFF_MILLIS);
builder.setBackoffCriteria(JobInfo.DEFAULT_INITIAL_BACKOFF_MILLIS, JobInfo.BACKOFF_POLICY_LINEAR);//線性重試方案
} else {
builder.setPeriodic(JobInfo.DEFAULT_INITIAL_BACKOFF_MILLIS);
}
builder.setPersisted(true); // 設(shè)置設(shè)備重啟時寄狼,執(zhí)行該任務
builder.setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY);
builder.setRequiresCharging(false); // 當插入充電器丁寄,執(zhí)行該任務
JobInfo info = builder.build();
mJobScheduler.schedule(info); //開始定時執(zhí)行該系統(tǒng)任務
}
@TargetApi(21)
public void stopJobScheduler(){
if(isBelowLOLLIPOP())
return;
mJobScheduler.cancelAll();
}
private boolean isBelowLOLLIPOP(){
// API< 21
return Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP;
}
}
6.流氓手段通過無限循環(huán)播放一個無聲音樂來保活
- 6.1 創(chuàng)建服務PlayerMusicService
public class PlayerMusicService extends Service {
private final static String TAG = "PlayerMusicService";
private MediaPlayer mMediaPlayer;
@Nullable
@Override
public IBinder onBind(Intent intent) {
return null;
}
@Override
public void onCreate() {
super.onCreate();
Log.d(TAG,"onCreate start...");
mMediaPlayer = MediaPlayer.create(getApplicationContext(), R.raw.silent);
mMediaPlayer.setLooping(true);
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
new Thread(new Runnable() {
@Override
public void run() {
startPlayMusic();
}
}).start();
return START_STICKY;
}
private void startPlayMusic(){
if(mMediaPlayer != null){
Log.d(TAG,"startPlayMusic start...");
mMediaPlayer.start();
}
}
private void stopPlayMusic(){
if(mMediaPlayer != null){
Log.d(TAG,"stopPlayMusic start...");
mMediaPlayer.stop();
}
}
@Override
public void onDestroy() {
super.onDestroy();
stopPlayMusic();
Log.d(TAG,TAG+"onDestroy start...");
// 重啟自己
Intent intent = new Intent(getApplicationContext(),PlayerMusicService.class);
startService(intent);
}
}
- 6.2 注冊服務
<service android:name=".alive.music.PlayerMusicService"
android:enabled="true"
android:exported="true"
android:process=":music_service"/>
探索發(fā)現(xiàn)
以上就幾種常見的后臺服務崩觯活常用的解決方案狡逢,通過測試發(fā)現(xiàn)宁舰,Activity逼绰眩活,Notication甭瑁活腋腮,雙Services守護,SyncAdapter壤蚜、JobService即寡,循環(huán)播放音樂,這些方案不管是明手段還是流氓手段在android5.0下都還算好使袜刷,但是在Android6.0以后不管是哪種方案聪富,在app進入后臺,亮屏情況下好用著蟹,如果手機黑屏情況下墩蔓,頂多半個小時進程就被系統(tǒng)殺死了梢莽,為此我?guī)缀醢讶W(wǎng)關(guān)于Android后臺進程保活的文章都看了一遍奸披,方案都大同小異昏名,無外乎就上面所羅列的幾種方案。
那么在6.0以后應用進入后臺后阵面,當終端處于黑屏模式轻局,也就是Doze模式時,系統(tǒng)到底進行了哪些操作:
- 暫停網(wǎng)絡訪問样刷。
- 系統(tǒng)忽略所有的WakeLock仑扑。
- 標準的AlarmManager alarms被延緩到下一個maintenance window,但使用AlarmManager的 setAndAllowWhileIdle置鼻、setExactAndAllowWhileIdle和setAlarmClock時夫壁,alarms定義事件仍會啟動。 在這些alarms啟動前沃疮,系統(tǒng)會短暫地退出Doze模式盒让。
- 系統(tǒng)不再進行WiFi掃描。
- 系統(tǒng)不允許sync adapters運行司蔬。
- 系統(tǒng)不允許JobScheduler運行邑茄。
android6.0以后google對系統(tǒng)安全這塊的保護措施更進一步增強了,程序進入后臺系統(tǒng)為了省電俊啼,就認為該應用用戶已經(jīng)不再使用了肺缕,系統(tǒng)就自動清理掉該進程,有的人可能會問微信授帕,支付寶這些應用為什么用戶殺都殺不掉同木,原因就在于他們已經(jīng)進入了系統(tǒng)進程白名單了如果你不信你可以去自己看看。 設(shè)置白名單有兩種方式跛十,一種就是手機廠商自行設(shè)置彤路,另外一種就需要用戶進入手機省電模式自己進行手動設(shè)置。
所以最終結(jié)論: 在7.0這個版本芥映,如果不通過用戶或廠家設(shè)置洲尊,至少service是絕對沒有任何辦法保活的奈偏,絕對坞嘀,除非你還能找到未知的BUG。進入進程白名單惊来,然后通過雙服務崩錾活,加上JobService定時任務保駕護航裁蚁,就可以解決問題了矢渊。但我真的很贊同谷歌這樣的做法检眯,如果每個應用都是后臺常駐,整個系統(tǒng)的性能將大打折扣昆淡,都說android 手機耗電锰瘸,現(xiàn)在android可以說是將一切統(tǒng)統(tǒng)殺掉,然后最后的決定權(quán)留給用戶昂灵。
問題反饋
在使用中有任何問題避凝,歡迎反饋給我,可以用以下聯(lián)系方式跟我交流
- QQ:303704981
- email:geduo_83@163.com
- weibo:@geduo_83
關(guān)于作者
var geduo_83 = {
nickName : "geduo_83",
site : "http://www.weibo.com/geduo83"
}