最近遇到BackupManagerService备份恢复后桌面小部件重复添加达到800个之多的问题,严重影响系统体验,所以抽出半天时间进行分析.
废话不多说先看下BackupManagerService dump出来的信息从中窥探它的大概原理以及结构
DUMP OF SERVICE backup:
Backup Manager is enabled / provisioned / not pending init
Auto-restore is enabled
Last backup pass started: 0 (now = 1516085568739)
next scheduled: 1516056771175
Transport whitelist:
android/com.android.internal.backup.LocalTransportService
com.google.android.gms/.backup.component.D2dTransportService
com.google.android.gms/.backup.BackupTransportService
Available transports:
android/com.android.internal.backup.LocalTransport
destination: Backing up to debug-only private cache
intent: null
com.google.android.gms/.backup.migrate.service.D2dTransport
destination: Moving data to new device
intent: null
* com.google.android.gms/.backup.BackupTransportService
destination: hackerdevdas@gmail.com
intent: Intent { cmp=com.google.android.gms/.backup.SetBackupAccountActivity }
@pm@ - 3086 state bytes
com.google.android.apps.maps - 124 state bytes
android - 477 state bytes
com.google.android.apps.messaging - 124 state bytes
com.android.cellbroadcastreceiver - 116 state bytes
com.google.android.googlequicksearchbox - 116 state bytes
com.google.android.videos - 72 state bytes
com.google.android.googlequicksearchbox_widget - 95 state bytes
com.google.android.contacts - 124 state bytes
com.android.providers.settings - 84 state bytes
Pending init: 0
Ancestral: 0
Current: 3fea8d5038fd8e55
Participants:
uid: 1000
com.android.providers.settings
android
uid: 10000
com.android.providers.blockednumber
com.android.calllogbackup
com.android.providers.userdictionary
uid: 10009
com.android.cellbroadcastreceiver
uid: 10012
Ancestral packages: none
Ever backed up: 28
com.google.android.apps.maps
android
com.google.android.apps.messaging
com.android.cellbroadcastreceiver
com.google.android.googlequicksearchbox
com.google.android.videos
com.google.android.contacts
com.android.providers.settings
com.google.android.marvin.talkback
com.google.android.play.games
com.android.calllogbackup
com.google.android.apps.inputmethod.hindi
Pending key/value backup: 31
BackupRequest{pkg=com.google.android.apps.maps}
BackupRequest{pkg=android}
BackupRequest{pkg=com.google.android.apps.messaging}
BackupRequest{pkg=com.android.cellbroadcastreceiver}
BackupRequest{pkg=com.google.android.googlequicksearchbox}
BackupRequest{pkg=com.google.android.videos}
BackupRequest{pkg=com.google.android.contacts}
BackupRequest{pkg=com.android.providers.settings}
Full backup queue:68
0 : com.studio71.sunday_suspense
0 : com.qapp.secprotect
0 : se.dirac.acs
0 : com.android.bips
0 : com.android.carrierdefaultapp
0 : com.qti.vzw.ims.internal.tests
0 : com.qualcomm.timeservice
1511525714741 : com.google.android.ext.services
1511525862187 : com.android.launcher3
1511526633498 : com.google.android.backuptransport
1511530346032 : com.android.smspush
1511606005489 : com.lenovo.anyshare.gps
1511606022751 : com.graymatrix.did
1511606025293 : com.jio.media.ondemand
1511606027829 : com.android.cts.priv.ctsshim
--------- 0.013s was the duration of dumpsys backup
首先可以肯定BackupManagerService肯定是一个调度框架
其次它调度什么呢,肯定是Transport,这里可以看到有白名单,和可用的 transports,还有Transport执行任务的历史数据.
另外后面的几项信息
Pending init是什么? 从字意思看应该是待执行的初始化
Participants是参加者的含义到底是用来做什么的?
Ancestral字面意思是先祖,到底是干什么用的
另外三项好像还比较理解
Ever backed up:
Pending key/value backup 延时的keyvalue 备份
Full backup queue 完全备份队列
好了带着这些疑问我们进行分析.
public BackupManagerService(Context context, Trampoline parent) {
......
mBackupManagerBinder = Trampoline.asInterface(parent.asBinder());
....
Set<ComponentName> transportWhitelist = systemConfig.getBackupTransportWhitelist();
String transport = Settings.Secure.getString(context.getContentResolver(),
Settings.Secure.BACKUP_TRANSPORT);
......
mTransportManager = new TransportManager(context, transportWhitelist, currentTransport,
mTransportBoundListener, mHandlerThread.getLooper());
mTransportManager.registerAllTransports();
......
}
值得注意的是Trampoline是真正实现IBackupManager.Stub接口的类,向外部提供服务. 另外TransportManager可定是用于管理transport的,这里进行初始化.我们看下初始化逻辑
TransportManager(Context context, Set<ComponentName> whitelist, String defaultTransport,
TransportBoundListener listener, Looper looper) {
mContext = context;
mPackageManager = context.getPackageManager();
mTransportWhitelist = (whitelist != null) ? whitelist : new ArraySet<>();
mCurrentTransportName = defaultTransport;
mTransportBoundListener = listener;
mHandler = new RebindOnTimeoutHandler(looper);
}
只是简单的赋值,这里面连个关键的数据whitelist和defaultTransport已经传进来了.
我们再看一下TransportManager.registerAllTransports()函数,注意参数都null
private final Intent mTransportServiceIntent = new Intent(SERVICE_ACTION_TRANSPORT_HOST);
private static final String SERVICE_ACTION_TRANSPORT_HOST = "android.backup.TRANSPORT_HOST";
private void bindToAllInternal(String packageName, String[] components) {
PackageInfo pkgInfo = null;
...
Intent intent = new Intent(mTransportServiceIntent);
if (packageName != null) {
intent.setPackage(packageName);
}
List<ResolveInfo> hosts = mPackageManager.queryIntentServicesAsUser(
intent, 0, UserHandle.USER_SYSTEM);
if (hosts != null) {
for (ResolveInfo host : hosts) {
final ComponentName infoComponentName = host.serviceInfo.getComponentName();
boolean shouldBind = false;
if (components != null && packageName != null) {
for (String component : components) {
ComponentName cn = new ComponentName(pkgInfo.packageName, component);
if (infoComponentName.equals(cn)) {
shouldBind = true;
break;
}
}
} else {
shouldBind = true;
}
if (shouldBind && isTransportTrusted(infoComponentName)) {
tryBindTransport(infoComponentName);
}
}
}
}
private void tryBindTransport(ComponentName transportComponentName) {
Slog.d(TAG, "Binding to transport: " + transportComponentName.flattenToShortString());
// TODO: b/22388012 (Multi user backup and restore)
TransportConnection connection = new TransportConnection(transportComponentName);
if (bindToTransport(transportComponentName, connection)) {
synchronized (mTransportLock) {
mValidTransports.put(transportComponentName, connection);
}
} else {
Slog.w(TAG, "Couldn't bind to transport " + transportComponentName);
}
}
代码也很简单, 查询Action是android.backup.TRANSPORT_HOST的service,如果查询到的组件在白名单且是预装在priv-app下的应用,则认为是可以新人的组件,绑定它的service,绑定的过程很简单我们看下绑定完成执行的onServiceConnected函数,代码在frameworks/base/services/backup/java/com/android/server/backup/TransportManager.java文件中.
success = mTransportBoundListener.onTransportBound(mBinder);
mBoundTransports.put(mTransportName, component);
添加到mBoundTransports集合里面,代表已经绑定成功,另外绑定成功后又添加到mValidTransports中代表组件可用.
另外执行了mTransportBoundListener的回调,函数的代码在BackupManagerService类中.是一个内部成员类
private TransportManager.TransportBoundListener mTransportBoundListener =
new TransportManager.TransportBoundListener() {
@Override
public boolean onTransportBound(IBackupTransport transport) {
// If the init sentinel file exists, we need to be sure to perform the init
// as soon as practical. We also create the state directory at registration
// time to ensure it's present from the outset.
String name = null;
try {
name = transport.name();
String transportDirName = transport.transportDirName();
File stateDir = new File(mBaseStateDir, transportDirName);
stateDir.mkdirs();
File initSentinel = new File(stateDir, INIT_SENTINEL_FILE_NAME);
if (initSentinel.exists()) {
synchronized (mQueueLock) {
mPendingInits.add(name);
// TODO: pick a better starting time than now + 1 minute
long delay = 1000 * 60; // one minute, in milliseconds
mAlarmManager.set(AlarmManager.RTC_WAKEUP,
System.currentTimeMillis() + delay, mRunInitIntent);
}
}
return true;
} catch (Exception e) {
// the transport threw when asked its file naming prefs; declare it invalid
Slog.w(TAG, "Failed to regiser transport: " + name);
return false;
}
}
};
这里检查/data/backup/${transportDirName}/need_init文件是否存在,如果存在则建立一个mPendingInits任务,一分钟后唤醒执行mRunInitIntent描述的一个广播任务. 可见mRunInitIntent所描述的任务肯定是用于处理mPendingInits的. 另外${transportDirName}为绑定的transport自己定义的路径. 第一次绑定肯定不会有这个文件的,所以我们先不去分析初始化任务.
BackupManagerService如何启动还没有分析,这里就不去翻SystemServer代码了,是通过
BackupManagerService.Lifecycle启动的,Lifecycle实现了SystemService
public static final class Lifecycle extends SystemService {
public Lifecycle(Context context) {
super(context);
sInstance = new Trampoline(context);
}
@Override
public void onStart() {
publishBinderService(Context.BACKUP_SERVICE, sInstance);
}
@Override
public void onUnlockUser(int userId) {
if (userId == UserHandle.USER_SYSTEM) {
sInstance.initialize(userId);
// Migrate legacy setting
if (!backupSettingMigrated(userId)) {
if (DEBUG) {
Slog.i(TAG, "Backup enable apparently not migrated");
}
final ContentResolver r = sInstance.mContext.getContentResolver();
final int enableState = Settings.Secure.getIntForUser(r,
Settings.Secure.BACKUP_ENABLED, -1, userId);
if (enableState >= 0) {
if (DEBUG) {
Slog.i(TAG, "Migrating enable state " + (enableState != 0));
}
writeBackupEnableState(enableState != 0, userId);
Settings.Secure.putStringForUser(r,
Settings.Secure.BACKUP_ENABLED, null, userId);
} else {
if (DEBUG) {
Slog.i(TAG, "Backup not yet configured; retaining null enable state");
}
}
}
try {
sInstance.setBackupEnabled(readBackupEnableState(userId));
} catch (RemoteException e) {
// can't happen; it's a local object
}
}
}
}
启动直到onUnlockUser后就能读取到非dirctBoot的service了sInstance.initialize(userId);函数就是用于创建BackupManagerService,做初始化操作. 之后要根据backup_enabled值去写/data/backup/backup_enabled文件,别问我为啥还要做这一步周转,我目前也不知道,我们慢慢分析.
接下来,整个初始化的关键,让整个系统跑起来的关键就在 sInstance.setBackupEnabled(readBackupEnableState(userId));这一步.会调用到BackupManagerService的setBackupEnabled函数.
public void setBackupEnabled(boolean enable) {
......
boolean wasEnabled = mEnabled;
synchronized (this) {
writeBackupEnableState(enable, UserHandle.USER_SYSTEM);
mEnabled = enable;
}
synchronized (mQueueLock) {
if (enable && !wasEnabled && mProvisioned) {
// if we've just been enabled, start scheduling backup passes
KeyValueBackupJob.schedule(mContext);
scheduleNextFullBackupJob(0);
} else if (!enable) {
KeyValueBackupJob.cancel(mContext);
if (wasEnabled && mProvisioned) {
// NOTE: we currently flush every registered transport, not just
// the currently-active one.
String[] allTransports = mTransportManager.getBoundTransportNames();
// build the set of transports for which we are posting an init
for (String transport : allTransports) {
recordInitPendingLocked(true, transport);
}
mAlarmManager.set(AlarmManager.RTC_WAKEUP, System.currentTimeMillis(),
mRunInitIntent);
}
}
}
}
...
}
在开启状态的时候会执行 KeyValueBackupJob.schedule(mContext);
scheduleNextFullBackupJob(0); 函数,这是整个系统跑起来的关键,为了防止读代码时候太跳跃我们先看下变成不可用的情况,给每个allTransports添加了一个mPendingInit任务,并要求马上执行.
这下就可以看两个schedule函数了.
先看KeyValueBackupJob,代码在frameworks/base/services/backup/java/com/android/server/backup/KeyValueBackupJob.java中
private static ComponentName sKeyValueJobService =
new ComponentName("android", KeyValueBackupJob.class.getName());
public static void schedule(Context ctx, long delay) {
synchronized (KeyValueBackupJob.class) {
if (!sScheduled) {
if (delay <= 0) {
delay = BATCH_INTERVAL + new Random().nextInt(FUZZ_MILLIS);
}
if (BackupManagerService.DEBUG_SCHEDULING) {
Slog.v(TAG, "Scheduling k/v pass in "
+ (delay / 1000 / 60) + " minutes");
}
JobScheduler js = (JobScheduler) ctx.getSystemService(Context.JOB_SCHEDULER_SERVICE);
JobInfo.Builder builder = new JobInfo.Builder(JOB_ID, sKeyValueJobService)
.setMinimumLatency(delay)
.setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY)
.setRequiresCharging(true)
.setOverrideDeadline(MAX_DEFERRAL);
js.schedule(builder.build());
sNextScheduled = System.currentTimeMillis() + delay;
sScheduled = true;
}
}
}
这里添加了一个JobScheduler任务,延时4个小时到一天,真正执行任务的service是sKeyValueJobService(其实就是KeyValueBackupJob这个service),并且需要插电. 可见备份事件还是不太紧急的
public boolean onStartJob(JobParameters params) {
synchronized (KeyValueBackupJob.class) {
sNextScheduled = 0;
sScheduled = false;
}
// Time to run a key/value backup!
Trampoline service = BackupManagerService.getInstance();
try {
service.backupNow();
} catch (RemoteException e) {}
// This was just a trigger; ongoing wakelock management is done by the
// rest of the backup system.
return false;
}
这里只是调用了BackupManagerService.backupNow()函数.
Intent backupIntent = new Intent(RUN_BACKUP_ACTION);
backupIntent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
mRunBackupIntent = PendingIntent.getBroadcast(context, MSG_RUN_BACKUP, backupIntent, 0);
public void backupNow() {
......
final PowerSaveState result =
mPowerManager.getPowerSaveState(ServiceType.KEYVALUE_BACKUP);
if (result.batterySaverEnabled) {
if (DEBUG) Slog.v(TAG, "Not running backup while in battery save mode");
KeyValueBackupJob.schedule(mContext); // try again in several hours
} else {
if (DEBUG) Slog.v(TAG, "Scheduling immediate backup pass");
synchronized (mQueueLock) {
mRunBackupIntent.send();
......
// ...and cancel any pending scheduled job, because we've just superseded it
KeyValueBackupJob.cancel(mContext);
}
}
}
函数发了一个mRunBackupIntent描述的pendingIntent任务,然后取消job,我们来看看如何处理这个任务
在RunBackupReceiver内部实例中处理该广播RUN_BACKUP_ACTION
private class RunBackupReceiver extends BroadcastReceiver {
public void onReceive(Context context, Intent intent) {
if (RUN_BACKUP_ACTION.equals(intent.getAction())) {
synchronized (mQueueLock) {
if (mPendingInits.size() > 0) {
.....
} else {
if (mEnabled && mProvisioned) {
if (!mBackupRunning) {
mBackupRunning = true;
mWakelock.acquire();
Message msg = mBackupHandler.obtainMessage(MSG_RUN_BACKUP);
mBackupHandler.sendMessage(msg);
} else {
Slog.i(TAG, "Backup time but one already running");
}
} else {
Slog.w(TAG, "Backup pass but e=" + mEnabled + " p=" + mProvisioned);
}
}
}
}
}
}
这里有遇到了mPendingInits我们先不管它,直接分析else部分,如果备份可用并且当前没有正在运行的备份任务,就发送一个MSG_RUN_BACKUP给后台线程的handler处理
处理这个消息的部分在BackupManagerService的内部实例BackupHandler的handleMessage中,这部分代码也比较长.
ArrayList<BackupRequest> queue = new ArrayList<BackupRequest>();
File oldJournal = mJournal;
synchronized (mQueueLock) {
if (mPendingBackups.size() > 0) {
for (BackupRequest b: mPendingBackups.values()) {
queue.add(b);
}
mPendingBackups.clear();
// Start a new backup-queue journal file too
......
}
}
if (queue.size() > 0) {
// Spin up a backup state sequence and set it running
......
PerformBackupTask pbt = new PerformBackupTask(transport, dirName, queue,
oldJournal, null, null, Collections.<String>emptyList(), false,
false /* nonIncremental */);
Message pbtMessage = obtainMessage(MSG_BACKUP_RESTORE_STEP, pbt);
sendMessage(pbtMessage);
其实逻辑很简单,就是收集mPendingBackups放到queue集合里面.并创建PerformBackupTask作为一个备份任务去,并创建MSG_BACKUP_RESTORE_STEP去执行,这里只是因为分步去做才又进行了一次倒手,其实处理该任务的handler还是这个线程同一个handler. 我们先不管mPendingBackups哪里来的,这里告诉读者,是允许备份的包护数据变化后会通知BackupManagerService并添加PendingBackup任务.我们先去分析如何执行备份任务,回来在分析任务的添加过程.
case MSG_BACKUP_RESTORE_STEP:
{
try {
BackupRestoreTask task = (BackupRestoreTask) msg.obj;
if (MORE_DEBUG) Slog.v(TAG, "Got next step for " + task + ", executing");
task.execute();
} catch (ClassCastException e) {
Slog.e(TAG, "Invalid backup task in flight, obj=" + msg.obj);
}
break;
}
这里task实际类型为PerformBackupTask,我们只需要看下它的execute()函数就能知道其中逻辑.
public void execute() {
synchronized (mCancelLock) {
switch (mCurrentState) {
case INITIAL:
beginBackup();
break;
case RUNNING_QUEUE:
invokeNextAgent();
break;
case FINAL:
if (!mFinished) finalizeBackup();
else {
Slog.e(TAG, "Duplicate finish");
}
mFinished = true;
break;
}
}
}
这里要执行的是void beginBackup()函数