基本概念 进程类型
前台进程(foreground)
可见进程(visible)
次要服务进程
后台进程(会被保存在 LRU 列表中)
内容提供者进程
空进程
ADJ级别 定义在ProcessList.java文件,oom_adj划分为16级,从-17到16之间取值
进程state级别 定义在ActivityManager.java文件,process_state划分18类,从-1到16之间取值
force-stop并不会杀 persistent 进程;
当app被force-stop后,无法接收到任何普通广播(带FLAG_INCLUDE_STOPPED_PACKAGES的Intent除外),那么监听系统的变化来拉起进程肯定不可行;
通过force-stop杀掉的进程在packagemanager中还会设置一个flag,以至于之后所有的广播都会将你过滤掉,防止你被系统的广播呼起,直到用户手动点击icon启动该应用,该flag才会置为false。当然这只针对系统广播,我们自己发的广播只要加入flag为FLAG_INCLUDE_STOPPED_PACKAGES就可以了;
当app被force-stop后,那么alarm闹钟一并被清理;
app被force-stop后,四大组件以及相关进程都被一一清理,即便多进程架构的app也无法拉起自己;
级联诛杀:当app通过ClassLoader加载另一个app,则会在force-stop的过程中会被级联诛杀;
生死与共:当app与另一个app使用了share uid,则会在force-stop的过程,任意一方被杀则另一方也被杀,建立起生死与共的强关系。
需要反射调用,且需要系统签名,即root权限
杀进程方法
Process.killProcess(int pid): 杀pid进程
Process.killProcessQuiet(int pid):杀pid进程,且不输出log信息
Process.killProcessGroup(int uid, int pid):杀同一个uid下同一进程组下的所有进程
无论是系统杀(android机型上长按home键中清除),或者是他杀(第三方管理软件,如360,腾讯等)。其实现方法,基本都是借助ActivityManagerService的removeLruProcessLocked。
5.0以下
1 2 3 4 5 6 7 8 9 10 final void removeLruProcessLocked (ProcessRecord app) { /....... if (lrui >= 0 ) { if (!app.killed) { Slog.wtfStack(TAG, "Removing process that hasn't been killed: " + app); Process.killProcessQuiet(app.pid); } /....... } }
5.0以上
1 2 3 4 5 6 7 8 9 10 11 final void removeLruProcessLocked (ProcessRecord app) { /....... if (lrui >= 0 ) { if (!app.killed) { Slog.wtfStack(TAG, "Removing process that hasn't been killed: " + app); Process.killProcessQuiet(app.pid); Process.killProcessGroup(app.info.uid, app.pid); } /....... } }
同组杀 参考文档
/acct/uid_XX/pid_XX/cgroup.procs记录了这个进程的所fork()或execlp出来的任何进程,都是该进程的同组进程;
Process.killProcessGroup中的group不是linux中的组
App主动杀(Process.killProcess) 内存不够内核杀(LowMemoryKiller) 手动停止(killPackageProcesses) 最近任务(removeTask)
进程被杀的场景
场景
调用接口
可能影响范围
触发系统进程管理机制
LowMemoryKiller
由oom_adj从大到小杀进程
被第三方应用杀死(无Root)
killBackground或Force-stop(AccessibilityService)
前者只杀死iim_adj > 4;后者可杀所有非系统进程
被第三方应用杀死(Root)
Force-stop或kill
理论上可杀所有进程
厂商杀进程功能
Force-stop或kill
理论上可杀所有进程
用户点击Force-stop
Force-stop
可杀所有非系统进程
关机、重启
***
***
查看进程oom_adj 查看进程id
或者在其他调试工具中查看,如:AndroidStudio
查看oom_adj
1 cat /proc/[进程ID]/oom_adj
UI进程和常驻Service进程的oom_adj=0,而普通后台进程oom_adj=15
App退到后台时,其所有的进程优先级都会降低。但是UI进程是降低最为明显的,因为它占用的内存资源最多,系统内存不足的时候肯定优先杀这些占用内存高的进程来腾出资源。所以,为了尽量避免后台UI进程被杀,需要尽可能的释放一些不用的资源,尤其是图片、音视频之类的。
保活策略 返回START_STICKY 1 2 3 4 public int onStartCommand (Intent intent, int flags, int startId) { flags = START_STICKY; return super .onStartCommand(intent, flags, startId); }
Service 第一次被异常杀死后会在5秒内重启,第二次被杀死会在10秒内重启,第三次会在20秒内重启,一旦在短时间内 Service 被杀死达到5次,则系统不再拉起
双service
Low Memory Killer 机制决定是否杀进程,除了内存大小,还有进程优先级。从这个原理来说,我们可以通过提高进程的优先级来保活。
值得注意的是,Android 的前台service机制。但该机制的缺陷是通知栏保留了图标。
API level < 18
调用startForeground(ID, new Notification()),发送空的Notification ,图标则不会显示。
API level >= 18
在需要提优先级的serviceA启动一个InnerService,两个服务同时startForeground,且绑定同样的NotificationID。Stop掉InnerService ,这样通知栏图标即被移除。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 @Override public int onStartCommand (Intent intent, int flags, int startId) { startForeground(R.id.notify, new Notification()); startService(new Intent(this , FakeService.class )) ; return super .onStartCommand(intent, flags, startId); } public class FakeService extends Service { @Nullable @Override public IBinder onBind (Intent intent) { return null ; } @Override public int onStartCommand (Intent intent, int flags, int startId) { startForeground(R.id.notify, new Notification()); stopSelf(); return super .onStartCommand(intent, flags, startId); } @Override public void onDestroy () { stopForeground(true ); super .onDestroy(); } } <service android:name=".ForegroundEnablingService" /> <service android:name=".ForegroundService" />
no longer works in Android 7.1
监听系统广播判断Service状态
类型
权限/广播
开机广播
RECEIVE_BOOT_COMPLETED
网络变化
ACCESS_NETWORK_STATE CHANGE_NETWORK_STATE ACCESS_WIFI_STATE CHANGE_WIFI_STATE ACCESS_FINE_LOCATION ACCESS_LOCATION_EXTRA_COMMANDS
文件挂载
MOUNT_UNMOUNT_FILESYSTEMS
屏幕亮、灭
SCREEN_ON SCREEN_OFF
锁屏、解锁
RECEIVE_USER_PRESENT
应用安装、卸载
PACKAGE_ADDED PACKAGE_REMOVED
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 <receiver android:name="com.dbjtech.acbxt.waiqin.BootReceiver" > <intent-filter> <action android:name="android.intent.action.BOOT_COMPLETED" /> <action android:name="android.intent.action.USER_PRESENT" /> <action android:name="android.intent.action.PACKAGE_RESTARTED" /> <action android:name="com.dbjtech.waiqin.destroy" /> </intent-filter> </receiver> @Override public void onReceive (Context context, Intent intent) { if (Intent.ACTION_BOOT_COMPLETED.equals(intent.getAction())) { System.out.println("手机开机了...." ); startUploadService(context); } if (Intent.ACTION_USER_PRESENT.equals(intent.getAction())) { startUploadService(context); } }
persistent进程 AndroidManifest.xml:android:persistent=”true”
force-stop并不会杀persistent进程,需要系统shareuid
重写Service的onDestroy方法 1 2 3 4 5 6 @Override public void onDestroy () { Intent intent = new Intent(this , KeeLiveService.class ) ; startService(intent); super .onDestroy(); }
监听第三方应用的静态广播 通过反编译第三方 Top 应用,如:手机QQ、微信、支付宝、UC浏览器等,找出它们外发的广播,在应用中进行监听
AlarmManager唤醒 1 2 3 4 5 6 7 8 9 10 public void startKeepLiveService (Context context, int timeMillis, String action) { AlarmManager alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE); Intent intent = new Intent(context,KeepLiveServie.class ) ; intent.setAction(action); PendingIntent pendingIntent = PendingIntent.getService(context,0 ,intent, PendingIntent.FLAG_UPDATE_CURRENT); alarmManager.setRepeating(AlarmManager.RTC_WAKEUP,System.currentTimeMillis(),timeMillis,pendingIntent); }
账户同步
Android系统里有一个账户系统,系统定期唤醒账号更新服务,同步的事件间隔是有限制的,最短1分钟
1像素悬浮层 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 public class MainActivity extends AppCompatActivity { private static final StringTAG="keeplive" ; @Override protected void onCreate (Bundle savedInstanceState) { super .onCreate(savedInstanceState); Window window = getWindow(); window.setGravity(Gravity.LEFT|Gravity.TOP); WindowManager.LayoutParams params = window.getAttributes(); params.x=0 ; params.y=0 ; params.height=1 ; params.width=1 ; window.setAttributes(params); } } <activity android:name=".KeepAliveActivity" android:excludeFromRecents="true" android:exported="false" android:finishOnTaskLaunch="false" android:launchMode="singleInstance" android:process=":live" android:theme="@style/LiveActivityStyle" > </activity> <stylename="LiveActivityStyle" > <itemname="android:windowBackground">@android:color/transparent</item> <itemname="android:windowFrame">@null</item> <itemname="android:windowNoTitle">true</item> <itemname="android:windowIsFloating">true</item> <itemname="android:windowIsTranslucent">true</item> <itemname="android:windowContentOverlay">@null</item> <itemname="android:windowAnimationStyle">@null</item> <itemname="android:windowDisablePreview">true</item> <itemname="android:windowNoDisplay">true</item> </style> public class KeepLiveReceiver extends BroadcastReceiver { privateContextmContext; @Override public void onReceive (Context context, Intent intent) { String action = intent.getAction(); if (action.equals(Intent.ACTION_SCREEN_OFF)) { KeepLiveManeger.getInstance(mContext).startKeepLiveActivity(); } else if (action.equals(Intent.ACTION_USER_PRESENT)) { KeepLiveManeger.getInstance(mContext).destroyKeepLiveActivity(); } KeepLiveManeger.getInstance(mContext).startKeepLiveService(); } }
应用间互相拉起
App之间知道包名就可以相互唤醒了,比如杀了qq,只要微信还在就能确保随时唤醒qq。还有百度全系App都通过bdshare实现互拉互保,自定义一个广播,定时发,其他app收广播自起等
C进程 要点:
单进程
Android5.0在应用退出后,ActivityManagerService不仅把主进程给杀死,也会把主进程所属的进程组一并杀死
让c进程脱离主进程,不要受到主进程的影响:两次fork,退出第一次fork的进程,使第二个子进程是会话组长。
感知主进程是否存活有两种实现方式:
轮训判断主进程是否存活,其pid是否变为1,耗电;
文件锁,需要封装 Linux 层的文件锁供上层调用;
循环startService(),通过执行am命令;
利用 Localsocket 保证 Native 进程的唯一性,不至于出现创建多个 Native 进程以及 Native 进程变成僵尸进程等问题
双进程
父进程如何监视到子进程(监视进程)的死亡?
在linux中,子进程被终止时,会向父进程发送SIG_CHLD信号,于是我们可以安装信号处理函数,并在此信号处理函数中重新启动创建监视进程;
子进程(监视进程)如何监视到父进程死亡?
当父进程死亡以后,子进程就成为了孤儿进程由Init进程领养,于是我们可以在一个循环中读取子进程的父进程PID,当变为1就说明其父进程已经死亡,于是可以重启父进程。
最终还是执行am命令唤起常驻进程
方案3 实际运行的c进程(可执行二进制文件)
1 2 3 4 5 /data/data/com.taobao.idlefish/files/DaemonServer /data/data/com.netease.mail/files/gdaemon_20161017 /data/data/com.myj.takeout.merchant/lldb/bin/lldb-server
添加一个桌面组件,这个组件也是App的一部分, 但它却是一个广播,一直会被系统持续唤醒。参考
JobScheduler Android5.0 以上版本提供了 JobScheduler 接口,系统会定时调用该进程以使应用进行一些逻辑操作