class="markdown_views prism-dracula">
前言
保活:如何让我们的app在Android系统不被杀死 保证存活,简单做法就是提升程序的优先级,看完本文一些流氓锁机你也会了哦.但锁机源码我不打算提供 为了防止某些恶心的人直接复制然后在市面上搞破坏
android 进程优先级如下:
1. 前台进程;Foreground process
1. 用户正在交互的Activity(onResume())
2. 当某个Service绑定正在交互的Activity。
3. 被主动调用为前台Service(startForeground())
4. 组件正在执行生命周期的回调(onCreate()/onStart()/onDestroy())
5. BroadcastReceiver 正在执行onReceive();
2. 可见进程;Visible process
1. 我们的Activity处在onPause()(没有进入onStop())
2. 绑定到可见的Activity的Service。
3. 服务进程;Service process
1. 简单的startService()启动。
4. 后台进程;Background process
1. 对用户没有直接影响的进程----Activity出于onStop()的时候。
5. 空进程; Empty process
1. 不含有任何的活动的组件。(android设计的,为了第二次启动更快,采取的一个权衡)
如何查看系统进程的优先级状况
前提:需要root
本人在window环境下做示范
打开控制台
adb shell (进入android命令控制)
su (得到root权限)
cat /proc/${pid}/oom_adj
(${pid}指代进程pid)
然后控制台会输出一个数字
数字对应含义如下
主要有这么几个优先级(oom_adj值) 转载至参考文献:
UNKNOWN_ADJ = 16
预留的最低级别,一般对于缓存的进程才有可能设置成这个级别
Adjustment used in certain places where we don’t know it yet. (Generally this is something that is going to be cached, but we don’t know the exact value in the cached range to assign yet.)
CACHED_APP_MAX_ADJ = 15
缓存进程,空进程,在内存不足的情况下就会优先被kill
This is a process only hosting activities that are not visible, so it can be killed without any disruption.
CACHED_APP_MIN_ADJ = 9
缓存进程,也就是空进程
SERVICE_B_ADJ = 8
不活跃的进程
The B list of SERVICE_ADJ – these are the old and decrepit services that aren’t as shiny and interesting as the ones in the A list.
PREVIOUS_APP_ADJ = 7
切换进程
This is the process of the previous application that the user was in. This process is kept above other things, because it is very common to switch back to the previous app. This is important both for recent task switch (toggling between the two top recent apps) as well as normal UI flow such as clicking on a URI in the e-mail app to view in the browser, and then pressing back to return to e-mail.
HOME_APP_ADJ = 6
与Home交互的进程
This is a process holding the home application – we want to try avoiding killing it, even if it would normally be in the background, because the user interacts with it so much.
SERVICE_ADJ = 5
有Service的进程
This is a process holding an application service – killing it will not have much of an impact as far as the user is concerned.
HEAVY_WEIGHT_APP_ADJ = 4
高权重进程
This is a process with a heavy-weight application. It is in the background, but we want to try to avoid killing it. Value set in system/rootdir/init.rc on startup.
BACKUP_APP_ADJ = 3
正在备份的进程
This is a process currently hosting a backup operation. Killing it is not entirely fatal but is generally a bad idea.
PERCEPTIBLE_APP_ADJ = 2
可感知的进程,比如那种播放音乐
This is a process only hosting components that are perceptible to the user, and we really want to avoid killing them, but they are not immediately visible. An example is background music playback.
This is a process only hosting activities that are visible to the user, so we’d prefer they don’t disappear.
- FOREGROUND_APP_ADJ = 0
前台进程
This is the process running the current foreground app. We’d really rather not kill it!
PERSISTENT_SERVICE_ADJ = -11
重要进程
This is a process that the system or a persistent process has bound to, and indicated it is important.
PERSISTENT_PROC_ADJ = -12
核心进程
This is a system persistent process, such as telephony. Definitely don’t want to kill it, but doing so is not completely fatal.
The system process runs at the default adjustment.
NATIVE_ADJ = -17
系统起的Native进程
Special code for native processes that are not being managed by the system (so don’t have an oom adj assigned by the system).
在Android-18及以下空进程不叫CACHED_APP_MIN_ADJ ,叫HIDDEN_APP_MIN_ADJ,有这么点不一样,但是值都一样。
如何查看系统进程的PID
本人在window环境下做示范
打开控制台
adb shell (进入android命令控制)
ps
此时你会看到控制台输出很多信息 如何你需要查找你的app进程pid比较麻烦的会可以把输出信息输出到文本 ,然后在文本中查找包名
adb shell ps > print.txt (输出到print.txt文本中)
参考图
对应文本位于dosSave下(仅限本例,放的位置是根据你命令行所在文件夹位置)
流氓技术之旅
1. 锁屏时开启一个前台activity让程序变为前台进程
大致思路
开启一个广播接收锁屏,和点亮屏幕广播
锁屏时开启1个1像素透明的activity(目的让用户看不见)
点亮时销毁
tip:由于监听锁屏和点亮广播在高版本 无法静态注册(无法再清单文件中写一个广播)
创建一个Activity 让广播接收锁屏广播后开启一个一像素窗口,
注意一定要继承一个含有
- "android:windowBackground">
@android :color/transparent 的主题 不然会显示一片黑 {MOD}的东西
来看看具体代码:
public class EmptyActivity extends AppCompatActivity {
@Override
protected void onCreate (Bundle savedInstanceState) {
super .onCreate(savedInstanceState);
MyApp application = (MyApp) getApplication();
application.putActivity(this );
Window window = getWindow();
window.setGravity(Gravity.LEFT);
WindowManager.LayoutParams attributes = window.getAttributes();
attributes.height = 1 ;
attributes.width =1 ;
attributes.x = 0 ;
attributes.y = 0 ;
window.setAttributes(attributes);
}
}
主题样式
<style name ="MyStyle" parent ="Theme.AppCompat" >
<item name ="android :windowBackground" >@android:color/transparent
- "android:windowFrame"
>@null
- "android:windowNoTitle"
>true
- "android:windowIsFloating">true
- "android:backgroundDimEnabled">false
- "android:windowContentOverlay">@null
- "android:windowIsTranslucent">true
- "android:windowAnimationStyle">@null
- "android:windowDisablePreview">true
- "android:windowNoDisplay">false
style >
/**
* Created by FMY on 2017/4/2.
*/
public class MyApp extends Application {
WeakReference weakReference;
public void putActivity (Activity activity){
weakReference = new WeakReference<>(activity);
}
public void finishActivity (){
if (weakReference != null ) {
Activity activity1 = weakReference.get();
if (activity1 != null ) {
activity1.finish();
}
}
}
@Override
public void onCreate () {
super .onCreate();
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction(Intent.ACTION_SCREEN_ON);
intentFilter.addAction(Intent.ACTION_SCREEN_OFF);
intentFilter.addAction(Intent.ACTION_USER_PRESENT);
intentFilter.setPriority(10000 );
registerReceiver(new OffSreenBroadcast(), intentFilter);
}
}
最后再来看看广播接收者类OffSreenBroadcast
public class OffSreenBroadcast extends BroadcastReceiver {
private static final String TAG = "OffSreenBroadcast" ;
@Override
public void onReceive (Context context, Intent intent) {
if (intent==null ||intent.getAction()==null ) {
return ;
}
if (Intent.ACTION_SCREEN_OFF.equals(intent.getAction())) {
Log.e(TAG, "屏幕关了启动屏幕 开启一个activity: " +intent.getAction() );
Intent intent1 = new Intent(context, EmptyActivity.class);
intent1.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
context.startActivity(intent1);
}
if (Intent.ACTION_SCREEN_ON.equals(intent.getAction())) {
Log.e(TAG, "屏幕开了 销毁" +intent.getAction() );
MyApp app = (MyApp) context;
app.finishActivity();
}
}
}
2. 双进程守护
开启两个c进程守护 因为5.0以上卸载或者关闭程序会同一uid的进程一起关掉所以我这里就不做此方法阐述(需要JNI/NDK基本知识)
开启双服务 同样关闭程序的时候会把两个服务杀死 但是简单所以我们这里就介绍此种方式
tip:
双服务可以防止一些鲁大师应用的杀死,但无法避免用户直接按下最近任务按键,然后清理所有程序杀死.
一般情况下服务(startServeice)和宿主进程在一个进程 除非另行指定
指定服务和宿主进程不在同一进程方法如下:在清单文件中服务添加android:process=":aa"
属性
如果服务和宿主进程在同一进程 那么宿主进程发生奔溃现象服务将一起销毁(系统核心进程除外 放入system/app的应用 对应oom_adj=-12)
大致思路:开启两个服务相互aidl互相绑定,如果其中一个被杀死会回调另一个服务的onServiceDisconnected 此时重新绑定即可
两个服务代码:
1. OneService :
public class OneService extends Service {
public OneService () {
}
private static final String TAG = "OneService" ;
@Override
public void onCreate () {
super .onCreate();
}
class MyConnet implements ServiceConnection {
@Override
public void onServiceConnected (ComponentName name, IBinder service) {
new Thread(){
@Override
public void run () {
super .run();
while (true ) {
try {
sleep(1000 );
} catch (InterruptedException e) {
e.printStackTrace();
}
Log.e(TAG, "onServiceConnected: " );
}
}
}.start();
}
@Override
public void onServiceDisconnected (ComponentName name) {
Log.e(TAG, "onServiceDisconnected: " );
Intent intent1 = new Intent(OneService.this ,TwoService.class);
bindService(intent1,new OneService.MyConnet(), Context.BIND_AUTO_CREATE);
}
}
@Override
public int onStartCommand (Intent intent, int flags, int startId) {
Log.e(TAG, "onStartCommand: " );
Intent intent1 = new Intent(this ,TwoService.class);
bindService(intent1,new MyConnet(), Context.BIND_IMPORTANT);
NotificationCompat.Builder builder = new NotificationCompat.Builder(this );
builder.setContentTitle("前台服务开启中" );
builder.setContentTitle("one服务" );
builder.setSmallIcon(R.mipmap.ic_launcher);
startForeground(13 ,builder.build());
return START_STICKY;
}
@Override
public IBinder onBind (Intent intent) {
Log.e(TAG, "onBind: " );
return new MyIBinder();
}
class MyIBinder extends IMyAidlInterface.Stub
{
@Override
public void basicTypes () throws RemoteException {
}
}
}
TwoService:
public class TwoService extends Service {
private static final String TAG = "Two Server" ;
public TwoService () {
}
@Override
public void onCreate () {
super .onCreate();
}
class MyConnet implements ServiceConnection {
@Override
public void onServiceConnected (ComponentName name, IBinder service) {
Log.e(TAG, "onServiceConnected: " );
new Thread(){
@Override
public void run () {
super .run();
while (true ) {
try {
sleep(1000 );
} catch (InterruptedException e) {
e.printStackTrace();
}
Log.e(TAG, "onServiceConnected: " );
}
}
}.start();
}
@Override
public void onServiceDisconnected (ComponentName name) {
Log.e(TAG, "onServiceDisconnected: " );
Intent intent1 = new Intent(TwoService.this , OneService.class);
bindService(intent1,new MyConnet(), Context.BIND_IMPORTANT);
}
}
@Override
public int onStartCommand (Intent intent, int flags, int startId) {
Log.e(TAG, "onStartCommand: " );
Intent intent1 = new Intent(this ,OneService.class);
bindService(intent1,new MyConnet(), Context.BIND_AUTO_CREATE);
NotificationCompat.Builder builder = new NotificationCompat.Builder(this );
builder.setContentTitle("前台服务开启中" );
builder.setContentTitle("one服务" );
builder.setSmallIcon(R.mipmap.ic_launcher);
startForeground(13 ,builder.build());
return START_STICKY;
}
@Override
public IBinder onBind (Intent intent) {
Log.e(TAG, "onBind: " );
return new MyIBinder();
}
class MyIBinder extends IMyAidlInterface.Stub
{
@Override
public void basicTypes () throws RemoteException {
}
}
}
MainActivity :
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate (Bundle savedInstanceState) {
super .onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
startService(new Intent(this ,OneService.class));
startService(new Intent(this ,TwoService.class));
}
}
清单文件:
<manifest xmlns:android ="http://schemas.android.com/apk/res/android"
package ="com.youjiayou.fmy.maputil" >
<permission android:name ="android.permission.STATUS_BAR" />
<uses-permission android:name ="android.permission.STATUS_BAR" />
<uses-permission android:name ="android.permission.EXPAND_STATUS_BAR" />
<uses-permission android:name ="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name ="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name ="android.permission.RECEIVE_BOOT_COMPLETED" />
<uses-permission android:name ="android.permission.PACKAGE_USAGE_STATS" />
<uses-permission android:name ="android.permission.GET_TASKS" />
<uses-permission android:name ="android.permission.REORDER_TASKS" />
<uses-permission android:name ="android.permission.REAL_GET_TASKS" />
<application
android:allowBackup ="true"
android:icon ="@mipmap/ic_launcher"
android:label ="@string/app_name"
android:roundIcon ="@mipmap/ic_launcher_round"
android:supportsRtl ="true"
android:theme ="@style/AppTheme" >
<activity
android:name =".MainActivity"
android:theme ="@style/Theme.AppCompat.Light.NoActionBar" >
<intent-filter >
<action android:name ="android.intent.action.MAIN" />
<category android:name ="android.intent.category.LAUNCHER" />
intent-filter >
activity >
<receiver android:name =".BootBroadcastReceiver" >
<intent-filter >
<action android:name ="android.intent.action.BOOT_COMPLETED" />
<category android:name ="android.intent.category.HOME" />
<action android:name ="android.media.AUDIO_BECOMING_NOISY" />
<action android:name ="android.intent.action.TEST_GKP" />
intent-filter >
receiver >
<service
android:name =".MyService"
android:enabled ="true"
android:process =":oneServer"
android:exported ="true" >
service >
<service
android:process =":twoServer"
android:name =".LanchService"
android:enabled ="true"
android:exported ="true" >
service >
application >
manifest >
3. 悬浮窗
在MIUI8 中我在服务中开启了一个悬浮窗,然后我发现一个有趣的现象,按下手机的最近任务键,然后点击清理所有任务按钮,发现服务和悬浮窗没有死,这我不惊讶,惊讶的是我在最近任务手动滑动当前进程去关掉,悬浮窗和服务才死去.也就说说我们可以学习qq黑科技在屏幕上留一个像素点悬浮窗.然后你懂的
import static android.content.ContentValues.TAG;
public class MyService extends Service {
public MyService () {
}
@Override
public void onCreate () {
super .onCreate();
/**
* 悬浮窗会隐式持有此activity
*/
WindowManager systemService = (WindowManager) getSystemService(Context.WINDOW_SERVICE);
WindowManager.LayoutParams layoutParams = new WindowManager.LayoutParams();
layoutParams.format = PixelFormat.RGBA_8888;
layoutParams.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
layoutParams.width =1 ;
layoutParams.height = 1 ;
layoutParams.type = WindowManager.LayoutParams.TYPE_PHONE;
View inflate = LayoutInflater.from(this ).inflate(R.layout.hover_view, null );
inflate.findViewById(R.id.btn).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick (View v) {
Log.e("fmy" , "悬浮窗被点击了" );
Toast.makeText(MyService.this , "阿斯达斯" , Toast.LENGTH_LONG).show();
}
});
systemService.addView(inflate, layoutParams);
new Thread(){
@Override
public void run () {
super .run();
while (true ) {
try {
sleep(1000 );
} catch (InterruptedException e) {
e.printStackTrace();
}
Log.e(TAG, "run: " );
}
}
}.start();
}
@Override
public IBinder onBind (Intent intent) {
throw new UnsupportedOperationException("Not yet implemented" );
}
}
杀死进程复活方法
前面我们说了点击最最近任务按钮然后 清理所有任务就可以杀死所有相关服务.但是在android5.0中有一个新API却提供了便利JobScheduler.
JobScheduler是一把双刃剑可以省电也可以利用其保活,类似AlarManager.
我们使用JobScheduler创建一个周期任务,间隔10毫秒执行任务.(实际不可能 ,必须和手机的心率对齐,执行任务必须在窗口期执行,添加的队列存在android系统中,正因如此所以….)
JobSchedulerService2 :
@RequiresApi (api = Build.VERSION_CODES.LOLLIPOP)
public class JobSchedulerService2 extends JobService {
@Override
public boolean onStartJob (JobParameters params) {
Toast.makeText(this ,"asd" ,Toast.LENGTH_LONG).show();
return false ;
}
@Override
public boolean onStopJob (JobParameters params) {
return false ;
}
}
public class MainActivity extends AppCompatActivity {
@RequiresApi (api = Build.VERSION_CODES.LOLLIPOP)
@Override
protected void onCreate (Bundle savedInstanceState) {
super .onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
JobScheduler jobScheduler = (JobScheduler) getSystemService(Context.JOB_SCHEDULER_SERVICE);
JobInfo.Builder builder2= new JobInfo.Builder( 1 ,
new ComponentName( getPackageName(),
JobSchedulerService2.class.getName() ) );
builder2.setPeriodic(10 );
jobScheduler.schedule(builder2.build());
}
}
清单文件
<manifest xmlns:android ="http://schemas.android.com/apk/res/android"
package ="com.example.hover" >
<uses-permission android:name ="android.permission.SYSTEM_ALERT_WINDOW" />
<uses-permission android:name ="android.permission.BIND_JOB_SERVICE" />
<uses-permission android:name ="android.permission.RECEIVE_BOOT_COMPLETED" />
<application
android:allowBackup ="true"
android:icon ="@mipmap/ic_launcher"
android:label ="@string/app_name"
android:theme ="@style/AppTheme" >
<activity android:name =".MainActivity" >
<intent-filter >
<action android:name ="android.intent.action.MAIN" />
<category android:name ="android.intent.category.LAUNCHER" />
intent-filter >
activity >
<service
android:name =".JobSchedulerService2"
android:enabled ="true"
android:exported ="true"
android:permission ="android.permission.BIND_JOB_SERVICE" >
service >
application >
manifest >
上面的代码执行后哪怕杀死进程也会执行
写入系统app命令
就是把apk放入 system/app中即可
下面大家简单即可
public void onclick (View view) {
DataOutputStream dataOutputStream = null ;
BufferedReader err = null ;
try {
Process process = Runtime.getRuntime().exec("su root" );
dataOutputStream = new DataOutputStream(process.getOutputStream());
File externalStorageDirectory = Environment.getExternalStorageDirectory();
File apkFile = new File(externalStorageDirectory.getAbsoluteFile()+File.separator+"youjiayou.apk" );
String command = "cp " + apkFile.getAbsolutePath() + " system/app
" ;
dataOutputStream.writeBytes("mount -o remount rw /system
" );
dataOutputStream.write(command.getBytes("UTF-8" ));
dataOutputStream.writeBytes("chmod 777 /system/app/youjiayou.apk
" );
dataOutputStream.writeBytes("mount -o remount ro /system
" );
dataOutputStream.flush();
dataOutputStream.writeBytes("reboot
" );
dataOutputStream.writeBytes("exit
" );
dataOutputStream.flush();
process.waitFor();
String line;
String msg = "" ;
err = new BufferedReader(new InputStreamReader(process.getInputStream()));
while ((line = err.readLine()) != null ) {
msg += line;
}
Log.e("Fmy" , "结果 " +msg);
} catch (Exception e) {
Log.e("Fmy" , "发生异常" + e.getMessage());
} finally {
try {
if (dataOutputStream != null ) {
dataOutputStream.close();
}
if (err != null ) {
err.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
开机启动
开启自启需要用户手动去授权,但是在miui系统中接收到开机广播在开机几十秒后.在这里大家可以监听
这里当你开机和拔掉手机耳机的时候会发出广播
锁机代码大致思路
让用户手动开启悬浮窗权限.WindowManager.LayoutParams. type=xxxx就可以让悬浮窗在整个android系统视图最上层. = =当然大家千万别再给开机权限了不然更恶心
示例apk:正在制作稍后上传
参考文献
文献1
文献2
文献3