Android Open Source Project Patches

来源:互联网 发布:统计局数据由哪几部分 编辑:程序博客网 时间:2024/05/01 10:05

注: 以下链接基本都要出墙


1. Optimize code on get UidDetail in AppDataUsage

链接:https://android-review.googlesource.com/#/c/platform/packages/apps/Settings/+/348789/

第一个被merge的patch,UidDetailProvider提供了关于specific UID的API,可以直接调用,这里写的实在别扭,忍不住想改



2. RejectedExecutionException in DataUsageLIst

链接:https://android-review.googlesource.com/#/c/platform/packages/apps/Settings/+/347690/

这个patch要从AsyncTask的默认线程池说起

187    private static final int CORE_POOL_SIZE = Math.max(2, Math.min(CPU_COUNT - 1, 4));188    private static final int MAXIMUM_POOL_SIZE = CPU_COUNT * 2 + 1;199    private static final BlockingQueue<Runnable> sPoolWorkQueue =200            new LinkedBlockingQueue<Runnable>(128);

先看上面三个变量,它们的关系为:

(1) 如果池中的实际线程数小于CORE_POOL_SIZE,无论是否其中有空闲的线程,都会给新的任务产生新的线程
(2) 如果池中的线程数 > CORE_POOL_SIZE and < MAXIMUM_POOL_SIZE,而又有空闲线程,就给新任务使用空闲线程,如没有空闲线程,则产生新线程
(3) 如果池中的线程数=MAXIMUM_POOL_SIZE,则有空闲线程使用空闲线程,否则新任务放入sPoolWorkQueue。(线程的空闲只有在sPoolWorkQueue中不再有任务时才成立)

如果说任务多到sPoolWorkQueue都满了怎么办(可以看到sPoolWorkQueue的容量为128)?

这里又要谈到一个新概念:线程池的任务拒绝策略,直接贴developer的文档了

Rejected tasksNew tasks submitted in method execute(Runnable) will be rejected when the Executor has been shut down, and also when the Executor uses finite bounds for both maximum threads and work queue capacity, and is saturated. In either case, the execute method invokes the rejectedExecution(Runnable, ThreadPoolExecutor) method of its RejectedExecutionHandler. Four predefined handler policies are provided:In the default ThreadPoolExecutor.AbortPolicy, the handler throws a runtime RejectedExecutionException upon rejection.In ThreadPoolExecutor.CallerRunsPolicy, the thread that invokes execute itself runs the task.     This provides a simple feedback control mechanism that will slow down the rate that new tasks are submitted.In ThreadPoolExecutor.DiscardPolicy, a task that cannot be executed is simply dropped.In ThreadPoolExecutor.DiscardOldestPolicy, if the executor is not shut down,     the task at the head of the work queue is dropped, and then execution is retried (which can fail again, causing this to be repeated.)It is possible to define and use other kinds of RejectedExecutionHandler classes. Doing so requires some care especially when policies are designed to work only under particular capacity or queuing policies.
所以这个patch对应的crash的root cause就很明确了,短时间内提交的任务太多,且线程池使用默认的AbortPolicy作为任务拒绝策略,工作队列在被塞满时抛了RejectedExecutionException的异常。

一开始的修改方式是更改拒绝策略,至少不让它不发生crash,但这样的fix治标不治本,在Robin的启发以及我自己的尝试后发现子线程无须异步执行,由于任务本身并不耗时:

public static void bindView(UidDetailProvider provider, AppItem item,                AppDataUsagePreference target) {            final UidDetail cachedDetail = provider.getUidDetail(item.key, false);            if (cachedDetail != null) {                bindView(cachedDetail, target);            } else {                new UidDetailTask(provider, item, target).executeOnExecutor(                        AsyncTask.THREAD_POOL_EXECUTOR);            }        }
只是加载一些preference对应的icon和title,因此最终的改法是将executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR)改为execute (你可知道executeOnExecutor这个方法怎么读,反正我以前一直念的是错的[捂脸])。

说明一下,android 3.0之后,AsyncTask的execute方法不再是异步执行任务,它封装了一个静态内部类SerialExecutor和一个单例:

private static volatileExecutor sDefaultExecutor = SERIAL_EXECUTOR;

通过try finally块同步的在子线程中执行任务,这个不多说,感兴趣自己参考一下源码,代码还是很清楚的

241        public synchronized void execute(final Runnable r) {242            mTasks.offer(new Runnable() {243                public void run() {244                    try {245                        r.run();246                    } finally {247                        scheduleNext();248                    }249                }250            });251            if (mActive == null) {252                scheduleNext();253            }254        }255256        protected synchronized void scheduleNext() {257            if ((mActive = mTasks.poll()) != null) {258                THREAD_POOL_EXECUTOR.execute(mActive);259            }260        }261    }
从这个patch里真心学到不少。



3. Fix NPE crash in UsageAccessDetails

链接:https://android-review.googlesource.com/#/c/platform/packages/apps/Settings/+/487779/

本来是想尝试复现测试传来的crash stack,但没成想脑洞大开搞出了另一个crash,之后在7.0的nexus上复现,脑洞大开的复现过程:每个UsageAccessDetails对应着一个app,在UsageAccessDetails界面时uninstall对应的app package,后来发现在8.0上这个crash已经被Doris修掉了。仔细过了一遍逻辑代码,先前的crash stack也成功复现。

复现命令:adb shell am start -W -p 'com.android.settings' -n 'com.android.settings/.SubSettings' --es ':settings:show_fragment' 'com.android.settings.applications.UsageAccessDetails' --es 'package' 'com.android.settings2'

root cause:在AppInfoBase的onCreate中会调用retrieveAppEntry方法初始化一个叫mPackageInfo的对象,这个mPackageInfo是通过Intent传来的。

    protected String retrieveAppEntry() {        final Bundle args = getArguments();        mPackageName = (args != null) ? args.getString(ARG_PACKAGE_NAME) : null;        if (mPackageName == null) {            Intent intent = (args == null) ?                    getActivity().getIntent() : (Intent) args.getParcelable("intent");            if (intent != null) {                mPackageName = intent.getData().getSchemeSpecificPart();            }        }        mUserId = UserHandle.myUserId();        mAppEntry = mState.getEntry(mPackageName, mUserId);        if (mAppEntry != null) {            // Get application info again to refresh changed properties of application            try {                mPackageInfo = mPm.getPackageInfo(mAppEntry.info.packageName,                        PackageManager.MATCH_DISABLED_COMPONENTS |                        PackageManager.MATCH_ANY_USER |                        PackageManager.GET_SIGNATURES |                        PackageManager.GET_PERMISSIONS);            } catch (NameNotFoundException e) {                Log.e(TAG, "Exception when retrieving package:" + mAppEntry.info.packageName, e);            }        } else {            Log.w(TAG, "Missing AppEntry; maybe reinstalling?");            mPackageInfo = null;        }        return mPackageName;    }
再来看看onResume做了啥

    @Override    public void onResume() {        super.onResume();        mSession.resume();        mAppsControlDisallowedAdmin = RestrictedLockUtils.checkIfRestrictionEnforced(getActivity(),                UserManager.DISALLOW_APPS_CONTROL, mUserId);        mAppsControlDisallowedBySystem = RestrictedLockUtils.hasBaseUserRestriction(getActivity(),                UserManager.DISALLOW_APPS_CONTROL, mUserId);        if (!refreshUi()) {            setIntentAndFinish(true, true);        }    }
refreshUi方法在UsageAccessDetails中定义

    @Override    protected boolean refreshUi() {        if (mPackageInfo == null) {            return false;        }        mUsageState = mUsageBridge.getUsageInfo(mPackageName,                mPackageInfo.applicationInfo.uid);        boolean hasAccess = mUsageState.isPermissible();        mSwitchPref.setChecked(hasAccess);        mSwitchPref.setEnabled(mUsageState.permissionDeclared);        mUsagePrefs.setEnabled(hasAccess);        ResolveInfo resolveInfo = mPm.resolveActivityAsUser(mSettingsIntent,                PackageManager.GET_META_DATA, mUserId);        if (resolveInfo != null) {            if (findPreference(KEY_APP_OPS_SETTINGS_PREFS) == null) {                getPreferenceScreen().addPreference(mUsagePrefs);            }            Bundle metaData = resolveInfo.activityInfo.metaData;            mSettingsIntent.setComponent(new ComponentName(resolveInfo.activityInfo.packageName,                    resolveInfo.activityInfo.name));            if (metaData != null                    && metaData.containsKey(Settings.METADATA_USAGE_ACCESS_REASON)) {                mSwitchPref.setSummary(                        metaData.getString(Settings.METADATA_USAGE_ACCESS_REASON));            }        } else {            if (findPreference(KEY_APP_OPS_SETTINGS_PREFS) != null) {                getPreferenceScreen().removePreference(mUsagePrefs);            }        }        return true;    }
贴的是最新的aosp的代码,原本是没有对mPackageInfo是否为空的判断的(这里正是我提的patch中的一部分),可以看到下面有调到mPackageInfo.applicationInfo,mPackageInfo为空时自然发生crash,其实在retrieveAppEntry中有catch这个异常,但也正是因为这里catch住了,没有在这直接crash掉,按照安卓的生命周期,onResume也并不会因为onCreate有个异常被catch住就不执行了,毕竟是由AMS调度的。



4. Fix AddAccountSettings memory leak

链接:https://android-review.googlesource.com/#/c/platform/frameworks/base/+/475557/

第一个framework的patch,和添加账号有关,介绍这个问题得先介绍一下安卓账号管理的框架了,其实直接看官方文档是最好的,我就简单说明一下app的账号是如何被添加的:

当点击listview中的账号项时,先执行AddAccountSettings中的addAccount方法,其中传了一个mCallBack参数,先说一下这个callback的作用是跳转到相应的账号登录界面

private final AccountManagerCallback<Bundle> mCallback = new AccountManagerCallback<Bundle>() {    @Override    public void run(AccountManagerFuture<Bundle> future) {        boolean done = true;        try {            Bundle bundle = future.getResult();            Intent intent = (Intent) bundle.get(AccountManager.KEY_INTENT);            if (intent != null) {                done = false;                Bundle addAccountOptions = new Bundle();                addAccountOptions.putParcelable(KEY_CALLER_IDENTITY, mPendingIntent);                addAccountOptions.putBoolean(EXTRA_HAS_MULTIPLE_USERS,                        Utils.hasMultipleUsers(AddAccountSettings.this));                addAccountOptions.putParcelable(EXTRA_USER, mUserHandle);                intent.putExtras(addAccountOptions);                intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);                startActivityForResultAsUser(intent, ADD_ACCOUNT_REQUEST, mUserHandle);            } else {                setResult(RESULT_OK);                if (mPendingIntent != null) {                    mPendingIntent.cancel();                    mPendingIntent = null;                }            }             if (Log.isLoggable(TAG, Log.VERBOSE)) Log.v(TAG, "account added: " + bundle);        } catch (OperationCanceledException e) {            if (Log.isLoggable(TAG, Log.VERBOSE)) Log.v(TAG, "addAccount was canceled");        } catch (IOException e) {            if (Log.isLoggable(TAG, Log.VERBOSE)) Log.v(TAG, "addAccount failed: " + e);        } catch (AuthenticatorException e) {            if (Log.isLoggable(TAG, Log.VERBOSE)) Log.v(TAG, "addAccount failed: " + e);        } finally {            if (done) {                finish();            }        }    }};
AddAccountSettings的addAccount方法调用AccountManager的AddAccountAsUser方法

private void addAccount(String accountType) {    Bundle addAccountOptions = new Bundle();    /*     * The identityIntent is for the purposes of establishing the identity     * of the caller and isn't intended for launching activities, services     * or broadcasts.     *     * Unfortunately for legacy reasons we still need to support this. But     * we can cripple the intent so that 3rd party authenticators can't     * fill in addressing information and launch arbitrary actions.     */    Intent identityIntent = new Intent();    identityIntent.setComponent(new ComponentName(SHOULD_NOT_RESOLVE, SHOULD_NOT_RESOLVE));    identityIntent.setAction(SHOULD_NOT_RESOLVE);    identityIntent.addCategory(SHOULD_NOT_RESOLVE);     mPendingIntent = PendingIntent.getBroadcast(this, 0, identityIntent, 0);    addAccountOptions.putParcelable(KEY_CALLER_IDENTITY, mPendingIntent);    addAccountOptions.putBoolean(EXTRA_HAS_MULTIPLE_USERS, Utils.hasMultipleUsers(this));    AccountManager.get(this).addAccountAsUser(            accountType,            null, /* authTokenType */            null, /* requiredFeatures */            addAccountOptions,            null,            mCallback,            null /* handler */,            mUserHandle);    mAddAccountCalled  = true;}
AccountManager的AddAccountAsUser通过一个继承FutureTask并实现AccountManagerFuture接口的AmsTask对象处理添加账号事件,在重写的doWork方法中通过binder调用AccountManagerSerive的addAccountAsUser,最终会通过binder调到AbstractAccountAuthenticator的addAccount,到这总算是调到头了

public void addAccount(IAccountAuthenticatorResponse response, String accountType,        String authTokenType, String[] features, Bundle options)        throws RemoteException {    if (Log.isLoggable(TAG, Log.VERBOSE)) {        Log.v(TAG, "addAccount: accountType " + accountType                + ", authTokenType " + authTokenType                + ", features " + (features == null ? "[]" : Arrays.toString(features)));    }    checkBinderPermission();    try {        final Bundle result = AbstractAccountAuthenticator.this.addAccount(            new AccountAuthenticatorResponse(response),                accountType, authTokenType, features, options);        if (Log.isLoggable(TAG, Log.VERBOSE)) {            if (result != null) {                result.keySet(); // force it to be unparcelled            }            Log.v(TAG, "addAccount: result " + AccountManager.sanitizeResult(result));        }        if (result != null) {            response.onResult(result);        }    } catch (Exception e) {        handleException(response, "addAccount", accountType, e);    }}
这里就很清楚了,首先调用app自己实现的addAccount方法并将结果用一个Bundle对象保存,然后通过response的onResult方法将结果返回给AccountManagerService(通过binder),再由AccountManagerService$Session通过binder将结果返回给AccountManager,继续调用AccountManager$AmsTask的onResult

@Overridepublic void onResult(Bundle bundle) {    Intent intent = bundle.getParcelable(KEY_INTENT);    if (intent != null && mActivity != null) {        // since the user provided an Activity we will silently start intents        // that we see        mActivity.startActivity(intent);        // leave the Future running to wait for the real response to this request    } else if (bundle.getBoolean("retry")) {        try {            doWork();        } catch (RemoteException e) {            throw e.rethrowFromSystemServer();        }    } else {        set(bundle);    }}
这里的intent是app提供的,再看mActivity,mActivity的值是AddAccountSettings调用AccountManager的addAccountAsUser方法时传入的第5个参数,正好为null,因此这里会走到set(bundle),再来看看set做了什么事

protected void set(V v) {    if (U.compareAndSwapInt(this, STATE, NEW, COMPLETING)) {        outcome = v;        U.putOrderedInt(this, STATE, NORMAL); // final state        finishCompletion();    }}
首先更新了task的state,然后调用finishCompletion方法

private void finishCompletion() {    // assert state > COMPLETING;    for (WaitNode q; (q = waiters) != null;) {        if (U.compareAndSwapObject(this, WAITERS, q, null)) {            for (;;) {                Thread t = q.thread;                if (t != null) {                    q.thread = null;                    LockSupport.unpark(t);                }                WaitNode next = q.next;                if (next == null)                    break;                q.next = null; // unlink to help gc                q = next;            }            break;        }    }    done();    callable = null;        // to reduce footprint}
最后调到done方法,继续看AmsTask重写的done方法
protected void done() {    if (mCallback != null) {        postToHandler(mHandler, mCallback, this);    }}------------------------------------------------------------------------------------------------------------------------------------private void postToHandler(Handler handler, final AccountManagerCallback<Bundle> callback,        final AccountManagerFuture<Bundle> future) {    handler = handler == null ? mMainHandler : handler;    handler.post(new Runnable() {        @Override        public void run() {            callback.run(future);        }    });}
done方法中调用postToHandler,通过异步线程执行callback,绕了一大圈终于调回callback了,callback先getResult获取异步任务执行完毕的返回值(Bundle对象),然后通过这个bundle中存的Intent完成跳转。

下面说一下内存泄露是怎么产生的吧,这可以说是android原生为我们提供的一个教科书般的非静态匿名内部类造成内存泄露案例了,最开始的callback是AddAccountSettings中的一个非静态匿名内部类,它会持有外部类实例,AddAccountSettings会在每次点击账号item时调启。注意看AbstractAccountAuthenticator这个类作为binder call servert端的addAccount方法,先是从app继承AbstractAccountAuthenticator后实现的addAccount方法中获取Bundle对象,然后如果它不等于null,则调response的onResult了,然后等于null怎么办?据Robin所说,这里原本是有个binder call的超时广播的(觉得是会有这样一个广播的,找过,但没找到),原来是被Carlos删了,至于为什么删就不知道了,其实解决这个问题很简单,做个判断就行了,等于null的时候调response的onError方法,如果这里为null,binder call server端(AccountManagerService)不返回值给client端(AccountManager)会导致AccountManager一直持有AddAccountSettings中的callback,相当于一直持有AddAccountSettings这个Activity实例,导致其无法被gc从而发生内存泄露。

分析完这个问题感觉对binder call的理解又深了一些。



5. Fix another AddAccountSettings memory leak

链接:https://android-review.googlesource.com/#/c/platform/frameworks/base/+/489377/

上面分析并修复了一起由AddAccountSettings造成的内存泄露,这个patch是修复另一个。

上面的问题是app返回了一个空的Bundle,现在Bundle不空了,我们继续跟着调用栈走,到了AccountManagerService的onResult方法,这个方法非常的长

@Override        public void onResult(Bundle result) {            Bundle.setDefusable(result, true);            mNumResults++;            Intent intent = null;            if (result != null) {                boolean isSuccessfulConfirmCreds = result.getBoolean(                        AccountManager.KEY_BOOLEAN_RESULT, false);                boolean isSuccessfulUpdateCredsOrAddAccount =                        result.containsKey(AccountManager.KEY_ACCOUNT_NAME)                        && result.containsKey(AccountManager.KEY_ACCOUNT_TYPE);                // We should only update lastAuthenticated time, if                // mUpdateLastAuthenticatedTime is true and the confirmRequest                // or updateRequest was successful                boolean needUpdate = mUpdateLastAuthenticatedTime                        && (isSuccessfulConfirmCreds || isSuccessfulUpdateCredsOrAddAccount);                if (needUpdate || mAuthDetailsRequired) {                    boolean accountPresent = isAccountPresentForCaller(mAccountName, mAccountType);                    if (needUpdate && accountPresent) {                        updateLastAuthenticatedTime(new Account(mAccountName, mAccountType));                    }                    if (mAuthDetailsRequired) {                        long lastAuthenticatedTime = -1;                        if (accountPresent) {                            lastAuthenticatedTime = mAccounts.accountsDb                                    .findAccountLastAuthenticatedTime(                                            new Account(mAccountName, mAccountType));                        }                        result.putLong(AccountManager.KEY_LAST_AUTHENTICATED_TIME,                                lastAuthenticatedTime);                    }                }            }            if (result != null                    && (intent = result.getParcelable(AccountManager.KEY_INTENT)) != null) {                checkKeyIntent(                        Binder.getCallingUid(),                        intent);            }            if (result != null                    && !TextUtils.isEmpty(result.getString(AccountManager.KEY_AUTHTOKEN))) {                String accountName = result.getString(AccountManager.KEY_ACCOUNT_NAME);                String accountType = result.getString(AccountManager.KEY_ACCOUNT_TYPE);                if (!TextUtils.isEmpty(accountName) && !TextUtils.isEmpty(accountType)) {                    Account account = new Account(accountName, accountType);                    cancelNotification(getSigninRequiredNotificationId(mAccounts, account),                            new UserHandle(mAccounts.userId));                }            }            IAccountManagerResponse response;            if (mExpectActivityLaunch && result != null                    && result.containsKey(AccountManager.KEY_INTENT)) {                response = mResponse;            } else {                response = getResponseAndClose();            }            if (response != null) {                try {                    if (result == null) {                        if (Log.isLoggable(TAG, Log.VERBOSE)) {                            Log.v(TAG, getClass().getSimpleName()                                    + " calling onError() on response " + response);                        }                        response.onError(AccountManager.ERROR_CODE_INVALID_RESPONSE,                                "null bundle returned");                    } else {                        if (mStripAuthTokenFromResult) {                            result.remove(AccountManager.KEY_AUTHTOKEN);                        }                        if (Log.isLoggable(TAG, Log.VERBOSE)) {                            Log.v(TAG, getClass().getSimpleName()                                    + " calling onResult() on response " + response);                        }                        if ((result.getInt(AccountManager.KEY_ERROR_CODE, -1) > 0) &&                                (intent == null)) {                            // All AccountManager error codes are greater than 0                            response.onError(result.getInt(AccountManager.KEY_ERROR_CODE),                                    result.getString(AccountManager.KEY_ERROR_MESSAGE));                        } else {                            response.onResult(result);                        }                    }                } catch (RemoteException e) {                    // if the caller is dead then there is no one to care about remote exceptions                    if (Log.isLoggable(TAG, Log.VERBOSE)) {                        Log.v(TAG, "failure while notifying response", e);                    }                }            }        }
主要关注这个checkKeyIntent,它是android 7.0上新增的代码,作用从命名就能看出了

/**         * Checks Intents, supplied via KEY_INTENT, to make sure that they don't violate our         * security policy.         *         * In particular we want to make sure that the Authenticator doesn't try to trick users         * into launching arbitrary intents on the device via by tricking to click authenticator         * supplied entries in the system Settings app.         */        protected void checkKeyIntent(                int authUid,                Intent intent) throws SecurityException {            long bid = Binder.clearCallingIdentity();            try {                PackageManager pm = mContext.getPackageManager();                ResolveInfo resolveInfo = pm.resolveActivityAsUser(intent, 0, mAccounts.userId);                ActivityInfo targetActivityInfo = resolveInfo.activityInfo;                int targetUid = targetActivityInfo.applicationInfo.uid;                if (!isExportedSystemActivity(targetActivityInfo)                        && (PackageManager.SIGNATURE_MATCH != pm.checkSignatures(authUid,                                targetUid))) {                    String pkgName = targetActivityInfo.packageName;                    String activityName = targetActivityInfo.name;                    String tmpl = "KEY_INTENT resolved to an Activity (%s) in a package (%s) that "                            + "does not share a signature with the supplying authenticator (%s).";                    throw new SecurityException(                            String.format(tmpl, activityName, pkgName, mAccountType));                }            } finally {                Binder.restoreCallingIdentity(bid);            }        }
眼尖的一下就看出来了吧

ResolveInfo resolveInfo = pm.resolveActivityAsUser(intent, 0, mAccounts.userId);ActivityInfo targetActivityInfo = resolveInfo.activityInfo;
如果这个intent并不对应任何Activity,PMS会返回null,然后NPE在所难免,有趣的是,这里的NPE会被catch,在哪被catch?打断点后发现是在binder的execTransaction方法中被catch的,由于这里是单工通信(FLAG_ONEWAY),server将不会reply to client,到此处就和上一个问题一样了,你可能要问,哪个app会干这种无聊的事?(有兴趣的可以找个老版本手机test一下),不管app干不干,android系统必须有充足的check来保证自身的健壮性。



6. Fix wrong position of cursor in IccLockSettings

链接:https://android-review.googlesource.com/#/c/platform/packages/apps/Settings/+/512335/

光标问题不解释。



7. Remove "result2" in AccountManagerService

链接:https://android-review.googlesource.com/#/c/platform/frameworks/base/+/510996/

看着很别扭,分析了一下感觉没什么用。



8. Fix wrong position of cursor in OwnerInfoSettings

链接:https://android-review.googlesource.com/#/c/platform/packages/apps/Settings/+/519255/

光标问题不解释。


9. Fix wrong switch state set in DevelopmentSettings

链接:https://android-review.googlesource.com/#/c/platform/packages/apps/Settings/+/520275/

由于cherry-pick conflict被zhangfan abandon掉了,实际上是merge internally

这个问题直接贴源码就能看出root cause了。下面是8.0上修改前DevelopmentSettings的onResume方法

@Override    public void onResume() {        super.onResume();        if (mUnavailable) {            // Show error message            if (!isUiRestrictedByOnlyAdmin()) {                getEmptyTextView().setText(R.string.development_settings_not_available);            }            getPreferenceScreen().removeAll();            return;        }        // A DeviceAdmin has specified a maximum time until the device        // will lock...  in this case we can't allow the user to turn        // on "stay awake when plugged in" because that would defeat the        // restriction.        final EnforcedAdmin admin = RestrictedLockUtils.checkIfMaximumTimeToLockIsSet(                getActivity());        mKeepScreenOn.setDisabledByAdmin(admin);        if (admin == null) {            mDisabledPrefs.remove(mKeepScreenOn);        } else {            mDisabledPrefs.add(mKeepScreenOn);        }        final boolean lastEnabledState = mSettingsEnabler.getLastEnabledState();        mSwitchBar.setChecked(lastEnabledState);        setPrefsEnabledState(lastEnabledState);        if (mHaveDebugSettings && !lastEnabledState) {            // Overall debugging is disabled, but there are some debug            // settings that are enabled.  This is an invalid state.  Switch            // to debug settings being enabled, so the user knows there is            // stuff enabled and can turn it all off if they want.            mSettingsEnabler.enableDevelopmentSettings();            mSwitchBar.setChecked(lastEnabledState);            setPrefsEnabledState(lastEnabledState);        }        mSwitchBar.show();        if (mColorModePreference != null) {            mColorModePreference.startListening();            mColorModePreference.updateCurrentAndSupported();        }    }
8.0上把开发这选项的开关控件封装成了一个class,注意这一句:mSettingsEnabler.enableDevelopmentSettings();
mSettingsEnabler是一个DevelopmentSettingsEnabler的引用,看下具体实现:

public class DevelopmentSettingsEnabler implements LifecycleObserver, OnResume {    private final Context mContext;    private final SharedPreferences mDevelopmentPreferences;    private boolean mLastEnabledState;    public DevelopmentSettingsEnabler(Context context, Lifecycle lifecycle) {        mContext = context;        mDevelopmentPreferences = context.getSharedPreferences(DevelopmentSettings.PREF_FILE,                Context.MODE_PRIVATE);        if (lifecycle != null) {            lifecycle.addObserver(this);        }    }    @Override    public void onResume() {        mLastEnabledState = Settings.Global.getInt(mContext.getContentResolver(),                Settings.Global.DEVELOPMENT_SETTINGS_ENABLED, 0) != 0;    }    public static boolean enableDevelopmentSettings(Context context, SharedPreferences prefs) {        prefs.edit()                .putBoolean(DevelopmentSettings.PREF_SHOW, true)                .commit();        return Settings.Global.putInt(context.getContentResolver(),                Settings.Global.DEVELOPMENT_SETTINGS_ENABLED, 1);    }    public boolean getLastEnabledState() {        return mLastEnabledState;    }    public void enableDevelopmentSettings() {        mLastEnabledState = enableDevelopmentSettings(mContext, mDevelopmentPreferences);    }    public void disableDevelopmentSettings() {        mDevelopmentPreferences.edit()                .putBoolean(DevelopmentSettings.PREF_SHOW, false)                .commit();        Settings.Global.putInt(mContext.getContentResolver(),                Settings.Global.DEVELOPMENT_SETTINGS_ENABLED, 0);        mLastEnabledState = false;    }}

非常短的class,enableDevelopmentSettings方法实际上就是将打开或关闭的标记可持久化记录下,然后将此状态赋值给成员变量mLastEnabledState。
bug很明显:

mSwitchBar.setChecked(lastEnabledState);setPrefsEnabledState(lastEnabledState);
这两句的lastEnabledState并非最新值


未完待续。。。(昨天又发现了8.0的一个bug)




原创粉丝点击