├── .gitignore ├── .idea ├── caches │ └── build_file_checksums.ser ├── codeStyles │ └── Project.xml ├── encodings.xml ├── gradle.xml ├── misc.xml └── runConfigurations.xml ├── DaemonService ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ └── main │ ├── AndroidManifest.xml │ ├── aidl │ └── com │ │ └── sunfusheng │ │ └── daemon │ │ └── DaemonAidl.aidl │ ├── java │ └── com │ │ └── sunfusheng │ │ └── daemon │ │ ├── AbsHeartBeatService.java │ │ ├── DaemonHolder.java │ │ ├── DaemonReceiver.java │ │ ├── DaemonService.java │ │ ├── DaemonUtil.java │ │ └── JobSchedulerService.java │ └── res │ └── values │ └── strings.xml ├── README.md ├── Sample ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ └── main │ ├── AndroidManifest.xml │ ├── java │ └── com │ │ └── sunfusheng │ │ └── daemon │ │ └── sample │ │ ├── App.java │ │ ├── HeartBeatService.java │ │ └── MainActivity.java │ └── res │ ├── drawable-v24 │ └── ic_launcher_foreground.xml │ ├── drawable │ └── ic_launcher_background.xml │ ├── layout │ └── activity_main.xml │ ├── mipmap-anydpi-v26 │ ├── ic_launcher.xml │ └── ic_launcher_round.xml │ ├── mipmap-hdpi │ ├── ic_launcher.png │ └── ic_launcher_round.png │ ├── mipmap-mdpi │ ├── ic_launcher.png │ └── ic_launcher_round.png │ ├── mipmap-xhdpi │ ├── ic_launcher.png │ └── ic_launcher_round.png │ ├── mipmap-xxhdpi │ ├── ic_launcher.png │ └── ic_launcher_round.png │ ├── mipmap-xxxhdpi │ ├── ic_launcher.png │ └── ic_launcher_round.png │ └── values │ ├── colors.xml │ ├── strings.xml │ └── styles.xml ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/libraries 5 | /.idea/modules.xml 6 | /.idea/workspace.xml 7 | .DS_Store 8 | /build 9 | /captures 10 | .externalNativeBuild 11 | -------------------------------------------------------------------------------- /.idea/caches/build_file_checksums.ser: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sunfusheng/DaemonService/21ecc868106cb2b14e9919160e67e01c66f74b9f/.idea/caches/build_file_checksums.ser -------------------------------------------------------------------------------- /.idea/codeStyles/Project.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 15 | 16 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /.idea/encodings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/gradle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 18 | 19 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 36 | 37 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 86 | 96 | 97 | 98 | 99 | 100 | 101 | 103 | -------------------------------------------------------------------------------- /.idea/runConfigurations.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | 12 | -------------------------------------------------------------------------------- /DaemonService/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /DaemonService/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.library' 2 | 3 | android { 4 | compileSdkVersion 27 5 | defaultConfig { 6 | minSdkVersion 14 7 | targetSdkVersion 27 8 | versionCode 1 9 | versionName "1.0" 10 | } 11 | 12 | compileOptions { 13 | sourceCompatibility JavaVersion.VERSION_1_8 14 | targetCompatibility JavaVersion.VERSION_1_8 15 | } 16 | 17 | buildTypes { 18 | release { 19 | minifyEnabled false 20 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 21 | } 22 | } 23 | } 24 | 25 | dependencies { 26 | implementation fileTree(dir: 'libs', include: ['*.jar']) 27 | implementation 'com.android.support:appcompat-v7:27.1.1' 28 | } 29 | -------------------------------------------------------------------------------- /DaemonService/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | -------------------------------------------------------------------------------- /DaemonService/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 17 | 18 | 23 | 24 | 27 | 28 | 29 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /DaemonService/src/main/aidl/com/sunfusheng/daemon/DaemonAidl.aidl: -------------------------------------------------------------------------------- 1 | package com.sunfusheng.daemon; 2 | 3 | interface DaemonAidl { 4 | void startService(); 5 | void stopService(); 6 | } 7 | -------------------------------------------------------------------------------- /DaemonService/src/main/java/com/sunfusheng/daemon/AbsHeartBeatService.java: -------------------------------------------------------------------------------- 1 | package com.sunfusheng.daemon; 2 | 3 | import android.app.Service; 4 | import android.content.ComponentName; 5 | import android.content.Context; 6 | import android.content.Intent; 7 | import android.content.ServiceConnection; 8 | import android.os.IBinder; 9 | import android.os.RemoteException; 10 | import android.support.annotation.Nullable; 11 | import android.util.Log; 12 | 13 | import java.util.Timer; 14 | import java.util.TimerTask; 15 | 16 | /** 17 | * 当前进程服务 18 | * 19 | * @author sunfusheng on 2018/8/1. 20 | */ 21 | public abstract class AbsHeartBeatService extends Service { 22 | private static final String TAG = "---> HeartBeatService"; 23 | 24 | private Timer timer = new Timer(); 25 | private TimerTask timerTask = new TimerTask() { 26 | @Override 27 | public void run() { 28 | onHeartBeat(); 29 | } 30 | }; 31 | 32 | private final DaemonAidl aidl = new DaemonAidl.Stub() { 33 | @Override 34 | public void startService() throws RemoteException { 35 | Log.d(TAG, "aidl startService()"); 36 | } 37 | 38 | @Override 39 | public void stopService() throws RemoteException { 40 | Log.e(TAG, "aidl stopService()"); 41 | } 42 | }; 43 | 44 | private final ServiceConnection serviceConnection = new ServiceConnection() { 45 | @Override 46 | public void onServiceConnected(ComponentName name, IBinder service) { 47 | Log.d(TAG, "onServiceConnected() 已绑定"); 48 | try { 49 | service.linkToDeath(() -> { 50 | Log.e(TAG, "onServiceConnected() linkToDeath"); 51 | try { 52 | aidl.startService(); 53 | startBindService(); 54 | } catch (RemoteException e) { 55 | e.printStackTrace(); 56 | } 57 | }, 1); 58 | } catch (RemoteException e) { 59 | e.printStackTrace(); 60 | } 61 | } 62 | 63 | @Override 64 | public void onServiceDisconnected(ComponentName name) { 65 | Log.e(TAG, "onServiceDisconnected() 已解绑"); 66 | try { 67 | aidl.stopService(); 68 | } catch (RemoteException e) { 69 | e.printStackTrace(); 70 | } 71 | } 72 | 73 | @Override 74 | public void onBindingDied(ComponentName name) { 75 | onServiceDisconnected(name); 76 | } 77 | }; 78 | 79 | private void startBindService() { 80 | try { 81 | startService(new Intent(this, DaemonService.class)); 82 | bindService(new Intent(this, DaemonService.class), serviceConnection, Context.BIND_IMPORTANT); 83 | } catch (Exception e) { 84 | e.printStackTrace(); 85 | } 86 | } 87 | 88 | @Override 89 | public void onCreate() { 90 | super.onCreate(); 91 | Log.d(TAG, "onCreate()"); 92 | onStartService(); 93 | startBindService(); 94 | if (getHeartBeatMillis() > 0) { 95 | timer.schedule(timerTask, getDelayExecutedMillis(), getHeartBeatMillis()); 96 | } 97 | } 98 | 99 | @Override 100 | public int onStartCommand(Intent intent, int flags, int startId) { 101 | Log.d(TAG, "onStartCommand()"); 102 | return Service.START_STICKY; 103 | } 104 | 105 | @Nullable 106 | @Override 107 | public IBinder onBind(Intent intent) { 108 | Log.d(TAG, "onBind()"); 109 | return (IBinder) aidl; 110 | } 111 | 112 | @Override 113 | public void onDestroy() { 114 | super.onDestroy(); 115 | Log.e(TAG, "onDestroy()"); 116 | onStopService(); 117 | 118 | unbindService(serviceConnection); 119 | DaemonHolder.restartService(getApplicationContext(), DaemonHolder.mService); 120 | 121 | try { 122 | timer.cancel(); 123 | timer.purge(); 124 | timerTask.cancel(); 125 | } catch (Exception e) { 126 | e.printStackTrace(); 127 | } 128 | } 129 | 130 | public abstract void onStartService(); 131 | 132 | public abstract void onStopService(); 133 | 134 | public abstract long getDelayExecutedMillis(); 135 | 136 | public abstract long getHeartBeatMillis(); 137 | 138 | public abstract void onHeartBeat(); 139 | } 140 | -------------------------------------------------------------------------------- /DaemonService/src/main/java/com/sunfusheng/daemon/DaemonHolder.java: -------------------------------------------------------------------------------- 1 | package com.sunfusheng.daemon; 2 | 3 | import android.annotation.SuppressLint; 4 | import android.app.AlarmManager; 5 | import android.app.PendingIntent; 6 | import android.app.Service; 7 | import android.content.ComponentName; 8 | import android.content.Context; 9 | import android.content.Intent; 10 | import android.content.pm.PackageManager; 11 | import android.os.Build; 12 | import android.os.SystemClock; 13 | import android.util.Log; 14 | 15 | /** 16 | * @author sunfusheng on 2018/8/1. 17 | */ 18 | public class DaemonHolder { 19 | private static final String TAG = "---> DaemonHolder"; 20 | @SuppressLint("StaticFieldLeak") 21 | static Context mContext; 22 | static Class mService; 23 | private static String mServiceCanonicalName; 24 | 25 | private DaemonHolder() { 26 | } 27 | 28 | public static void init(Context context, Class service) { 29 | mContext = context; 30 | mService = service; 31 | mServiceCanonicalName = service.getCanonicalName(); 32 | startService(); 33 | } 34 | 35 | public static void startService() { 36 | if (mContext != null && mService != null && !DaemonUtil.isServiceRunning(mContext, mServiceCanonicalName)) { 37 | try { 38 | mContext.startService(new Intent(mContext, mService)); 39 | Log.d(TAG, "启动服务"); 40 | } catch (Exception e) { 41 | e.printStackTrace(); 42 | } 43 | 44 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP && !DaemonUtil.isXiaomi()) { 45 | JobSchedulerService.scheduleJobService(mContext); 46 | Log.d(TAG, "启动 JobService"); 47 | } 48 | 49 | mContext.getPackageManager().setComponentEnabledSetting(new ComponentName(mContext.getPackageName(), mService.getName()), 50 | PackageManager.COMPONENT_ENABLED_STATE_ENABLED, PackageManager.DONT_KILL_APP); 51 | } 52 | } 53 | 54 | public static void stopService() { 55 | if (mContext != null && mService != null && DaemonUtil.isServiceRunning(mContext, mServiceCanonicalName)) { 56 | try { 57 | mContext.stopService(new Intent(mContext, mService)); 58 | Log.d(TAG, "停止服务"); 59 | } catch (Exception e) { 60 | e.printStackTrace(); 61 | } 62 | } 63 | } 64 | 65 | public static void restartService(Context context, Class cls) { 66 | Log.d(TAG, "重启服务 AlarmManager"); 67 | Intent intent = new Intent(context, cls); 68 | intent.setPackage(context.getPackageName()); 69 | PendingIntent pendingIntent = PendingIntent.getService(context, 1, intent, PendingIntent.FLAG_ONE_SHOT); 70 | AlarmManager alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE); 71 | if (alarmManager != null) { 72 | alarmManager.set(AlarmManager.ELAPSED_REALTIME, SystemClock.elapsedRealtime() + DaemonUtil.getIntervalTime(), pendingIntent); 73 | } 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /DaemonService/src/main/java/com/sunfusheng/daemon/DaemonReceiver.java: -------------------------------------------------------------------------------- 1 | package com.sunfusheng.daemon; 2 | 3 | import android.content.BroadcastReceiver; 4 | import android.content.Context; 5 | import android.content.Intent; 6 | import android.util.Log; 7 | 8 | /** 9 | * @author sunfusheng on 2018/8/3. 10 | */ 11 | public class DaemonReceiver extends BroadcastReceiver { 12 | private static final String TAG = "---> DaemonReceiver"; 13 | 14 | @Override 15 | public void onReceive(Context context, Intent intent) { 16 | if (intent != null) { 17 | Log.d(TAG, "onReceive() action: " + intent.getAction()); 18 | } 19 | DaemonHolder.startService(); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /DaemonService/src/main/java/com/sunfusheng/daemon/DaemonService.java: -------------------------------------------------------------------------------- 1 | package com.sunfusheng.daemon; 2 | 3 | import android.app.Service; 4 | import android.content.BroadcastReceiver; 5 | import android.content.ComponentName; 6 | import android.content.Context; 7 | import android.content.Intent; 8 | import android.content.IntentFilter; 9 | import android.content.ServiceConnection; 10 | import android.net.ConnectivityManager; 11 | import android.net.Network; 12 | import android.net.NetworkRequest; 13 | import android.os.Build; 14 | import android.os.IBinder; 15 | import android.os.RemoteException; 16 | import android.support.annotation.Nullable; 17 | import android.util.Log; 18 | 19 | /** 20 | * 远程进程服务 21 | * 22 | * @author sunfusheng on 2018/8/1. 23 | */ 24 | public class DaemonService extends Service { 25 | private static final String TAG = "---> DaemonService"; 26 | private ScreenBroadcastReceiver screenBroadcastReceiver = new ScreenBroadcastReceiver(); 27 | 28 | private final DaemonAidl aidl = new DaemonAidl.Stub() { 29 | @Override 30 | public void startService() throws RemoteException { 31 | Log.d(TAG, "aidl startService()"); 32 | } 33 | 34 | @Override 35 | public void stopService() throws RemoteException { 36 | Log.e(TAG, "aidl stopService()"); 37 | } 38 | }; 39 | 40 | private final ServiceConnection serviceConnection = new ServiceConnection() { 41 | @Override 42 | public void onServiceConnected(ComponentName name, IBinder service) { 43 | Log.d(TAG, "onServiceConnected() 已绑定"); 44 | try { 45 | service.linkToDeath(() -> { 46 | Log.e(TAG, "onServiceConnected() linkToDeath"); 47 | try { 48 | aidl.startService(); 49 | startBindService(); 50 | } catch (RemoteException e) { 51 | e.printStackTrace(); 52 | } 53 | }, 1); 54 | } catch (RemoteException e) { 55 | e.printStackTrace(); 56 | } 57 | } 58 | 59 | @Override 60 | public void onServiceDisconnected(ComponentName name) { 61 | Log.e(TAG, "onServiceDisconnected() 已解绑"); 62 | try { 63 | aidl.stopService(); 64 | } catch (RemoteException e) { 65 | e.printStackTrace(); 66 | } 67 | } 68 | 69 | @Override 70 | public void onBindingDied(ComponentName name) { 71 | onServiceDisconnected(name); 72 | } 73 | }; 74 | 75 | private void startBindService() { 76 | try { 77 | if (DaemonHolder.mService != null) { 78 | startService(new Intent(this, DaemonHolder.mService)); 79 | bindService(new Intent(this, DaemonHolder.mService), serviceConnection, Context.BIND_IMPORTANT); 80 | } 81 | } catch (Exception e) { 82 | e.printStackTrace(); 83 | } 84 | } 85 | 86 | @Override 87 | public void onCreate() { 88 | super.onCreate(); 89 | Log.d(TAG, "onCreate()"); 90 | startBindService(); 91 | listenNetworkConnectivity(); 92 | screenBroadcastReceiver.registerScreenBroadcastReceiver(this); 93 | } 94 | 95 | @Override 96 | public int onStartCommand(Intent intent, int flags, int startId) { 97 | Log.d(TAG, "onStartCommand()"); 98 | return Service.START_STICKY; 99 | } 100 | 101 | @Nullable 102 | @Override 103 | public IBinder onBind(Intent intent) { 104 | Log.d(TAG, "onBind()"); 105 | return (IBinder) aidl; 106 | } 107 | 108 | @Override 109 | public void onDestroy() { 110 | super.onDestroy(); 111 | Log.e(TAG, "onDestroy()"); 112 | unbindService(serviceConnection); 113 | 114 | DaemonHolder.restartService(getApplicationContext(), getClass()); 115 | screenBroadcastReceiver.unregisterScreenBroadcastReceiver(this); 116 | } 117 | 118 | private void listenNetworkConnectivity() { 119 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { 120 | ConnectivityManager connectivityManager = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE); 121 | if (connectivityManager != null) { 122 | connectivityManager.requestNetwork(new NetworkRequest.Builder().build(), new ConnectivityManager.NetworkCallback() { 123 | @Override 124 | public void onAvailable(Network network) { 125 | super.onAvailable(network); 126 | Log.d(TAG, "onAvailable()"); 127 | DaemonHolder.startService(); 128 | } 129 | 130 | @Override 131 | public void onUnavailable() { 132 | super.onUnavailable(); 133 | Log.d(TAG, "onUnavailable()"); 134 | DaemonHolder.startService(); 135 | } 136 | 137 | @Override 138 | public void onLost(Network network) { 139 | super.onLost(network); 140 | Log.d(TAG, "onLost()"); 141 | DaemonHolder.startService(); 142 | } 143 | }); 144 | } 145 | } 146 | } 147 | 148 | private class ScreenBroadcastReceiver extends BroadcastReceiver { 149 | private boolean isRegistered = false; 150 | 151 | @Override 152 | public void onReceive(Context context, Intent intent) { 153 | if (intent != null) { 154 | Log.e(TAG, "onReceive() action: " + intent.getAction()); 155 | } 156 | DaemonHolder.startService(); 157 | } 158 | 159 | public void registerScreenBroadcastReceiver(Context context) { 160 | if (!isRegistered) { 161 | isRegistered = true; 162 | IntentFilter filter = new IntentFilter(); 163 | filter.addAction(Intent.ACTION_SCREEN_ON); // 开屏 164 | filter.addAction(Intent.ACTION_SCREEN_OFF); // 锁屏 165 | filter.addAction(Intent.ACTION_USER_PRESENT); // 解锁 166 | filter.addAction(Intent.ACTION_CLOSE_SYSTEM_DIALOGS); // Home键 167 | context.registerReceiver(ScreenBroadcastReceiver.this, filter); 168 | } 169 | } 170 | 171 | public void unregisterScreenBroadcastReceiver(Context context) { 172 | if (isRegistered) { 173 | isRegistered = false; 174 | context.unregisterReceiver(ScreenBroadcastReceiver.this); 175 | } 176 | } 177 | } 178 | } 179 | -------------------------------------------------------------------------------- /DaemonService/src/main/java/com/sunfusheng/daemon/DaemonUtil.java: -------------------------------------------------------------------------------- 1 | package com.sunfusheng.daemon; 2 | 3 | import android.app.ActivityManager; 4 | import android.content.Context; 5 | import android.os.Build; 6 | import android.text.TextUtils; 7 | 8 | /** 9 | * @author sunfusheng on 2018/8/1. 10 | */ 11 | public class DaemonUtil { 12 | private static final long INTERVAL_TIME = 30 * 1000; 13 | private static final String BRAND = Build.BRAND.toLowerCase(); 14 | private static ActivityManager activityManager; 15 | 16 | public static ActivityManager getActivityManager(Context context) { 17 | if (activityManager == null) { 18 | activityManager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE); 19 | } 20 | return activityManager; 21 | } 22 | 23 | public static boolean isServiceRunning(Context context, String serviceName) { 24 | if (TextUtils.isEmpty(serviceName)) { 25 | return false; 26 | } 27 | for (ActivityManager.RunningServiceInfo serviceInfo : getActivityManager(context).getRunningServices(100)) { 28 | if (serviceInfo.service.getClassName().equals(serviceName)) { 29 | return true; 30 | } 31 | } 32 | return false; 33 | } 34 | 35 | public static boolean isProcessRunning(Context context, String processName) { 36 | for (ActivityManager.RunningAppProcessInfo processInfo : getActivityManager(context).getRunningAppProcesses()) { 37 | if (processInfo.processName.equals(processName)) { 38 | return true; 39 | } 40 | } 41 | return false; 42 | } 43 | 44 | public static boolean isProcessRunning(Context context) { 45 | return isProcessRunning(context, getProcessName(context)); 46 | } 47 | 48 | public static String getProcessName(Context context, int pid) { 49 | for (ActivityManager.RunningAppProcessInfo processInfo : getActivityManager(context).getRunningAppProcesses()) { 50 | if (processInfo != null && processInfo.pid == pid) { 51 | return processInfo.processName; 52 | } 53 | } 54 | return null; 55 | } 56 | 57 | public static String getProcessName(Context context) { 58 | return getProcessName(context, android.os.Process.myPid()); 59 | } 60 | 61 | public static long getIntervalTime() { 62 | return INTERVAL_TIME; 63 | } 64 | 65 | public static boolean isXiaomi() { 66 | return Build.MANUFACTURER.toLowerCase().equals("xiaomi"); 67 | } 68 | 69 | public static boolean isVivo() { 70 | return BRAND.contains("vivo") || BRAND.contains("bbk"); 71 | } 72 | 73 | public static boolean isOppo() { 74 | return BRAND.contains("oppo"); 75 | } 76 | 77 | public static boolean isHuawei() { 78 | return BRAND.contains("huawei") || BRAND.contains("honor"); 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /DaemonService/src/main/java/com/sunfusheng/daemon/JobSchedulerService.java: -------------------------------------------------------------------------------- 1 | package com.sunfusheng.daemon; 2 | 3 | import android.app.job.JobInfo; 4 | import android.app.job.JobParameters; 5 | import android.app.job.JobScheduler; 6 | import android.app.job.JobService; 7 | import android.content.ComponentName; 8 | import android.content.Context; 9 | import android.os.Build; 10 | import android.support.annotation.RequiresApi; 11 | import android.util.Log; 12 | 13 | /** 14 | * @author sunfusheng on 2018/8/1. 15 | */ 16 | @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) 17 | public class JobSchedulerService extends JobService { 18 | private static final String TAG = "---> JobService"; 19 | private static final int JOB_ID = 10000; 20 | 21 | public static void scheduleJobService(Context context) { 22 | boolean isSuccess = false; 23 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { 24 | JobInfo.Builder builder = new JobInfo.Builder(JOB_ID, new ComponentName(context, JobSchedulerService.class)); 25 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { 26 | builder.setMinimumLatency(DaemonUtil.getIntervalTime()); 27 | builder.setOverrideDeadline(DaemonUtil.getIntervalTime() * 2); 28 | builder.setBackoffCriteria(DaemonUtil.getIntervalTime(), JobInfo.BACKOFF_POLICY_LINEAR);//线性重试方案 29 | } else { 30 | builder.setPeriodic(DaemonUtil.getIntervalTime()); 31 | } 32 | builder.setPersisted(true); 33 | JobScheduler jobScheduler = (JobScheduler) context.getSystemService(Context.JOB_SCHEDULER_SERVICE); 34 | if (jobScheduler != null) { 35 | jobScheduler.cancelAll(); 36 | isSuccess = jobScheduler.schedule(builder.build()) == JobScheduler.RESULT_SUCCESS; 37 | } 38 | } 39 | if (isSuccess) { 40 | Log.d(TAG, "Scheduler Success!"); 41 | } else { 42 | Log.e(TAG, "Scheduler Failed!"); 43 | } 44 | } 45 | 46 | @Override 47 | public boolean onStartJob(JobParameters params) { 48 | Log.d(TAG, "onStartJob()"); 49 | DaemonHolder.startService(); 50 | return false; 51 | } 52 | 53 | @Override 54 | public boolean onStopJob(JobParameters params) { 55 | Log.d(TAG, "onStopJob()"); 56 | DaemonHolder.startService(); 57 | return false; 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /DaemonService/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | DaemonService 3 | 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # DaemonService 2 | Android端心跳服务与进程保活 3 | 4 | ### 使用 5 | 6 | 继承AbsHeartBeatService抽象心跳服务,在onHeartBeat()中处理自己的任务,具体保活策略不需要关心 7 | 8 | ```java 9 | public class HeartBeatService extends AbsHeartBeatService { 10 | 11 | @Override 12 | public void onStartService() { 13 | } 14 | 15 | @Override 16 | public void onStopService() { 17 | } 18 | 19 | @Override 20 | public long getHeartBeatMillis() { 21 | return 30 * 1000; 22 | } 23 | 24 | @Override 25 | public void onHeartBeat() { 26 | } 27 | } 28 | ``` 29 | 30 | 在Manifest中注册服务 31 | 32 | ```xml 33 | 34 | ``` 35 | 36 | 初始化并启动服务 37 | 38 | ```java 39 | DaemonHolder.init(this, HeartBeatService.class); 40 | ``` 41 | 42 | ### 实现思想 43 | 44 | 实现进程保活,暂时实现了双进程守护、JobService检测与拉起、进程死亡AlarmManager定时拉起、 45 | 广播监听(网络变化、开机等),同时通过Timer和TimerTask实现心跳服务。 46 | 47 | #### 1、双进程守护 48 | 49 | 双进程即本地进程和远程进程,看两个类: 50 | AbsHeartBeatService:本地进程,抽象的心跳服务 51 | DaemonService:远程进程,即守护进程 52 | 启动本地服务后会启动远程进程的服务并绑定远程服务,同时远程服务也会绑定本地进程的服务, 53 | 任何一个服务停止都会得到另一个进程的Binder通知,即刻被拉起,实现进程保活的一种方式 54 | 55 | #### 2、JobService检测与拉起 56 | 57 | Android5.0以上可以使用JobService来做定时任务,定时检测本地进程的服务是否在运行,参考JobSchedulerService, 58 | 但是个别深度定制的ROM厂商屏蔽了JobService,比如小米手机。 59 | 60 | #### 3、进程死亡AlarmManager定时拉起 61 | 62 | AlarmManager是提供一种访问系统闹钟服务的方式,允许你去设置在将来的某个时间点去执行你的应用程序。 63 | 当你的闹钟时间到时,在它上面注册的一个意图(Intent)将会被系统以广播发出,然后自动启动目标程序,如果它没有正在运行。 64 | 所以,不管是我们的本地进程还是我们的远程进程,如果他们死了,就在死的时候定一个被拉活的闹钟,等待复活。 65 | 66 | #### 4、广播监听 67 | 68 | 动态广播监听:网络变化、开屏、锁屏、解锁、点击Home键 69 | 静态广播监听:开机、连接电源、断开电源、安装应用、卸载应用 -------------------------------------------------------------------------------- /Sample/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /Sample/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | compileSdkVersion 27 5 | defaultConfig { 6 | applicationId "com.sunfusheng.daemon.sample" 7 | minSdkVersion 14 8 | targetSdkVersion 27 9 | versionCode 1 10 | versionName "1.0" 11 | } 12 | 13 | compileOptions { 14 | sourceCompatibility JavaVersion.VERSION_1_8 15 | targetCompatibility JavaVersion.VERSION_1_8 16 | } 17 | 18 | buildTypes { 19 | release { 20 | minifyEnabled false 21 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 22 | } 23 | } 24 | } 25 | 26 | dependencies { 27 | implementation fileTree(dir: 'libs', include: ['*.jar']) 28 | implementation 'com.android.support:appcompat-v7:27.1.1' 29 | implementation project(':DaemonService') 30 | } 31 | -------------------------------------------------------------------------------- /Sample/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | -------------------------------------------------------------------------------- /Sample/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /Sample/src/main/java/com/sunfusheng/daemon/sample/App.java: -------------------------------------------------------------------------------- 1 | package com.sunfusheng.daemon.sample; 2 | 3 | import android.app.Application; 4 | import android.util.Log; 5 | 6 | import com.sunfusheng.daemon.DaemonHolder; 7 | import com.sunfusheng.daemon.DaemonUtil; 8 | 9 | /** 10 | * @author sunfusheng on 2018/8/1. 11 | */ 12 | public class App extends Application { 13 | private static final String TAG = "---> App"; 14 | 15 | @Override 16 | public void onCreate() { 17 | super.onCreate(); 18 | Log.d(TAG, "onCreate() 【" + DaemonUtil.getProcessName(this) + "】"); 19 | DaemonHolder.init(this, HeartBeatService.class); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Sample/src/main/java/com/sunfusheng/daemon/sample/HeartBeatService.java: -------------------------------------------------------------------------------- 1 | package com.sunfusheng.daemon.sample; 2 | 3 | import android.os.Looper; 4 | import android.util.Log; 5 | 6 | import com.sunfusheng.daemon.AbsHeartBeatService; 7 | 8 | /** 9 | * @author sunfusheng on 2018/8/3. 10 | */ 11 | public class HeartBeatService extends AbsHeartBeatService { 12 | private static final String TAG = "---> HeartBeatService"; 13 | private static final android.os.Handler mainThreadHandler = new android.os.Handler(Looper.getMainLooper()); 14 | 15 | @Override 16 | public void onStartService() { 17 | Log.d(TAG, "onStartService()"); 18 | } 19 | 20 | @Override 21 | public void onStopService() { 22 | Log.e(TAG, "onStopService()"); 23 | } 24 | 25 | @Override 26 | public long getDelayExecutedMillis() { 27 | return 0; 28 | } 29 | 30 | @Override 31 | public long getHeartBeatMillis() { 32 | return 30 * 1000; 33 | } 34 | 35 | @Override 36 | public void onHeartBeat() { 37 | Log.d(TAG, "onHeartBeat()"); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /Sample/src/main/java/com/sunfusheng/daemon/sample/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.sunfusheng.daemon.sample; 2 | 3 | import android.os.Bundle; 4 | import android.support.v7.app.AppCompatActivity; 5 | import android.view.View; 6 | import android.widget.TextView; 7 | 8 | import com.sunfusheng.daemon.DaemonHolder; 9 | 10 | public class MainActivity extends AppCompatActivity { 11 | private TextView vText; 12 | 13 | @Override 14 | protected void onCreate(Bundle savedInstanceState) { 15 | super.onCreate(savedInstanceState); 16 | setContentView(R.layout.activity_main); 17 | vText = findViewById(R.id.text); 18 | } 19 | 20 | public void onClick(View v) { 21 | switch (v.getId()) { 22 | case R.id.startService: 23 | DaemonHolder.startService(); 24 | break; 25 | case R.id.stopService: 26 | DaemonHolder.stopService(); 27 | break; 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /Sample/src/main/res/drawable-v24/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 12 | 13 | 19 | 22 | 25 | 26 | 27 | 28 | 34 | 35 | -------------------------------------------------------------------------------- /Sample/src/main/res/drawable/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 11 | 16 | 21 | 26 | 31 | 36 | 41 | 46 | 51 | 56 | 61 | 66 | 71 | 76 | 81 | 86 | 91 | 96 | 101 | 106 | 111 | 116 | 121 | 126 | 131 | 136 | 141 | 146 | 151 | 156 | 161 | 166 | 171 | 172 | -------------------------------------------------------------------------------- /Sample/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 12 | 13 |