前言:笔者前段原因因为职业规划所以在试用期离职,现在工作面试受阻(主要是没有面试机会),哈哈哈。所以趁着这段时间不如整理下自己从事Android客户端开发三年的一些经历或者经验,仅作记录,也供大家参考。
背景:研究应用是怎么推送通知
原生保活策略:
Android系统APP保活
主要有两个因素决定:
- APP预置到system分区下的app、priv-app或Vendor-app下,预置到/data/app目录下APP的的不能被保活。
- APP的AndroidManifest.xml添加android:persistent="true"属性。
该属性的代码位置 : frameworks/base/core/res/res/values/attrs_manifest.xml
<!-- Flag to control special persistent mode of an application. This should
not normally be used by applications; it requires that the system keep
your application running at all times. -->
<attr name="persistent" format="boolean" />
通过官方注释 该属性用于是否让你的应用一直处于运行状态(通常说的常驻内存)。
设置该属性为true的app具有如下特点:
1、在系统启动的时候会被系统启动起来
2、在该app被强制杀掉后系统会重新启动该app,这种情况只针对系统内置app,第三方安装app不会被重启
Persistent属性的解析
1、persistent属性的解析该属性的解析主要在app被安装或者系统启动的时候发生
解析代码:
frameworks/base/core/java/android/content/pm/PackageParser.java
@UnsupportedAppUsage
private boolean parseBaseApplication(Package owner, Resources res,
XmlResourceParser parser, int flags, String[] outError)
throws XmlPullParserException, IOException {
final ApplicationInfo ai = owner.applicationInfo;
final String pkgName = owner.applicationInfo.packageName;
TypedArray sa = res.obtainAttributes(parser,
com.android.internal.R.styleable.AndroidManifestApplication);
...........
...........
if (sa.getBoolean(
com.android.internal.R.styleable.AndroidManifestApplication_persistent,
false)) {
// Check if persistence is based on a feature being present
final String requiredFeature = sa.getNonResourceString(com.android.internal.R.styleable
.AndroidManifestApplication_persistentWhenFeatureAvailable);
if (requiredFeature == null || mCallback.hasFeature(requiredFeature)) {
ai.flags |= ApplicationInfo.FLAG_PERSISTENT;
}
}
在解析完包信息之后系统会将解析好的所有包信息存放到PKMS中的mPackages的map中,
而ApplicationInfo的flag中有一个bit位用于保存该app是否是persistent的。这里只是把保存persistent的flag设置为FLAG_PERSISTENT。
在AndroidManifest设置了persistent为true的app是否能够在被异常杀死后能够得到重启的权力需要取决于该app对应的ProcessRecord的persistent属性,该属性只有在你的app既在AndroidManifest中配置了persistent=“true”,又是系统内置app时才会被设置为true。
2、app被异常结束后在frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java , cleanUpApplicationRecordLocked中对persistent为true的app进行重启.
@GuardedBy("this")
final boolean cleanUpApplicationRecordLocked(ProcessRecord app, int pid,
boolean restarting, boolean allowRestart, int index, boolean replacingPid,
boolean fromBinderDied) {
boolean restart;
synchronized (mProcLock) {
if (index >= 0) {
removeLruProcessLocked(app);
ProcessList.remove(pid);
}
// We don't want to unlinkDeathRecipient immediately, if it's not called from binder
// and it's not isolated, as we'd need the signal to bookkeeping the dying process list.
restart = app.onCleanupApplicationRecordLSP(mProcessStats, allowRestart,
fromBinderDied || app.isolated /* unlinkDeath */);
// Cancel pending frozen task and clean up frozen record if there is any.
mOomAdjuster.mCachedAppOptimizer.onCleanupApplicationRecordLocked(app);
}
mAppProfiler.onCleanupApplicationRecordLocked(app);
skipCurrentReceiverLocked(app);
updateProcessForegroundLocked(app, false, 0, false);
mServices.killServicesLocked(app, allowRestart);
mPhantomProcessList.onAppDied(pid);
// If the app is undergoing backup, tell the backup manager about it
final BackupRecord backupTarget = mBackupTargets.get(app.userId);
if (backupTarget != null && pid == backupTarget.app.getPid()) {
if (DEBUG_BACKUP || DEBUG_CLEANUP) Slog.d(TAG_CLEANUP, "App "
+ backupTarget.appInfo + " died during backup");
mHandler.post(new Runnable() {
@Override
public void run(){
try {
IBackupManager bm = IBackupManager.Stub.asInterface(
ServiceManager.getService(Context.BACKUP_SERVICE));
bm.agentDisconnectedForUser(app.userId, app.info.packageName);
} catch (RemoteException e) {
// can't happen; backup manager is local
}
}
});
}
mProcessList.scheduleDispatchProcessDiedLocked(pid, app.info.uid);
// If this is a preceding instance of another process instance
allowRestart = mProcessList.handlePrecedingAppDiedLocked(app);
// If somehow this process was still waiting for the death of its predecessor,
// (probably it's "killed" before starting for real), reset the bookkeeping.
final ProcessRecord predecessor = app.mPredecessor;
if (predecessor != null) {
predecessor.mSuccessor = null;
predecessor.mSuccessorStartRunnable = null;
app.mPredecessor = null;
}
// If the caller is restarting this app, then leave it in its
// current lists and let the caller take care of it.
if (restarting) {
return false;
}
if (!app.isPersistent() || app.isolated) {
if (DEBUG_PROCESSES || DEBUG_CLEANUP) Slog.v(TAG_CLEANUP,
"Removing non-persistent process during cleanup: " + app);
if (!replacingPid) {
mProcessList.removeProcessNameLocked(app.processName, app.uid, app);
}
mAtmInternal.clearHeavyWeightProcessIfEquals(app.getWindowProcessController());
} else if (!app.isRemoved()) {
// This app is persistent, so we need to keep its record around.
// If it is not already on the pending app list, add it there
// and start a new process for it.
if (mPersistentStartingProcesses.indexOf(app) < 0) {
mPersistentStartingProcesses.add(app);
restart = true; //!!!!!!!!!!!!!!!!!
}
}
if ((DEBUG_PROCESSES || DEBUG_CLEANUP) && mProcessesOnHold.contains(app)) Slog.v(
TAG_CLEANUP, "Clean-up removing on hold: " + app);
mProcessesOnHold.remove(app);
mAtmInternal.onCleanUpApplicationRecord(app.getWindowProcessController());
mProcessList.noteProcessDiedLocked(app);
if (restart && allowRestart && !app.isolated) {
// We have components that still need to be running in the
// process, so re-launch it.
if (index < 0) {
ProcessList.remove(pid);
}
// Remove provider publish timeout because we will start a new timeout when the
// restarted process is attaching (if the process contains launching providers).
mHandler.removeMessages(CONTENT_PROVIDER_PUBLISH_TIMEOUT_MSG, app);
mProcessList.addProcessNameLocked(app);
app.setPendingStart(false);
mProcessList.startProcessLocked(app, new HostingRecord(
HostingRecord.HOSTING_TYPE_RESTART, app.processName),
ZYGOTE_POLICY_FLAG_EMPTY);
return true;
} else if (pid > 0 && pid != MY_PID) {
// Goodbye!
removePidLocked(pid, app);
mHandler.removeMessages(PROC_START_TIMEOUT_MSG, app);
mBatteryStatsService.noteProcessFinish(app.processName, app.info.uid);
if (app.isolated) {
mBatteryStatsService.removeIsolatedUid(app.uid, app.info.uid);
}
app.setPid(0);
}
return false;
}
常见的第三方应用保活
以信鸽推送举例的流程原理介绍
简单说明Android客户端实现推送流程的步骤。
1.客户端App启动的时候会启动一个信鸽主Service,信鸽主Service全局唯一,一台设备共享一个信鸽主service
2.信鸽主Service在接入信鸽的应用中随机启动一个备份的Service,2个Service互相拉活,互相备份
3.信鸽主Service建立一个信鸽服务器的Socket长连接,并通过心跳等机制维持长连接一直存在
4.客户端主Service通过Socket长连接请求向信鸽服务器请求Token
5.信鸽服务器通过Socket长连接推送消息到客户端主Service
6.主Service把Push消息转发到对应的客户端App上
简单说明Android客户端实现厂商通道推送流程的步骤
- 发送注册请求到厂商服务请求Token
- 获取厂商的token
- 保存厂商返回的 Token 并同步到服务器
- 服务器将厂商生成的 Token 和账号等信息建立映射关系并保存
- 将需要推送的内容等信息告知给服务器
- 服务器根据 Token 关系调用厂商推送的 API ,推送消息到厂商服务器
- 厂商服务器再推送消息到客户端 APP