Android Open Source Project Patches

2024-03-20 12:18

本文主要是介绍Android Open Source Project Patches,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

注: 以下链接均要翻墙

 

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/

要从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 tasks
New 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        }
255
256        protected synchronized void scheduleNext() {
257            if ((mActive = mTasks.poll()) != null) {
258                THREAD_POOL_EXECUTOR.execute(mActive);
259            }
260        }
261    }

 

 

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对象,它是通过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 applicationtry {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做了什么

    @Overridepublic 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中定义

    @Overrideprotected 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>() {@Overridepublic 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

@Override
public 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 seemActivity.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 statefinishCompletion();}
}

首先更新了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 gcq = 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() {@Overridepublic 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从而发生内存泄露。

 


5. Fix another AddAccountSettings memory leak

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

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

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

@Overridepublic 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 successfulboolean 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 0response.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 exceptionsif (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方法

@Overridepublic void onResume() {super.onResume();if (mUnavailable) {// Show error messageif (!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);}}@Overridepublic 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并非最新值

 


还有不少有空的时候再整理吧

 


 

这篇关于Android Open Source Project Patches的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/829422

相关文章

Android实现任意版本设置默认的锁屏壁纸和桌面壁纸(两张壁纸可不一致)

客户有些需求需要设置默认壁纸和锁屏壁纸  在默认情况下 这两个壁纸是相同的  如果需要默认的锁屏壁纸和桌面壁纸不一样 需要额外修改 Android13实现 替换默认桌面壁纸: 将图片文件替换frameworks/base/core/res/res/drawable-nodpi/default_wallpaper.*  (注意不能是bmp格式) 替换默认锁屏壁纸: 将图片资源放入vendo

Android平台播放RTSP流的几种方案探究(VLC VS ExoPlayer VS SmartPlayer)

技术背景 好多开发者需要遴选Android平台RTSP直播播放器的时候,不知道如何选的好,本文针对常用的方案,做个大概的说明: 1. 使用VLC for Android VLC Media Player(VLC多媒体播放器),最初命名为VideoLAN客户端,是VideoLAN品牌产品,是VideoLAN计划的多媒体播放器。它支持众多音频与视频解码器及文件格式,并支持DVD影音光盘,VCD影

android-opencv-jni

//------------------start opencv--------------------@Override public void onResume(){ super.onResume(); //通过OpenCV引擎服务加载并初始化OpenCV类库,所谓OpenCV引擎服务即是 //OpenCV_2.4.3.2_Manager_2.4_*.apk程序包,存

从状态管理到性能优化:全面解析 Android Compose

文章目录 引言一、Android Compose基本概念1.1 什么是Android Compose?1.2 Compose的优势1.3 如何在项目中使用Compose 二、Compose中的状态管理2.1 状态管理的重要性2.2 Compose中的状态和数据流2.3 使用State和MutableState处理状态2.4 通过ViewModel进行状态管理 三、Compose中的列表和滚动

Android 10.0 mtk平板camera2横屏预览旋转90度横屏拍照图片旋转90度功能实现

1.前言 在10.0的系统rom定制化开发中,在进行一些平板等默认横屏的设备开发的过程中,需要在进入camera2的 时候,默认预览图像也是需要横屏显示的,在上一篇已经实现了横屏预览功能,然后发现横屏预览后,拍照保存的图片 依然是竖屏的,所以说同样需要将图片也保存为横屏图标了,所以就需要看下mtk的camera2的相关横屏保存图片功能, 如何实现实现横屏保存图片功能 如图所示: 2.mtk

android应用中res目录说明

Android应用的res目录是一个特殊的项目,该项目里存放了Android应用所用的全部资源,包括图片、字符串、颜色、尺寸、样式等,类似于web开发中的public目录,js、css、image、style。。。。 Android按照约定,将不同的资源放在不同的文件夹中,这样可以方便的让AAPT(即Android Asset Packaging Tool , 在SDK的build-tools目

Android fill_parent、match_parent、wrap_content三者的作用及区别

这三个属性都是用来适应视图的水平或者垂直大小,以视图的内容或尺寸为基础的布局,比精确的指定视图的范围更加方便。 1、fill_parent 设置一个视图的布局为fill_parent将强制性的使视图扩展至它父元素的大小 2、match_parent 和fill_parent一样,从字面上的意思match_parent更贴切一些,于是从2.2开始,两个属性都可以使用,但2.3版本以后的建议使

Android Environment 获取的路径问题

1. 以获取 /System 路径为例 /*** Return root of the "system" partition holding the core Android OS.* Always present and mounted read-only.*/public static @NonNull File getRootDirectory() {return DIR_ANDR

Android逆向(反调,脱壳,过ssl证书脚本)

文章目录 总结 基础Android基础工具 定位关键代码页面activity定位数据包参数定位堆栈追踪 编写反调脱壳好用的脚本过ssl证书校验抓包反调的脚本打印堆栈bilibili反调的脚本 总结 暑假做了两个月的Android逆向,记录一下自己学到的东西。对于app渗透有了一些思路。 这两个月主要做的是代码分析,对于分析完后的持久化等没有学习。主要是如何反编译源码,如何找到

android系统源码12 修改默认桌面壁纸--SRO方式

1、aosp12修改默认桌面壁纸 代码路径 :frameworks\base\core\res\res\drawable-nodpi 替换成自己的图片即可,不过需要覆盖所有目录下的图片。 由于是静态修改,则需要make一下,重新编译。 2、方法二Overlay方式 由于上述方法有很大缺点,修改多了之后容易遗忘自己修改哪些文件,为此我们采用另外一种方法,使用Overlay方式。