一、基础知识

1.Android 进程优先级

1.1 进程优先级等级一般分法:
- Activte process
- Visible Process
- Service process
- Background process
- Empty process

1.2 进程优先级号

ProcessList.java

// 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.)
static final int UNKNOWN_ADJ = 16; // This is a process only hosting activities that are not visible,
// so it can be killed without any disruption.
static final int CACHED_APP_MAX_ADJ = 15;
static final int CACHED_APP_MIN_ADJ = 9; // 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.
static final int SERVICE_B_ADJ = 8; // 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.
static final int PREVIOUS_APP_ADJ = 7; // 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.
static final int HOME_APP_ADJ = 6; // This is a process holding an application service -- killing it will not
// have much of an impact as far as the user is concerned.
static final int SERVICE_ADJ = 5; // 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.
static final int HEAVY_WEIGHT_APP_ADJ = 4; // This is a process currently hosting a backup operation. Killing it
// is not entirely fatal but is generally a bad idea.
static final int BACKUP_APP_ADJ = 3; // 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.
static final int PERCEPTIBLE_APP_ADJ = 2; // This is a process only hosting activities that are visible to the
// user, so we'd prefer they don't disappear.
static final int VISIBLE_APP_ADJ = 1; // This is the process running the current foreground app. We'd really
// rather not kill it!
static final int FOREGROUND_APP_ADJ = 0; // This is a process that the system or a persistent process has bound to,
// and indicated it is important.
static final int PERSISTENT_SERVICE_ADJ = -11; // This is a system persistent process, such as telephony. Definitely
// don't want to kill it, but doing so is not completely fatal.
static final int PERSISTENT_PROC_ADJ = -12; // The system process runs at the default adjustment.
static final int SYSTEM_ADJ = -16; // Special code for native processes that are not being managed by the system (so
// don't have an oom adj assigned by the system).
static final int NATIVE_ADJ = -17;

2. Android Low Memory Killer

  Android系统内存不足时,系统会杀掉一部分进程以释放空间,谁生谁死的这个生死大权就是由LMK所决定的,这就是Android系统中的Low Memory Killer,其基于Linux的OOM机制,其阈值定义如下面所示的lowmemorykiller文件中,当然也可以通过系统的init.rc实现自定义。

lowmemorykiller.c

static uint32_t lowmem_debug_level = 1;
static int lowmem_adj[6] = {
0,
1,
6,
12,
};
static int lowmem_adj_size = 4;
static int lowmem_minfree[6] = {
3 * 512, /* 6MB */
2 * 1024, /* 8MB */
4 * 1024, /* 16MB */
16 * 1024, /* 64MB */
};
static int lowmem_minfree_size = 4;

 在Low Memory Killer中通过进程的oom_adj与占用内存的大小决定要杀死的进程,oom_adj值越小越不容易被杀死。其中,lowmem_minfree是杀进程的时机,谁被杀,则取决于lowmem_adj,具体值得含义参考上面 Android进程优先级 所述.

 在init.rc中定义了init进程(系统进程)的oom_adj为-16,其不可能会被杀死(init的PID是1),而前台进程是0(这里的前台进程是指用户正在使用的Activity所在的进程),用户按Home键回到桌面时的优先级是6,普通的Service的进程是8.

init.rc

1 # Set init and its forked children's oom_adj.
2 write /proc/1/oom_adj -16

关于Low Memory Killer的具体实现原理可参考Ref-2.

3. 查看某个App的进程

步骤(手机与PC连接)
1. adb shell
2. ps | grep 进程名
3. cat /proc/pid/oom_adj //其中pid是上述grep得到的进程号

4. Android账号和同步机制

属于Android中较偏冷的知识,具体参考 Ref 3/4/5

二、现有方法

1. 网络连接保活方法

a. GCM
b. 公共的第三方push通道(信鸽等)
c. 自身跟服务器通过轮询,或者长连接
具体实现请参考 微信架构师杨干荣的"微信Android客户端后台保活经验分享" (Ref-1).

2. 双service 提高进程优先级

思路:(API level > 18 )
① 应用启动时启动一个假的Service(FakeService), startForeground(),传一个空的Notification
② 启动真正的Service(AlwaysLiveService),startForeground(),注意必须相同Notification ID
③ FakeService stopForeground()

效果:通过adb查看,运行在后台的服务其进程号变成了1(优先级仅次于前台进程)

风险:Android系统前台service的一个漏洞,可能在6.0以上系统中修复

实现:核心代码如下

AlwaysLiveService 常驻内存服务

 @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);
}

FakeService 临时服务

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();
}
}

3. 守护进程及时拉起

AlarmReceiver, ConnectReceiver,BootReceiver等

三、新方法(AccountSyncAdapter)

1. 思路:

利用Android系统提供的账号和同步机制实现

2. 效果:

① 通过adb查看,运行在后台的服务其进程号变成了1(优先级仅次于前台进程),能提高进程优先级,对比如下图

正常情况

采用AccountSyncAdapter方法后

② 进程被系统kill后,可以由syn拉起

3. 风险:

① SyncAdapter时间进度不高,往往会因为手机处于休眠状态,而时间往后调整,同步间隔最低为1分钟
 用户可以单独停止或者删除,有些手机账号默认是不同步的,需要手动开启

4. 实现:核心代码如下

① 建立数据同步系统(ContentProvider)

通过一个ContentProvider用来作数据同步,由于并没有实际数据同步,所以此处就直接建立一个空的ContentProvider即可

public class XXAccountProvider extends ContentProvider {
public static final String AUTHORITY = "包名.provider";
public static final String CONTENT_URI_BASE = "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;
}
}

然后再Manifest中声明

<provider
android:name="**.XXAccountProvider"
android:authorities="@string/account_auth_provider"
android:exported="false"
android:syncable="true"/>

② 建立Sync系统 (SyncAdapter) 
通过实现SyncAdapter这个系统服务后, 利用系统的定时器对程序数据ContentProvider进行更新,具体步骤为:
- 创建Sync服务

public class XXSyncService extends Service {
private static final Object sSyncAdapterLock = new Object();
private static XXSyncAdapter sSyncAdapter = null;
@Override
public void onCreate() {
synchronized (sSyncAdapterLock) {
if (sSyncAdapter == null) {
sSyncAdapter = new XXSyncAdapter(getApplicationContext(), true);
}
}
} @Override
public IBinder onBind(Intent intent) {
return sSyncAdapter.getSyncAdapterBinder();
} static class XXSyncAdapter extends AbstractThreadedSyncAdapter {
public XXSyncAdapter(Context context, boolean autoInitialize) {
super(context, autoInitialize);
}
@Override
public void onPerformSync(Account account, Bundle extras, String authority, ContentProviderClient provider, SyncResult syncResult) {
getContext().getContentResolver().notifyChange(XXAccountProvider.CONTENT_URI, null, false);
}
}
}

- 声明Sync服务

<service
android:name="**.XXSyncService"
android:exported="true"
android:process=":core">
<intent-filter>
<action
android:name="android.content.SyncAdapter"/>
</intent-filter>
<meta-data
android:name="android.content.SyncAdapter"
android:resource="@xml/sync_adapter"/>
</service>

其中sync_adapter为:

<sync-adapter xmlns:android="http://schemas.android.com/apk/res/android"
android:accountType="@string/account_auth_type"
android:allowParallelSyncs="false"
android:contentAuthority="@string/account_auth_provide"
android:isAlwaysSyncable="true"
android:supportsUploading="false"
android:userVisible="true"/>
参数说明:
android:contentAuthority 指定要同步的ContentProvider在其AndroidManifest.xml文件中有个android:authorities属性。
android:accountType 表示进行同步的账号的类型。
android:userVisible 设置是否在“设置”中显示
android:supportsUploading 设置是否必须notifyChange通知才能同步
android:allowParallelSyncs 是否支持多账号同时同步
android:isAlwaysSyncable 设置所有账号的isSyncable为1
android:syncAdapterSettingsAction 指定一个可以设置同步的activity的Action。 - 账户调用Sync服务
首先配置好Account(第三步),然后再通过ContentProvider实现
手动更新
public void triggerRefresh() {
Bundle b = new Bundle();
b.putBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, true);
b.putBoolean(ContentResolver.SYNC_EXTRAS_EXPEDITED, true);
ContentResolver.requestSync(
account,
CONTENT_AUTHORITY,
b);
}

添加账号

 Account account = AccountService.GetAccount();
AccountManager accountManager = (AccountManager) context.getSystemService(Context.ACCOUNT_SERVICE);
accountManager.addAccountExplicitly(...)

同步周期设置

 ContentResolver.setIsSyncable(account, CONTENT_AUTHORITY, 1);
ContentResolver.setSyncAutomatically(account, CONTENT_AUTHORITY, true);
ContentResolver.addPeriodicSync(account, CONTENT_AUTHORITY, new Bundle(), SYNC_FREQUENCY);

③ 建立账号系统 (Account Authenticator)
通过建立Account账号,并关联SyncAdapter服务实现同步
- 创建Account服务

public class XXAuthService extends Service {
private XXAuthenticator mAuthenticator; @Override
public void onCreate() {
mAuthenticator = new XXAuthenticator(this);
} private XXAuthenticator getAuthenticator() {
if (mAuthenticator == null)
mAuthenticator = new XXAuthenticator(this);
return mAuthenticator;
} @Override
public IBinder onBind(Intent intent) {
return getAuthenticator().getIBinder();
} class XXAuthenticator extends AbstractAccountAuthenticator {
private final Context context;
private AccountManager accountManager;
public XXAuthenticator(Context context) {
super(context);
this.context = context;
accountManager = AccountManager.get(context);
} @Override
public Bundle addAccount(AccountAuthenticatorResponse response, String accountType, String authTokenType, String[] requiredFeatures, Bundle options)
throws NetworkErrorException {
// 添加账号 示例代码
final Bundle bundle = new Bundle();
final Intent intent = new Intent(context, AuthActivity.class);
intent.putExtra(AccountManager.KEY_ACCOUNT_AUTHENTICATOR_RESPONSE, response);
bundle.putParcelable(AccountManager.KEY_INTENT, intent);
return bundle;
} @Override
public Bundle getAuthToken(AccountAuthenticatorResponse response, Account account, String authTokenType, Bundle options)
throws NetworkErrorException {
// 认证 示例代码
String authToken = accountManager.peekAuthToken(account, getString(R.string.account_token_type));
//if not, might be expired, register again
if (TextUtils.isEmpty(authToken)) {
final String password = accountManager.getPassword(account);
if (password != null) {
//get new token
authToken = account.name + password;
}
}
//without password, need to sign again
final Bundle bundle = new Bundle();
if (!TextUtils.isEmpty(authToken)) {
bundle.putString(AccountManager.KEY_ACCOUNT_NAME, account.name);
bundle.putString(AccountManager.KEY_ACCOUNT_TYPE, account.type);
bundle.putString(AccountManager.KEY_AUTHTOKEN, authToken);
return bundle;
} //no account data at all, need to do a sign
final Intent intent = new Intent(context, AuthActivity.class);
intent.putExtra(AccountManager.KEY_ACCOUNT_AUTHENTICATOR_RESPONSE, response);
intent.putExtra(AuthActivity.ARG_ACCOUNT_NAME, account.name);
bundle.putParcelable(AccountManager.KEY_INTENT, intent);
return bundle;
} @Override
public String getAuthTokenLabel(String authTokenType) {
// throw new UnsupportedOperationException();
return null;
} @Override
public Bundle editProperties(AccountAuthenticatorResponse response, String accountType) {
return null;
} @Override
public Bundle confirmCredentials(AccountAuthenticatorResponse response, Account account, Bundle options)
throws NetworkErrorException {
return null;
} @Override
public Bundle updateCredentials(AccountAuthenticatorResponse response, Account account, String authTokenType, Bundle options)
throws NetworkErrorException {
return null;
} @Override
public Bundle hasFeatures(AccountAuthenticatorResponse response, Account account, String[] features)
throws NetworkErrorException {
return null;
}
}
}

- 声明Account服务

<service
android:name="**.XXAuthService"
android:exported="true"
android:process=":core">
<intent-filter>
<action
android:name="android.accounts.AccountAuthenticator"/>
</intent-filter>
<meta-data
android:name="android.accounts.AccountAuthenticator"
android:resource="@xml/authenticator"/>
</service>

其中authenticator为:

<?xml version="1.0" encoding="utf-8"?>
<account-authenticator xmlns:android="http://schemas.android.com/apk/res/android"
android:accountType="@string/account_auth_type"
android:icon="@drawable/icon"
android:smallIcon="@drawable/icon"
android:label="@string/app_name"
/>

- 使用Account服务 
同SyncAdapter,通过AccountManager使用

  - 申请Token主要是通过 [AccountManager.getAuthToken]系列方法

  - 添加账号则通过 [AccountManager.addAccount]

  - 查看是否存在账号通过 [AccountManager.getAccountsByType]

Refs

1. [微信Android客户端后台保活经验分享]

2. [Android Low Memory Killer原理]

3. [stackOverflow 上介绍的双Service方法]

4. [Write your own Android Sync Adapter]

5. [Write your own Android Authenticator]

6. Android developer
- [android.accounts]
- [AccountManager]
- [AbstractAccountAuthenticator]
- [AccountAuthenticatorActivity]
- [Creating a Sync Adapter]

一种提高Android应用进程存活率新方法的更多相关文章

  1. 【Android】一种提高Android应用进程存活率新方法

    [Android]一种提高Android应用进程存活率新方法 SkySeraph Jun. 19st 2016 Email:skyseraph00@163.com 更多精彩请直接访问SkySeraph ...

  2. 关于如何提高SRAM存储器的新方法

    SRAM是当今处理器上最普遍的内存.当芯片制造商宣布他们已经成功地将更多的电路封装到芯片上时,通常是较小的晶体管引起了人们的注意.但是连接晶体管形成电路的互连也必须收缩.IMEC的研究人员提出了一个方 ...

  3. Android 普通通知栏新方法,现在需要创建通知渠道才可以

    先看看效果看看是不是你想要的 点击后 话不多所,贴代码 xml文件: <?xml version="1.0" encoding="utf-8"?>& ...

  4. 一种部署 Python 代码的新方法

    在Nylas,我们喜欢使用Python进行开发.它的语法简单并富有表现力,拥有大量可用的开源模块和框架,而且这个社区既受欢迎又有多样性.我们的后台是纯用 Python 写的,团队也经常在 PyCon ...

  5. Eclipse中提高Android SDK Manager下载速度方法

    在Windows-System32-drivers-ect目录下找到hosts文件 打开hosts文件(用记事本打开就可以),在文件以下填上一下内容: 203.208.46.146 www.googl ...

  6. mac os下提高android studio运行速度终极方法

    /Users/hangliao/ 删除(.android  .gradle)两个文件夹 android studio恢复所有设置到初始化状态,这样会删除已创建的模拟器,所以需从创建一下模拟器 mac ...

  7. NFV一种提高进程消息高可用性的方法

    1.背景及概述 1.1 背景 在做NFV的过程中,由于控制面进程被放置到不同虚拟机中,中间可能跨越路由器,因此期间网络有可能震荡,这种情况下保证高可用性就必须有保护机制,本文正是在这种背景下的考虑. ...

  8. 重点:怎样正确的使用QThread类(注:包括推荐使用QThread线程的新方法QObject::moveToThread)

    背景描述: 以前,继承 QThread 重新实现 run() 函数是使用 QThread唯一推荐的使用方法.这是相当直观和易于使用的.但是在工作线程中使用槽机制和Qt事件循环时,一些用户使用错了.Qt ...

  9. 《Android群英传》读书笔记 (5) 第十一章 搭建云端服务器 + 第十二章 Android 5.X新特性详解 + 第十三章 Android实例提高

    第十一章 搭建云端服务器 该章主要介绍了移动后端服务的概念以及Bmob的使用,比较简单,所以略过不总结. 第十三章 Android实例提高 该章主要介绍了拼图游戏和2048的小项目实例,主要是代码,所 ...

随机推荐

  1. 多线程之ReadWriteLock模拟缓存(九)

    错误案例1: package com.net.thread.lock; import java.util.HashMap; import java.util.Map; import java.util ...

  2. 005--Django2.0的路由层

    URL配置就像Django所支撑的网站目录,它的本质是每条URL调用的视图函数的映射表,每一个请求执行对应的视图函数. 1.简单的路由配置  from django.contrib import ad ...

  3. java线程安全(单例模式)(转载)

    原文链接:http://www.jameswxx.com/java/%E8%AF%B4%E8%AF%B4%E5%8D%95%E4%BE%8B%E6%A8%A1%E5%BC%8F/ 单例模式?多么简单! ...

  4. 15.6,redis主从同步

    redis主从同步 原理:1. 从服务器向主服务器发送 SYNC 命令.2. 接到 SYNC 命令的主服务器会调用BGSAVE 命令,创建一个 RDB 文件,并使用缓冲区记录接下来执行的所有写命令.3 ...

  5. 快速登录机器&数据库

    本文来自网易云社区. 作者:盛国存 背景 我们日常在使用ApiDoc维护管理api文档,提高了api文档的整体维护性.但在老旧接口中,补充接口注解无疑是一次繁重的体力劳动.仔细查看,大多数接口的格式 ...

  6. 如何写一套下拉刷新的控件?《MJRefresh原理浅析》(附Demo下载地址)

    相信大家有很多人在做项目的时候都在使用MJRefresh 控件来实现下拉刷新的功能: MJRefresh经过不断的重构与更新迭代,现在不管是功能上还是代码结构上都是相当不错的,都是很值我们去学习的. ...

  7. 这是我见过最厉害的--智能代码生成器、html+js+底层+sql全都有、瓦特平台

    1:直接上图.图片有点多.我就没全部上传了. (demo.使用方法.数据库bak)下载:http://pan.baidu.com/s/1ntE5bDn 起源: 之前有好多人问我代码生成器的源码.我发了 ...

  8. 修改window 10 开始菜单问题

    cmd->powershell Get-AppxPackage | % { Add-AppxPackage -DisableDevelopmentMode -Register "$($ ...

  9. 剑指Offer - 九度1352 - 和为S的两个数字

    剑指Offer - 九度1352 - 和为S的两个数字2014-02-05 18:15 题目描述: 输入一个递增排序的数组和一个数字S,在数组中查找两个数,是的他们的和正好是S,如果有多对数字的和等于 ...

  10. 《Cracking the Coding Interview》——第4章:树和图——题目6

    2014-03-19 04:16 题目:找出一棵二叉搜索树中的中序遍历后继节点,每个节点都有指针指向其父节点. 解法1:分两种情况:向下走时,先右后左:向上走时,先左后右.如果目标节点有右子树,就向右 ...