BackupManagerService分析 基于Android 8.0

最近遇到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()函数

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值