安卓-进程

进程

当某个应用组件启动且该应用没有运行其他任何组件时,Android 系统会使用单个执行线程为应用启动新的 Linux 进程。默认情况下,同一应用的所有组件在相同的进程和线程(称为“主”线程)中运行。

各类组件元素的清单文件条目<activity><service><receiver><provider>—均支持 android:process 属性,此属性可以指定该组件应在哪个进程运行。

5个常用进程

优先级:前台进程>可见进程>服务进程>后台进程>空进程(Empty Process)

前台进程

杀死前台进程需要用户交互

  1. 进程持有一个正与用户交互的Activity
  2. 进程持有一个Service,这个Service处于这几种状态:
    1. Service与用户正在交互的Activity绑定
    2. Service调用了startForeground(),在前台运行
    3. Service执行在用户正在交互的Activity的生命周期回调函数中(onCreate()、onStart()、onDestory()
  3. 进程持有一个BroadcastReceiver,这个BroadcastReceiver正在执行它的onReceive()方法

可见进程

进程不含任何前台的组件,仍可被用户在屏幕上所见。

  1. 进程持有一个Activity,此Activity不在前台,但仍可被用户可见(处于onPause()但没调用onStop()状态,如:前台Activity打开了一个对话框)
  2. 进程持有一个Service,此Service和一个可见(或者前台的)Activity绑定

服务进程

一个进程中运行着一个Service,此Service是通过startService()开启的,如后台播放音乐、后台下载等。

后台进程

通常有很后台进程存在,它们被保存在LRU(least recently used)列表中,即保证了最近使用得activity最后被销毁。

  1. 进程持有一个用户不可见的Activity(activity的onStop()被调用),即可认为此进程是后台进程。后台进程不直接影响用户体验,系统会为了优先级高的进程而任意杀死后台进程。

空进程Empty process

一个进程中没有数据在运行了,但内存为此应用驻留了一个进程空间。保存这种进程的唯一理由是为了缓存的需要,为了加快下次启动这个进程中组件的启动时间。系统为了平衡进程缓存和底层内核缓存的资源,经常会杀死空进程。

  1. 一个进程不包含任何活跃的应用组件,则认为是空进程(android设计的,为了下次启动更快而采取的一个权衡)

进程生命周期

1、前台进程

  • 托管用户正在交互的 Activity(已调用 Activity 的 onResume() 方法)
  • 托管某个 Service,后者绑定到用户正在交互的 Activity
  • 托管正在“前台”运行的 Service(服务已调用 startForeground()
  • 托管正执行一个生命周期回调的 Service(onCreate()onStart()onDestroy()
  • 托管正执行其 onReceive() 方法的 BroadcastReceiver

2、可见进程

  • 托管不在前台、但仍对用户可见的 Activity(已调用其 onPause() 方法)。例如,如果 re前台 Activity 启动了一个对话框,允许在其后显示上一 Activity,则有可能会发生这种情况。
  • 托管绑定到可见(或前台)Activity 的 Service

3、服务进程

  • 正在运行已使用 startService() 方法启动的服务且不属于上述两个更高类别进程的进程。

4、后台进程

  • 包含目前对用户不可见的 Activity 的进程(已调用 Activity 的 onStop() 方法)。通常会有很多后台进程在运行,因此它们会保存在 LRU (最近最少使用)列表中,以确保包含用户最近查看的 Activity 的进程最后一个被终止。

5、空进程

  • 不含任何活动应用组件的进程。保留这种进程的的唯一目的是用作缓存,以缩短下次在其中运行组件所需的启动时间。 为使总体系统资源在进程缓存和底层内核缓存之间保持平衡,系统往往会终止这些进程。

在Service中新开线程比在Activity中新开线程的区别

在Service中新开线程至少有服务进程的优先级;

在Activity中新开线程,当Activity退出到桌面或其他情况时,会成为后台进程;

综上:若要处理长时间操作的话,新建Service再开启线程比在Activity中直接开线程好,可以保证这个长时间操作的稳定。

多进程

如果注册的四大组件中的任意一个组件时用到了多进程,运行该组件时,都会创建一个新的 Application 对象。对于多进程重复创建 Application 这种情况,只需要在该类中对当前进程加以判断即可。

单应用多进程:给四大组件(Activity、Service、Receiver、ContentProvider)在AndroidManifest中指定android:process属性。MainActivity运行在默认进程(默认进程名是包名)中。

andorid:process内容以“:”开头属于当前应用的私有进程,其他应用组件不可以和它跑在同一个进程中;内容是完整包名的是全局进程,其他应用可通过ShareUID(需要ShareUID、签名相同)和它跑在同一个进程中。每个应用都有个唯一的UID,有相同UID的应用才能共享数据。

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
public class MyApplication extends Application {

@Override
public void onCreate() {
Log.d("MyApplication", getProcessName(android.os.Process.myPid()));
super.onCreate();
}

/**
* 根据进程 ID 获取进程名
* @param pid 进程id
* @return 进程名
*/
public String getProcessName(int pid){
ActivityManager am = (ActivityManager)getSystemService(Context.ACTIVITY_SERVICE);
List<ActivityManager.RunningAppProcessInfo> processInfoList = am.getRunningAppProcesses();
if (processInfoList == null) {
return null;
}
for (ActivityManager.RunningAppProcessInfo processInfo : processInfoList) {
if (processInfo.pid == pid) {
return processInfo.processName;
}
}
return null;
}
}

多进程导致的问题

  1. 静态成员和单例模式完全失效
  2. 线程同步机制完全失效
  3. SharedPreferences的可靠性下降
  4. Application会多次创建

上面问题1、2:每个进程都会分配一个独立的虚拟机,在内存分配上有不同的地址空间。导致不同虚拟机访问同一个类的对象会产生多份副本。((对对象、全局类)加锁是没有用的(不同进程锁的不是同一个对象了))

上面问题3:因为SharedPreferences(底层通过读/写XML文件来实现的)不支持两个进程同时去执行写操作,否则会导致一定几率的数据丢失。甚至并发读/写都有可能出问题。

上面问题4:新进程分配新的虚拟机,此过程其实就是启动一个应用的过程。相当于系统又把这个应用重新启动了一遍,会创建新的Application。

进程存活

OOM_ADJ

ADJ级别 取值 解释
UNKNOWN_ADJ 16 一般指将要会缓存进程,无法获取确定值
CACHED_APP_MAX_ADJ 15 不可见进程的adj最大值
CACHED_APP_MIN_ADJ 9 不可见进程的adj最小值
SERVICE_B_AD 8 B List中的Service(较老的、使用可能性更小)
PREVIOUS_APP_ADJ 7 上一个App的进程(往往通过按返回键)
HOME_APP_ADJ 6 Home进程
SERVICE_ADJ 5 服务进程(Service process)
HEAVY_WEIGHT_APP_ADJ 4 后台的重量级进程,system/rootdir/init.rc文件中设置
BACKUP_APP_ADJ 3 备份进程
PERCEPTIBLE_APP_ADJ 2 可感知进程,比如后台音乐播放
VISIBLE_APP_ADJ 1 可见进程(Visible process)
FOREGROUND_APP_ADJ 0 前台进程(Foreground process)
PERSISTENT_SERVICE_ADJ -11 关联着系统或persistent进程
PERSISTENT_PROC_ADJ -12 系统persistent进程,比如telephony
SYSTEM_ADJ -16 系统进程
NATIVE_ADJ -17 native进程(不被系统管理)

进程被杀情况

进程杀死场景 调用接口 可能影响范围
触发系统进程管理机制 Lowmemorykiller 从进程importace值由大到小依次杀死,释放内存
被第三方应用杀死(无Root) killBackgroundProcess 只能杀死OOM_ADJ为4以上的进程
被第三方应用杀死(有Root) force-stop或者kill 理论上可以杀所有进程,一般只杀非系统关键进程和非前台和可见进程
厂商杀进程功能 force-stop或者kill 理论上可以杀所有进程,包括Native进程
用户主动“强行停止”进程 force-stop 只能停用第三方和非system/phone进程应用(停用system进程应用会造成Android重启)

进程保活方案

  • 开启一个像素的Activity
  • 使用前台服务
  • 多进程相互唤醒
  • JobSheduler唤醒
  • 粘性服务&与系统服务捆绑

保证Service不死的方案

  • 设置优先级

    AndroidManifest.xmlintent-filter标签可加android:priority="1000"属性,设置优先级,1000时最高值

  • startForeground()

    onStartCommand()里调用startForeground()将Service提升为前台进程。在onDestory里记得调用stopForeground()

  • onStartCommand(),返回START_STICKY

    在进程不被干掉的情况下:在onStartCommand()中手动返回START_STICKY,当Service因内存不足被kill,当内存又存在时,Service又被重新创建

    1
    2
    3
    4
    public int onStartCommand(Intent intent, int flags, int startId){
    flags = START_STICKY;
    return super.onStartCommand(intent, flags, startId);
    }

    onStartCommand返回int整型,有4个取值:

    1. START_STICKY:“粘性的”。若service进程被kill,保留service状态为开始状态,但不保留递送的intent对象。随后系统尝试重建service,因服务为开始状态,故创建服务后一定会调用onStartCommand(Intent, int, int),若在此期间没有任何启动命令传递到service,则参数Intent将为null
    2. START_NOT_STICKY:“非粘性的”。在执行玩onStartCommand(Intent, int, int)后,服务被异常kill掉,系统不会自动重启该服务。
    3. START_REDELIVER_INTENT:重传Intent。执行完onStartCommand后,服务被异常kill掉,系统不会自动重启该服务。
    4. START_STICKY_COMPATIBILITY:START_STICKY的兼容版本。但不保证服务被kill后一定能重启。
  • 在onDestroy中发广播重启Service

    service走onDestory时,发个自定义广播,当收到广播时重启service。(第三方应用或是在setting里–应用–强制停止时,APP进程直接被干掉了,onDestory方法进不来,故无法保证会执行)

  • 监听(系统)广播判断Service状态

    大厂的做法。监听系统、QQ、微信、系统应用、友盟推送等发送游戏额广播,来唤醒自己的App

  • Application加上Persistent属性

    某个进程不想被杀死(数据缓存进程、状态监控进程、远程服务进程)

    1
    2
    //加上以上属性相当于将该进程设置为常驻内存进程
    add android:persistent="true" into the section in your AndroidManifest.xml

    这个不可滥用,一般只适用于放在/system/app下的app。这里app一多容易造成系统崩溃。

  • 类似监听锁屏的方法

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    QQ采取在锁屏的时候启动一个1个像素的Activity,当用户解锁以后将这个Activity结束掉(顺便同时把自己的核心服务再开启一次)。
    故事:小米撕逼。
    背景:当手机锁屏的时候什么都干死了,为了省电。
    锁屏界面在上面盖住了。
    监听锁屏广播,锁了---启动这个Activity。
    监听锁屏的,开启---结束掉这个Activity。
    要监听锁屏的广播---动态注册。
    ScreenListener.begin(new xxxListener
    onScreenOff()
    );

    被系统无法杀死的进程。
  • 双进程守护+锁屏

  • 双进程守护+JobScheduler调度

  • 其他:App运营商与手机厂商合作列入白名单;利用账号同步机制唤醒我们的进程;NDK来解决,Native进程来实现双进程守护。

进程保活神器

进程间通信

  • bind机制(IPC –> AIDL)
  • Linux级共享内存
  • Broadcast
  • ContentProvider

IPC

IPC 即 Inter-Process Communication (进程间通信)。Android 基于 Linux,而 Linux 出于安全考虑,不同进程间不能之间操作对方的数据,这叫做“进程隔离”。

在 Linux 系统中,虚拟内存机制为每个进程分配了线性连续的内存空间,操作系统将这种虚拟内存空间映射到物理内存空间,每个进程有自己的虚拟内存空间,进而不能操作其他进程的内存空间,只有操作系统才有权限操作物理内存空间。 进程隔离保证了每个进程的内存安全。

基础概念介绍

Serializable

Java提供的一个序列化的空接口。实体类实现Serializable即可【再声明一个serialVersionUID】(需要大量I/O操作,开销大)

1
2
3
4
5
6
7
8
9
10
//序列化过程
User user = new User(0, "jake", true);
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("cache.txt"));
out.writeObject(user);
out.close();

//反序列化过程
ObjectInputStream in = new ObjectInputStream(new FileInputStream("cache.txt"));
User newUser = (User)in.readObject();
in.close();

应该手动指定serialVersionUID(指定为1L也可以,AS会根据当前类结构去生成hash值),反序列化时会检测是否与当前类的serialVersionUID一致,如果发生过某些变化(比如成员变量的数量、类型改变)就无法正常反序列化会报java.io.InvalidClassException:Main;local class incompatible:stream classdesc serialVersionUID=87113688010083044,local class serialVersionUID=8711368828010083043.

Parcelable

Android提供的新的序列化方式。一个类对象只要实现这个接口,就可以实现序列化并可以通过Intent和Binder传递。(效率更高,写法复杂)

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
public class User implements Parcelable{
public int userId;
public String userName;
public boolean isMale;
public Book book;

public User(int userId, String userName, boolean isMale){
this.userId = userId;
this.userName = userName;
this.isMale = isMale;
}

//内容描述功能。仅当当前对象中存在文件描述符时才返回1
public int describeContents(){
return 0;
}

//序列化过程
public void writeToParcel(Parcel out, int flags){
out.writeInt(userId);
out.writeString(userName);
out.writeInt(isMale ? 1 : 0);
out.writeParcelable(book, 0);
}

//反序列化过程
public static final Parcelable.Creator<User> CREATOR = new Parcelable.Creator<User>(){
public User createFromParcel(Parcel in){
return new User(in);
}

public User[] newArray(int size){
return new User(size);
}
};

private User(Parcel in){
userId = in.readInt();
userName = in.readString();
isMale = in.readInt() == 1;
//book是另一个可序列化对象,所以需传递当前线程的上下文类加载器,否则报无法找到类的错误
book = in.readParcelable(Thread.currentThread().getContextClassLoader());
}
}

Parcelable的方法说明

方法 功能 标记位
createFromParcel(Parcel in) 从序列化后的对象中创建原始对象
newArray(int size) 创建指定长度的原始对象数组
User(Parcel in) 从序列化后的对象中创建原始对象
writeToParcel(Parcel out, int flags) 将当前对象写入序列化结构中,其中flags标识有两种值:0或1(参见右侧标记位)。
为1时标识当前对象需要作为返回值返回,不能立即释放资源,几乎所有情况都为0
PARCEL_ABLE_WRITE_RETURN_VALUE
describeContents 返回当前对象地内容描述。如果含有文件描述符,返回1(参见右侧标记位),否则返回0,几乎所有情况都返回0 CONTENTS_FILE_DESCRIPTOR

系统已提供许多实现了Parcelable接口的类,Intent、Bundle、Bitmap等,同时List和Map也可以序列化,前提是其内每个元素都是可序列化的。

Binder

Binder是Android中的一个类,实现了IBinder接口。是Android中的一种跨进程通信方式。可理解为一种虚拟的物理设备,其设备驱动是/dev/binder,该通信方式在Linux中没有;从Android Framework角度来说,Binder是ServiceManager连接各种Manager(ActivityManager、WindowManager等等)和相应ManagerService的桥梁;从Android应用层来说,Binder是客户端和服务端进行通信的媒介,当bindService的时候,服务端会返回一个包含了服务端业务调用的Binder对象,通过这个Binder对象,客户端就可以获取服务端提供的服务或者数据,这里的服务包括普通服务和基于AIDL的服务。

Binder主要用于Service中,包括AIDL和Messenger,其中普通Service中的Binder不涉及进程间通信,较为简单。Messenger底层是AIDL。

IPC方式

名称 优点 缺点 适用场景
Bundle 简单易用 只能传输Bundle支持的数据类型 四大组件间的进程间通信
文件共享 简单易用 不适合高并发场景,并且无法做到进程间即时通信 无并发访问情形,交换简单的数据实时性不高的场景
AIDL 功能强大,支持一对多并发通信,支持实时通信 使用稍复杂,需要处理好线程同步 一对多通信且有RPC需求
Messenger 功能一般,支持一对多串行通信,支持实时通信 不能处理高并发线程,不支持RPC,数据通过Message进行传输,因此只能传输Bundle支持的数据类型 低并发的一对多即时通信,无RPC需求,或者无需返回结果的RPC需求
ContentProvider 在数据源访问方面功能强大,支持一对多并发数据共享,可通过Call方法扩展其他操作 可以理解为受约束的AIDL,主要提供数据源的CRUD(Create、Retrieve、Update、Delete)操作 一对多的进程间数据共享
Socket 功能强大,可以通过网络传输字节流,支持一对多并发实时通信 实现细节稍微有点烦琐,不支持直接的RPC 网络数据交换

AIDL

Android Interface Definition Language

  • 新建AIDL接口文件
    1
    2
    3
    4
    5
    6
    7
    8
    // RemoteService.aidl
    package com.example.mystudyapplication3;

    interface IRemoteService {

    int getUserId();

    }
  • 创建远程服务
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    public class RemoteService extends Service {

    private int mId = -1;

    private Binder binder = new IRemoteService.Stub() {

    @Override
    public int getUserId() throws RemoteException {
    return mId;
    }
    };

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
    mId = 1256;
    return binder;
    }
    }
  • 声明远程服务
    1
    2
    3
    <service
    android:name=".RemoteService"
    android:process=":aidl" />
  • 绑定远程服务
    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
    public class MainActivity extends AppCompatActivity {

    public static final String TAG = "wzq";

    IRemoteService iRemoteService;
    private ServiceConnection mConnection = new ServiceConnection() {
    @Override
    public void onServiceConnected(ComponentName name, IBinder service) {
    iRemoteService = IRemoteService.Stub.asInterface(service);
    try {
    Log.d(TAG, String.valueOf(iRemoteService.getUserId()));
    } catch (RemoteException e) {
    e.printStackTrace();
    }
    }

    @Override
    public void onServiceDisconnected(ComponentName name) {
    iRemoteService = null;
    }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    bindService(new Intent(MainActivity.this, RemoteService.class), mConnection, Context.BIND_AUTO_CREATE);
    }
    }

Messenger

Messenger可以在不同进程中传递Message对象,在Message中放入我们需要传递的数据,就可以轻松地实现数据的进程间传递了。Messenger是一种轻量级的IPC方案,底层实现是AIDL。

后台运行白名单-保活方式

原文

后台运行白名单设置步骤

  1. 获取权限
  2. 判断应用是否在白名单中
  3. 未在白名单中,申请加入白名单

具体步骤详情

  1. 获取权限

AndroidManifest.xml

1
<uses-permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS" />
  1. 判断应用是否在白名单中
1
2
3
4
5
6
7
8
9
@RequiresApi(api = Build.VERSION_CODES.M)
private boolean isIgnoringBatteryOptimizations() {
boolean isIgnoring = false;
PowerManager powerManager = (PowerManager) getSystemService(Context.POWER_SERVICE);
if (powerManager != null) {
isIgnoring = powerManager.isIgnoringBatteryOptimizations(getPackageName());
}
return isIgnoring;
}
  1. 未在白名单中,申请加入白名单
1
2
3
4
5
6
7
8
9
10
@RequiresApi(api = Build.VERSION_CODES.M)
public void requestIgnoreBatteryOptimizations() {
try {
Intent intent = new Intent(Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS);
intent.setData(Uri.parse("package:" + getPackageName()));
startActivity(intent);
} catch (Exception e) {
e.printStackTrace();
}
}

注意:

  • 申请加入白名单会弹出申请的弹窗,弹窗中有必要加用户会影响电池续航的提醒。

  • 如果点击了允许,可以在申请的时候调用startActivityForResult,在onActivityResult里再判断一次是否在白名单中。


厂商后台管理

上面的添加到到后台运行白名单,但也会被厂商的后台管理干掉。所以要把应用也加入到厂商系统的后台管理白名单。

整理了部分主流Android厂商,先给出两个方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/**
* 跳转到指定应用的首页
*/
private void showActivity(@NonNull String packageName) {
Intent intent = getPackageManager().getLaunchIntentForPackage(packageName);
startActivity(intent);
}

/**
* 跳转到指定应用的指定页面
*/
private void showActivity(@NonNull String packageName, @NonNull String activityDir) {
Intent intent = new Intent();
intent.setComponent(new ComponentName(packageName, activityDir));
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent);
}

以下是厂商判断,跳转方法及对应设置步骤。另外,跳转方法不保证在所有版本上都能成功所以都要加try catch

华为

1
2
3
4
5
6
7
8
9
10
/**
* 判断是否是华为厂商
*/
public boolean isHuawei(){
if (Build.BRAND == null) {
return false;
} else {
return Build.BRAND.toLowerCase().equals("huawei") || Build.BRAND.toLowerCase().equals("honor");
}
}
1
2
3
4
5
6
7
8
9
10
/**
* 跳转到华为手机管理的启动管理页
*/
private void goHuaweiSetting(){
try {
showActivity("com.huawei.systemmanager", "com.huawei.systemmanager.startupmgr.ui.StartupNormalAppListActivity");
} catch (Exception e) {
showActivity("com.huawei.systemmanager", "com.huawei.systemmanager.optimize.bootstart.BootStartActivity");
}
}

操作步骤:应用启动管理–》关闭应用开关–》打开允许自启动

小米

1
2
3
4
5
6
/**
* 是否是小米厂商的判断
*/
public static boolean isXiaomi() {
return Build.BRAND != null && Build.BRAND.toLowerCase().equals("xiaomi");
}
1
2
3
4
5
6
/**
* 跳转小米安全中心的自启动管理页面
*/
private void goXiaomiSetting() {
showActivity("com.miui.securitycenter", "com.miui.permcenter.autostart.AutoStartManagementActivity");
}

操作步骤:授权管理–》自启动管理–》允许应用自启动

OPPO

1
2
3
4
5
6
/**
* 是否是OPPO厂商的判断
*/
public static boolean isOPPO() {
return Build.BRAND != null && Build.BRAND.toLowerCase().equals("oppo");
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/**
* 跳转手机管理
*/
private void goOPPOSetting() {
try {
showActivity("com.coloros.phonemanager");
} catch (Exception e1) {
try {
showActivity("com.oppo.safe");
} catch (Exception e2) {
try {
showActivity("com.coloros.oppoguardelf");
} catch (Exception e3) {
showActivity("com.coloros.safecenter");
}
}
}
}

操作步骤:权限隐私 -> 自启动管理 -> 允许应用自启动

VIVO

1
2
3
4
5
6
/**
* 是否是VIVO厂商的判断
*/
public static boolean isVIVO() {
return Build.BRAND != null && Build.BRAND.toLowerCase().equals("vivo");
}
1
2
3
4
5
6
/**
* 跳转手机管理
*/
private void goVIVOSetting() {
showActivity("com.iqoo.secure");
}

操作步骤:权限管理 -> 自启动 -> 允许应用自启动

魅族

1
2
3
4
5
6
/**
* 是否是魅族厂商的判断
*/
public static boolean isMeizu() {
return Build.BRAND != null && Build.BRAND.toLowerCase().equals("meizu");
}
1
2
3
4
5
6
/**
* 跳转手机管理
*/
private void goMeizuSetting() {
showActivity("com.meizu.safe");
}

操作步骤:权限管理 -> 后台管理 -> 点击应用 -> 允许后台运行

三星

1
2
3
4
5
6
/**
* 是否是三星厂商的判断
*/
public static boolean isSamsung() {
return Build.BRAND != null && Build.BRAND.toLowerCase().equals("samsung");
}
1
2
3
4
5
6
7
8
9
10
/**
* 跳转手机管理
*/
private void goSamsungSetting() {
try {
showActivity("com.samsung.android.sm_cn");
} catch (Exception e) {
showActivity("com.samsung.android.sm");
}
}

操作步骤:自动运行应用程序 -> 打开应用开关 -> 电池管理 -> 未监视的应用程序 -> 添加应用

乐视

1
2
3
4
5
6
/**
* 是否是乐视厂商的判断
*/
public static boolean isLeTV() {
return Build.BRAND != null && Build.BRAND.toLowerCase().equals("letv");
}
1
2
3
4
5
6
7
/**
* 跳转手机管理
*/
private void goLetvSetting() {
showActivity("com.letv.android.letvsafe",
"com.letv.android.letvsafe.AutobootManageActivity");
}

操作步骤:自启动管理 -> 允许应用自启动

锤子

1
2
3
4
5
6
/**
* 是否是锤子厂商的判断
*/
public static boolean isSmartisan() {
return Build.BRAND != null && Build.BRAND.toLowerCase().equals("smartisan");
}
1
2
3
4
5
6
/**
* 跳转手机管理
*/
private void goSmartisanSetting() {
showActivity("com.smartisanos.security");
}

操作步骤:权限管理 -> 自启动权限管理 -> 点击应用 -> 允许被系统启动

研究TIM保活方式

原文:史上最强Android保活思路:深入剖析腾讯TIM的进程永生技术

总结本文思路

  1. 先有了初步分析过程中对一些常规套路的可能性的排除,并嗅到callingPid=0的异常举动;
  2. 沿着蛛丝马迹,不断反复杀进程,从中寻找更多的规律,不断地向自己提出疑问;
  3. 结合signal、strace、traces、ps、binder、linux、kill等技能不断地给解答自己的疑惑。

主要提出过这些疑惑:

问题1:安全中心已配置了禁止TIM的自启动,并且安全中心和Whetstone都有对进程自启动以及级联启动的严格限制,为何会有漏网之鱼?

问题2:startService()的callingPid怎么可能等于0?

问题3:进程内的名叫“Thread-89”的线程具有什么特点,如何做到把进程杀掉?

问题4:为何需要4个indicator文件?

问题5:这4个进程到底是什么如何相互监听的呢?

问题6:app_d到底是如何创建出来?又是如何成为init进程的子进程的?

问题7:为何单杀daemon,会牵连app_d进程被杀,这是什么原理?

问题8:app_d是由daemon进程间接fork出来的,会共享binder fd,所以即便daemon进程被杀,死亡回调也不会触发,这又是如何触发的呢?

问题9:TIM到底对Binder框架做了什么级别的修改?这4个互保进程,既然callingPid=0,有没有办法知道到底是谁拉起谁的?

本文总结(TIM保活技术要点)

  1. 通过flock的文件排他锁方式来监听进程存活状态
    1. 先采用一对普通的进程Daemoon和MSF相互监听文件的方式获得对方进程是否存活的状态;
    2. 同时再采用一对退孤给init进程的app_d进程相互监听文件的方式来获得对方进程是否存活的状态; 而这两个进程都有间接由Daemon和MSF进程所fork而来;双重保险。
  2. 不采用系统框架中startService的Binder框架代码,而是自身在Native层通过自己去查询获取BpActivityManager代理对象,然后自己实现startService接口,并修改为ONEWAY的binder调用,既增加分析问题的难度,也进一步隐藏自身策略;
  3. 当监听进程死亡,则通过自身实现的StartService的Binder call去拉起对方进程,系统对于这种方式启动进程并没有拦截机制。

这种flock方式至少比网上常说的通过循环监听的方式,要强很多。

比往常的互保更厉害的是TIM共有6个进程(说明:使用过程也还会创建一些进程),其中4个进程,形成两组互动进程,其中一组利用Linux基础托孤原理,可谓是隐藏得很深来互保,进一步确保进程永生。

当然,进程收到signal信号后,如果恰巧这四个进程在同一个时刻点退出,那么还是有概率会被杀。

不走系统框架代码,自己去实现启动服务的binder call也是一大亮点,不过还有更高级的玩法,直接封装ioctl跟驱动交互。之前针对这个问题,做过反保活方案,后来为了某些功能缘故又放开对这个的限制,这里就不再继续展开了。

TIM保活还有改进空间

TIM分别在Java层和Native层,主动向ServiceManager进程查询AMS后,获取BpActivityManager代理对象,然后继续使用框架中的IPCThreadState跟Binder驱动交互,并没有替换掉libbinder.so

其实,还可以更高级的玩法,连IPCThreadState这些框架通信代码也不使用,彻底地去自定义Binder交互代码,类似于servicemanager的方式。可以自己封装ioctl(),直接talkWithDriver。TIM保活还有改进空间, 提供保活变种方案,这样的话,上面的调试代码也拦截不了其对flags修改为ONEWAY的过程。 即使如此,一切都在Control之中, 完全可以在Binder Driver中拦截再定位其策略, 玩得再高级也主要活动在用户态, 内核态的策略还是相对安全的, 此所谓“魔高一座,道高一尺”。