基础知识Android 进程优先级1 进程优先级等级一般分法
- Activte process
- Visible Process
- Service process
- Background process
- Empty process
2 Service技巧
- onStartCommand返回START_STICKY
- onDestroy中startself
- Service后台变前置,setForground(true)
- android:persistent = “true”
3 进程优先级号
ProcessList.java
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 58 59 60 61 62 63 64 65 66 67
| // 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;
|
Android Low Memory KillerAndroid系统内存不足时,系统会杀掉一部分进程以释放空间,谁生谁死的这个生死大权就是由LMK所决定的,这就是Android系统中的Low Memory Killer,其基于Linux的OOM机制,其阈值定义如下面所示的lowmemorykiller文件中,当然也可以通过系统的init.rc实现自定义。 lowmemorykiller.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| 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 2
| # Set init and its forked children's oom_adj. write /proc/1/oom_adj -16
|
关于Low Memory Killer的具体实现原理可参考Ref-2.
查看某个App的进程步骤(手机与PC连接)
- adb shell
- ps | grep 进程名
- cat /proc/pid/oom_adj //其中pid是上述grep得到的进程号
Linux AM命令am命令:在Android系统中通过adb shell 启动某个Activity、Service、拨打电话、启动浏览器等操作Android的命令.其源码在Am.java中,在shell环境下执行am命令实际是启动一个线程执行Am.java中的主函数(main方法),am命令后跟的参数都会当做运行时参数传递到主函数中,主要实现在Am.java的run方法中。
拨打电话 命令:am start -a android.intent.action.CALL -d tel:电话号码 示例:am start -a android.intent.action.CALL -d tel:10086
打开一个网页 命令:am start -a android.intent.action.VIEW -d 网址 示例:am start -a android.intent.action.VIEW -d http://www.
启动一个服务 命令:am startservice <服务名称> 示例:am startservice -n com.android.music/ com.android.music.MediaPlaybackService
NotificationListenerService“A service that receives calls from the system when new notifications are posted or removed, or their ranking changed.” From Google
用来监听到通知的发送以及移除和排名位置变化,如果我们注册了这个服务,当系统任何一条通知到来或者被移除掉,我们都能通过这个service来监听到,甚至可以做一些管理工作。
Android账号和同步机制属于Android中较偏冷的知识,具体参考 Ref 3 /4 /5
Android多进程
- 实现:android:process
- 好处:一个独立的进程可以充分利用自己的RAM预算,使其主进程拥有更多的空间处理资源。此外,操作系统对待运行在不同组件中的进程是不一样的。这意味着,当系统运行在低可用内存的条件时,并不是所有的进程都会被杀死
- 大坑:每一个进程将有自己的Dalvik VM实例,意味着你不能通过这些实例共享数据,至少不是传统意义上的。例如,静态字段在每个进程都有自己的值,而不是你倾向于相信的只有一个值。
- 更多详细请参考Ref 9
现有方法网络连接保活方法A. GCM B. 公共的第三方push通道(信鸽等) C. 自身跟服务器通过轮询,或者长连接 具体实现请参考 微信架构师杨干荣的”微信Android客户端后台保活经验分享” (Ref-1).
双service(通知栏) 提高进程优先级思路:(API level > 18 )
- 应用启动时启动一个假的Service(FakeService), startForeground(),传一个空的Notification
- 启动真正的Service(AlwaysLiveService),startForeground(),注意必须相同Notification ID
- FakeService stopForeground()
效果:通过adb查看,运行在后台的服务其进程号变成了1(优先级仅次于前台进程)
风险:Android系统前台service的一个漏洞,可能在6.0以上系统中修复
实现:核心代码如下
1 2 3 4 5 6
| @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); }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| 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及时拉起AlarmReceiver, ConnectReceiver,BootReceiver等
- Service设置(见上面基础部分)
- 通过监听系统广播,如开机,锁屏,亮屏等重新启动服务
- 通过alarm定时器,启动服务
守护进程/进程互拉在分析360手机助手app时,发现其拥有N多个进程,一个进程kill后会被其它未kill的进程拉起,这也是一种思路吧,虽然有点流氓~ 守护进程一般有这样两种方式:
- 多个java进程守护互拉
- 底层C守护进程拉起App上层/java进程
Linux Am命令开启后台进程一种底层实现让进程不被杀死的方法,在Android4.4以上可能有兼容性问题,具体参考Ref-7
NotificationListenerService通知一种需要用户允许特定权限的系统拉起方式,4.3以上系统
前台浮窗有朋友提出一种应用退出后启动一个不可交互的浮窗,个人觉得这种方法是无效的,读者有兴趣可以一试
新方法(AccountSync)思路利用Android系统提供的账号和同步机制实现
效果
- 通过adb查看,运行在后台的服务其进程号变成了1(优先级仅次于前台进程),能提高进程优先级,对比如下图
正常情况
采用AccountSyncAdapter方法后
风险
- SyncAdapter时间进度不高,往往会因为手机处于休眠状态,而时间往后调整,同步间隔最低为1分钟
- 用户可以单独停止或者删除,有些手机账号默认是不同步的,需要手动开启
实现 (核心代码)
1 建立数据同步系统(ContentProvider)
通过一个ContentProvider用来作数据同步,由于并没有实际数据同步,所以此处就直接建立一个空的ContentProvider即可
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
| 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中声明
1 2 3 4 5
| <provider android:name="**.XXAccountProvider" android:authorities="@string/account_auth_provider" android:exported="false" android:syncable="true"/>
|
2 建立Sync系统 (SyncAdapter)
通过实现SyncAdapter这个系统服务后, 利用系统的定时器对程序数据ContentProvider进行更新,具体步骤为:
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 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); } } }
|
1 2 3 4 5 6 7 8 9 10 11 12
| <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为:
1 2 3 4 5 6 7
| <sync-adapter xmlns:android="http://schemas./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实现 手动更新
1 2 3 4 5 6 7 8 9
| 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); }
|
添加账号
1 2 3
| Account account = AccountService.GetAccount(); AccountManager accountManager = (AccountManager) context.getSystemService(Context.ACCOUNT_SERVICE); accountManager.addAccountExplicitly(...)
|
同步周期设置
1 2 3
| ContentResolver.setIsSyncable(account, CONTENT_AUTHORITY, 1); ContentResolver.setSyncAutomatically(account, CONTENT_AUTHORITY, true); ContentResolver.addPeriodicSync(account, CONTENT_AUTHORITY, new Bundle(), SYNC_FREQUENCY);
|
3 建立账号系统 (Account Authenticator)
通过建立Account账号,并关联SyncAdapter服务实现同步
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 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99
| 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; } } }
|
1 2 3 4 5 6 7 8 9 10 11 12
| <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为:
1 2 3 4 5 6 7
| <?xml version="1.0" encoding="utf-8"?> <account-authenticator xmlns:android="http://schemas./apk/res/android" android:accountType="@string/account_auth_type" android:icon="@drawable/icon" android:smallIcon="@drawable/icon" android:label="@string/app_name" />
|
Refs
微信Android客户端后台保活经验分享
Android Low Memory Killer原理
stackOverflow 上介绍的双Service方法
Write your own Android Sync Adapter
Write your own Android Authenticator
Android developer
Android篇从底层实现让进程不被杀死(失效Closed)
Android 4.3+ NotificationListenerService 的使用
Going multiprocess on Android
后记
2016.5.24
1. 本文发布时间写错了,5.19手贱成了6.19,就酱紫吧,懒得改了,五月份看过的童鞋就当狠狠滴穿越了一把吧,O(∩_∩)O哈哈哈~~
2. 本文在V2EX、稀土掘金、博客园、CSDN等等诸多网站上有转载或发布,收到了很多评论和讨论,其中有一部分以“天下兴亡匹夫有责”的心态批判笔者等同类开发者把Android生态给搞坏了,提到iOS的诸多好处等等,阐述几点个人观点:
① 据笔者研究,目前双Service拉起的方式在国内排前几的应用(微信/支付宝等等)中都有用到,进程互拉方式在360手机助手、应用宝等应用中有用到,这些才是真正黑科技,笔者提到的方法仅仅是取巧性的用到了Android系统提供的方法,谈不上XXX~~
② iOS的封闭造就其天然的优势,不存在这些问题; 而Android的开源,有诸多问题但不可否认的是其促进了技术的发展,科技的发展甚至人类的进步。 物极必反,很多事情都是双刃剑~
③ 后来经一些网友提醒,发现所谓提异议的这群家伙都是产品汪,半吊子技术,所以XXOO~~
④ 法海无涯,技术无边,风涯无罪,南无阿弥陀佛~~
本文首发于:“一种提高Android应用进程存活率新方法” 同步发表/转载 cnBlogs / CSDN / 伯乐在线 …
By SkySeraph-2016
|