最近遇到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.LocalTransportServicecom.google.android.gms/.backup.component.D2dTransportServicecom.google.android.gms/.backup.BackupTransportService
Available transports:android/com.android.internal.backup.LocalTransportdestination: Backing up to debug-only private cacheintent: nullcom.google.android.gms/.backup.migrate.service.D2dTransportdestination: Moving data to new deviceintent: null* com.google.android.gms/.backup.BackupTransportServicedestination: hackerdevdas@gmail.comintent: Intent { cmp=com.google.android.gms/.backup.SetBackupAccountActivity }@pm@ - 3086 state bytescom.google.android.apps.maps - 124 state bytesandroid - 477 state bytescom.google.android.apps.messaging - 124 state bytescom.android.cellbroadcastreceiver - 116 state bytescom.google.android.googlequicksearchbox - 116 state bytescom.google.android.videos - 72 state bytescom.google.android.googlequicksearchbox_widget - 95 state bytescom.google.android.contacts - 124 state bytescom.android.providers.settings - 84 state bytes
Pending init: 0
Ancestral: 0
Current:   3fea8d5038fd8e55
Participants:uid: 1000com.android.providers.settingsandroiduid: 10000com.android.providers.blockednumbercom.android.calllogbackupcom.android.providers.userdictionaryuid: 10009com.android.cellbroadcastreceiveruid: 10012
Ancestral packages: none
Ever backed up: 28com.google.android.apps.mapsandroidcom.google.android.apps.messagingcom.android.cellbroadcastreceivercom.google.android.googlequicksearchboxcom.google.android.videoscom.google.android.contactscom.android.providers.settingscom.google.android.marvin.talkbackcom.google.android.play.gamescom.android.calllogbackupcom.google.android.apps.inputmethod.hindi
Pending key/value backup: 31BackupRequest{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:680 : com.studio71.sunday_suspense0 : com.qapp.secprotect0 : se.dirac.acs0 : com.android.bips0 : com.android.carrierdefaultapp0 : com.qti.vzw.ims.internal.tests0 : com.qualcomm.timeservice1511525714741 : com.google.android.ext.services1511525862187 : com.android.launcher31511526633498 : com.google.android.backuptransport1511530346032 : com.android.smspush1511606005489 : com.lenovo.anyshare.gps1511606022751 : com.graymatrix.did1511606025293 : com.jio.media.ondemand1511606027829 : 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() {@Overridepublic 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 minutelong delay = 1000 * 60; // one minute, in millisecondsmAlarmManager.set(AlarmManager.RTC_WAKEUP,System.currentTimeMillis() + delay, mRunInitIntent);}}return true;} catch (Exception e) {// the transport threw when asked its file naming prefs; declare it invalidSlog.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);}@Overridepublic void onStart() {publishBinderService(Context.BACKUP_SERVICE, sInstance);}@Overridepublic void onUnlockUser(int userId) {if (userId == UserHandle.USER_SYSTEM) {sInstance.initialize(userId);// Migrate legacy settingif (!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 passesKeyValueBackupJob.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 initfor (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 itKeyValueBackupJob.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()函数

BackupManagerService分析 基于Android 8.0相关推荐

  1. 华为内测基于Android 10.0的EMUI 10系统;2019年Q1真无线耳机市场份额,苹果占半壁江山……...

    关注并标星星CSDN云计算 极客头条:速递.最新.绝对有料.这里有企业新动.这里有业界要闻,打起十二分精神,紧跟fashion你可以的! 每周三次,打卡即read 更快.更全了解泛云圈精彩news g ...

  2. 基于 Android 6.0 的 小米 MIUI 8 已开源

    MIUI是小米科技推出的一款基于CyanogenMod而深度定制的Android移动操作系统,从Android 4.0开始,已经完全脱离CyanogenMod而改为基于原厂ROM的定制方式,中文名称为 ...

  3. 华为EMUI 10系统内测截图曝光:基于Android 10.0

    日前有消息称,华为正在内测基于Android 10.0系统的EMUI 10系统,该系统不仅在内核方面得以升级,更会内置全套谷歌GMS套件. 从目前流出的截图来看,内测机型仅限今年上半年推出的旗舰产品华 ...

  4. 基于Android 9.0 电池温度异常提醒

    基于Android 9.0 电池温度异常提醒 参考文章: (1)基于Android 9.0 电池温度异常提醒 (2)https://www.cnblogs.com/wangjie1990/p/1132 ...

  5. miui 10 android 9,MIUI10开发版8.12.13发布 基于Android 9.0 小米Max3已更新

    截止目前,MIUI10开发版8.12.13已发布.其实,要按照MIUI官方推送的时间来计算,我手里的小米Max3早在前天就应该收到更新提醒.不过,稍微有些遗憾地是,由于小米Max3在抽屉里搁置太久,以 ...

  6. flyme最新7基于android,终于来了,魅族开始基于Android 7.0版本的Flyme内测

    拼 命 加 载 中 ... 在大部分厂商已经跟进基于Android 7.0的版本时,作为国内最著名的定制ROM之一的Flyme还停留在基于Android 6.0的版本,在系统更新速度上落后了很多.不过 ...

  7. 海信电视power android,海信85U9E评测:基于Android 9.0深度优化的电视系统

    关注手机行业的朋友应该注意到了,从今年开始,不但已经有厂商将手机拍照提升到 1 亿像素级别,还纷纷发力手机 8K 视频拍摄,虽然这多少有点炫技成分,但不得不说随着人们对视频内容的需求不断增加,我们生活 ...

  8. 氢os android 7.0,氢OS 3.0正式推送!基于Android 7.0,简洁纯净

    原标题:氢OS 3.0正式推送!基于Android 7.0,简洁纯净 一加手机自推出伊始就坚持"不将就"的品牌理念,用顶级硬件和较低的价格以及众多定制ROM俘获了众多"加 ...

  9. 联想Android+7,联想ZUK发布ZUI 2.5:基于Android 7.0 优化很强大

    原标题:联想ZUK发布ZUI 2.5:基于Android 7.0 优化很强大 [PConline 资讯]相信很多喜欢联想的用户对于ZUK这个品牌很熟悉吧,做为联想旗下的品牌,ZUK一直也很给力. 而今 ...

最新文章

  1. 2018-3-31 文章(ELM-Chinese-Brief)原文
  2. JavaScript实现自动评论哔哩哔哩视频
  3. suffix tree
  4. centos查看网关地址
  5. 如何使用JavaScript实现纯前端读取和导出excel文件(转)
  6. python gis库_使用开放的python库自动化GIS和遥感工作流
  7. 【汇编语言】清华大学学堂在线《汇编语言程序设计》课程学习笔记
  8. 神经网络基本原理简明教程-0-Python-Numpy库的点滴
  9. 【Android开发—智能家居系列】(三):手机连接WIFI模块
  10. 阿里云服务器下安装LAMP环境(CentOS Linux 6.3)(1)
  11. 利用Enterprise Library中的DAAB构造的数据库访问架构
  12. 从Gradient Descent 到 Stochastic Gradient Descent(SGD)
  13. ai人工智能电话机器人应用市场分析
  14. MathCAD14破解版
  15. 《Netlogo多主体建模入门》学习笔记(2)
  16. 英特尔核显无法为此计算机,win10intel显卡驱动装不上怎么办_Win10系统无法安装intel显卡驱动如何解决...
  17. 测量脉冲宽度仿真proteus
  18. 轻松搞定iOS自动化环境搭建
  19. HSSF、XSSF、SXSSF、对Excel的区别,以及对应不同后缀名Excel(xls,xlsx)的使用
  20. AngularJS车牌号正则表达式验证

热门文章

  1. 名著中的经典语录(转自网络)
  2. POJ 3290 WFF 'N PROOF 英文少
  3. python 删除pdf页面_删除PDF其中几页的方法
  4. 看设计师大牛如何将用户体验与建站融会贯通
  5. pdf翻译,两款pdf文件翻译软件,支持linux/ubuntu,window,mac下使用
  6. mysql rds数据库优势_云数据库 RDS MySQL究竟有何优势呢?
  7. 基于ResNetRS的宝可梦图像识别
  8. bag of tricks for image classification with convolutional neural networks
  9. 破解网易云js加密,爬虫获取网易云评论
  10. Educational Codeforces Round 92 (Rated for Div. 2)题解(A-C)