Android 6.0 動態許可權實戰(二
關於上一章節提到的 23.06ADT和6.0的SDK 資源,現在馬上貼出來
如果連結不存在,或者資源有問題,請在本博留言,勿發私信,謝謝!
在本篇開篇前先大致瞭解一下 6.0 動態許可權的大致過程,廢話不多說,linux開啟原始碼,開始探究
1.首先,比如我們需要手動授權的時候,怎麼去到對應包名的那個設定的介面呢?
我這裡以 EasyPermissions 的跳轉設定為例
其中 Settings.ACTION_APPLICATION_DETAILS_SETTINGS 來自 Settings 的一個靜態的字串型別,這個就不多說了,可以看到該行的下一行是將自己的包名打包通過uri以Intent的形式傳送出去了,那到底誰註冊了 Settings.ACTION_APPLICATION_DETAILS_SETTINGS 這個action呢?我們接著往下看dialogBuilder.setPositiveButton(positiveButtonText, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { // Create app settings intent Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS); Uri uri = Uri.fromParts("package", context.getPackageName(), null); intent.setData(uri); // Start for result startForResult(activityOrFragment, intent, settingsRequestCode); } });
2.Settings.ACTION_APPLICATION_DETAILS_SETTINGS 的註冊頁面
3.找到對應的靜態常量對應的action
4.找到對應action的註冊檔案
5.InstalledAppDetails extends AppInfoBase,AppInfoBase extends SettingsPreferenceFragment,SettingsPreferenceFragment extends InstrumentedPreferenceFragment , InstrumentedFragment extends PreferenceFragment
PreferenceFragment 來自 frameworks\base\core\java\android\preference 目錄
abstract class PreferenceFragment extends Fragment implements PreferenceManager.OnPreferenceTreeClickListener 是一個抽象類
6.檢視 InstalledAppDetails
public void onActivityCreated(Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); if (mFinishing) { return; } handleHeader(); mNotificationPreference = findPreference(KEY_NOTIFICATION); mNotificationPreference.setOnPreferenceClickListener(this); mStoragePreference = findPreference(KEY_STORAGE); mStoragePreference.setOnPreferenceClickListener(this); // 在這裡新增對應的 PermissionsPreference mPermissionsPreference = findPreference(KEY_PERMISSION); // 在這裡新增對應的 Preference 點選事件 mPermissionsPreference.setOnPreferenceClickListener(this); mDataPreference = findPreference(KEY_DATA); if (mDataPreference != null) { mDataPreference.setOnPreferenceClickListener(this); } mBatteryPreference = findPreference(KEY_BATTERY); mBatteryPreference.setEnabled(false); mBatteryPreference.setOnPreferenceClickListener(this); mMemoryPreference = findPreference(KEY_MEMORY); mMemoryPreference.setOnPreferenceClickListener(this); mLaunchPreference = findPreference(KEY_LAUNCH); if (mAppEntry != null && mAppEntry.info != null) { if ((mAppEntry.info.flags&ApplicationInfo.FLAG_INSTALLED) == 0 || !mAppEntry.info.enabled) { mLaunchPreference.setEnabled(false); } else { mLaunchPreference.setOnPreferenceClickListener(this); } } else { mLaunchPreference.setEnabled(false); } }
7.PermissionsPreference 點選事件的響應
@Override
public boolean onPreferenceClick(Preference preference) {
if (preference == mStoragePreference) {
startAppInfoFragment(AppStorageSettings.class, mStoragePreference.getTitle());
} else if (preference == mNotificationPreference) {
startAppInfoFragment(AppNotificationSettings.class,
getString(R.string.app_notifications_title));
// 這裡為點選許可權 Preference 的響應
} else if (preference == mPermissionsPreference) {
startManagePermissionsActivity();
} else if (preference == mLaunchPreference) {
startAppInfoFragment(AppLaunchSettings.class, mLaunchPreference.getTitle());
} else if (preference == mMemoryPreference) {
ProcessStatsBase.launchMemoryDetail((SettingsActivity) getActivity(),
mStatsManager.getMemInfo(), mStats);
} else if (preference == mDataPreference) {
Bundle args = new Bundle();
args.putString(DataUsageSummary.EXTRA_SHOW_APP_IMMEDIATE_PKG,
mAppEntry.info.packageName);
SettingsActivity sa = (SettingsActivity) getActivity();
sa.startPreferencePanel(DataUsageSummary.class.getName(), args, -1,
getString(R.string.app_data_usage), this, SUB_INFO_FRAGMENT);
} else if (preference == mBatteryPreference) {
BatteryEntry entry = new BatteryEntry(getActivity(), null, mUserManager, mSipper);
PowerUsageDetail.startBatteryDetailPage((SettingsActivity) getActivity(),
mBatteryHelper, BatteryStats.STATS_SINCE_CHARGED, entry, true);
} else {
return false;
}
return true;
}
8.PermissionsPreference 點選事件響應的執行方法
private void startManagePermissionsActivity() {
// start new activity to manage app permissions
Intent intent = new Intent(Intent.ACTION_MANAGE_APP_PERMISSIONS);
intent.putExtra(Intent.EXTRA_PACKAGE_NAME, mAppEntry.info.packageName);
intent.putExtra(AppInfoWithHeader.EXTRA_HIDE_INFO_BUTTON, true);
try {
startActivity(intent);
} catch (ActivityNotFoundException e) {
Log.w(LOG_TAG, "No app can handle android.intent.action.MANAGE_APP_PERMISSIONS");
}
}
由上述程式碼可知,該函式將包名打包到intent中傳送給了另一個頁面處理,所有許可權開關操作實際上 settings 只是充當了一個代理
8.檢視接收包名的 action9.檢視 action 的註冊
10.檢視該頁面的程式碼實現
/*
* Copyright (C) 2015 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.packageinstaller.permission.ui;
import android.app.Fragment;
import android.content.Intent;
import android.os.Bundle;
import android.util.Log;
public final class ManagePermissionsActivity extends OverlayTouchActivity {
private static final String LOG_TAG = "ManagePermissionsActivity";
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (savedInstanceState != null) {
return;
}
// 這裡定義一個 Fragment 物件
Fragment fragment;
// 接收intent的action
String action = getIntent().getAction();
// 根據action進行fragment的例項化
switch (action) {
case Intent.ACTION_MANAGE_PERMISSIONS: {
fragment = ManagePermissionsFragment.newInstance();
} break;
// 此處為我們要找的邏輯
case Intent.ACTION_MANAGE_APP_PERMISSIONS: {
String packageName = getIntent().getStringExtra(Intent.EXTRA_PACKAGE_NAME);
if (packageName == null) {
Log.i(LOG_TAG, "Missing mandatory argument EXTRA_PACKAGE_NAME");
finish();
return;
}
fragment = AppPermissionsFragment.newInstance(packageName);
} break;
case Intent.ACTION_MANAGE_PERMISSION_APPS: {
String permissionName = getIntent().getStringExtra(Intent.EXTRA_PERMISSION_NAME);
if (permissionName == null) {
Log.i(LOG_TAG, "Missing mandatory argument EXTRA_PERMISSION_NAME");
finish();
return;
}
fragment = PermissionAppsFragment.newInstance(permissionName);
} break;
default: {
Log.w(LOG_TAG, "Unrecognized action " + action);
finish();
return;
}
}
getFragmentManager().beginTransaction().replace(android.R.id.content, fragment).commit();
}
}
11.檢視 AppPermissionsFragment 的靜態函式newInstance
public static AppPermissionsFragment newInstance(String packageName) { return setPackageName(new AppPermissionsFragment(), packageName); } private static <T extends Fragment> T setPackageName(T fragment, String packageName) { Bundle arguments = new Bundle(); arguments.putString(Intent.EXTRA_PACKAGE_NAME, packageName); // 將包名以 Bundle String 鍵值對儲存,並呼叫setArguments fragment.setArguments(arguments); return fragment; } @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setLoading(true /* loading */, false /* animate */); setHasOptionsMenu(true); final ActionBar ab = getActivity().getActionBar(); if (ab != null) { ab.setDisplayHomeAsUpEnabled(true); } // getArguments() 從setArguments 函式取出 Bundle String 的包名鍵值對 String packageName = getArguments().getString(Intent.EXTRA_PACKAGE_NAME); Activity activity = getActivity(); // 根據包名獲取 PackageInfo PackageInfo packageInfo = getPackageInfo(activity, packageName); if (packageInfo == null) { Toast.makeText(activity, R.string.app_not_found_dlg_title, Toast.LENGTH_LONG).show(); activity.finish(); return; } // 傳入 PackageInfo 進行許可權相關操作 mAppPermissions = new AppPermissions(activity, packageInfo, null, true, new Runnable() { @Override public void run() { getActivity().finish(); } }); // 初始化GUI loadPreferences(); }
12.AppPermissions 建構函式
// AppPermissions 建構函式
public AppPermissions(Context context, PackageInfo packageInfo, String[] permissions,
boolean sortGroups, Runnable onErrorCallback) {
mContext = context;
mPackageInfo = packageInfo;
mFilterPermissions = permissions;
// 載入 app 的lable 也就是你的app在桌面的名字
mAppLabel = loadEllipsizedAppLabel(context, packageInfo);
mSortGroups = sortGroups;
mOnErrorCallback = onErrorCallback;
// 載入許可權組
loadPermissionGroups();
}
// 載入許可權組
private void loadPermissionGroups() {
// 當前物件初次呼叫例項化,清空 ArrayList<AppPermissionGroup> mGroups 物件的資料
mGroups.clear();
// 當 PackageInfo 為 null 不再往下執行
if (mPackageInfo.requestedPermissions == null) {
return;
}
// mFilterPermissions 在本地物件第一次例項化,是預設為 null的,因為引數來自 AppPermissions 且 null,所以會走 else 的邏輯
if (mFilterPermissions != null) {
for (String filterPermission : mFilterPermissions) {
for (String requestedPerm : mPackageInfo.requestedPermissions) {
if (!filterPermission.equals(requestedPerm)) {
continue;
}
if (hasGroupForPermission(requestedPerm)) {
break;
}
AppPermissionGroup group = AppPermissionGroup.create(mContext,
mPackageInfo, requestedPerm);
if (group == null) {
break;
}
mGroups.add(group);
break;
}
}
} else {
// 遍歷 app 的請求許可權列表
for (String requestedPerm : mPackageInfo.requestedPermissions) {
// 如果 ArrayList<AppPermissionGroup> mGroups 物件已存在該許可權,不新增,繼續下一個
if (hasGroupForPermission(requestedPerm)) {
continue;
}
AppPermissionGroup group = AppPermissionGroup.create(mContext,
mPackageInfo, requestedPerm);
// 若 group 為 null ,繼續下一個
if (group == null) {
continue;
}
mGroups.add(group);
}
}
// 預設許可權排序
if (mSortGroups) {
Collections.sort(mGroups);
}
// 清空許可權組的 鍵值名稱,如:電話或儲存空間
mNameToGroupMap.clear();
// 變數新的app所有的請求許可權組,新增至 mNameToGroupMap
for (AppPermissionGroup group : mGroups) {
mNameToGroupMap.put(group.getName(), group);
}
}
private boolean hasGroupForPermission(String permission) {
for (AppPermissionGroup group : mGroups) {
if (group.hasPermission(permission)) {
return true;
}
}
return false;
}
13.AppPermissionsFragment 的UI初始化
@Override
public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
if (mAppPermissions != null) {
bindUi(this, mAppPermissions.getPackageInfo());
}
}
private static void bindUi(SettingsWithHeader fragment, PackageInfo packageInfo) {
Activity activity = fragment.getActivity();
PackageManager pm = activity.getPackageManager();
ApplicationInfo appInfo = packageInfo.applicationInfo;
Intent infoIntent = null;
if (!activity.getIntent().getBooleanExtra(EXTRA_HIDE_INFO_BUTTON, false)) {
infoIntent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS)
.setData(Uri.fromParts("package", packageInfo.packageName, null));
}
// 圖示
Drawable icon = appInfo.loadIcon(pm);
// app label
CharSequence label = appInfo.loadLabel(pm);
fragment.setHeader(icon, label, infoIntent);
ActionBar ab = activity.getActionBar();
if (ab != null) {
ab.setTitle(R.string.app_permissions);
}
ViewGroup rootView = (ViewGroup) fragment.getView();
ImageView iconView = (ImageView) rootView.findViewById(R.id.lb_icon);
if (iconView != null) {
iconView.setImageDrawable(icon);
}
TextView titleView = (TextView) rootView.findViewById(R.id.lb_title);
if (titleView != null) {
titleView.setText(R.string.app_permissions);
}
TextView breadcrumbView = (TextView) rootView.findViewById(R.id.lb_breadcrumb);
if (breadcrumbView != null) {
breadcrumbView.setText(label);
}
}
14.許可權改變,使用者手點的實現
@Override
public boolean onPreferenceChange(final Preference preference, Object newValue) {
// 當 Preference change 也就是許可權被使用者手動改變時回撥的對應的 Preference
String groupName = preference.getKey();
// 將 key 去AppPermissionGroup中取出
final AppPermissionGroup group = mAppPermissions.getPermissionGroup(groupName);
// 當group為null預設關
if (group == null) {
return false;
}
// 取出宿主 OverlayTouchActivity
OverlayTouchActivity activity = (OverlayTouchActivity) getActivity();
// 監聽當前許可權介面是否被覆蓋和疊加
if (activity.isObscuredTouch()) {
// 彈出一個警告框,檢測到螢幕疊加層,會彈出此框,並提示 要更改此許可權設定,您必須首先在“設定”>“應用”中關閉螢幕疊加層"
activity.showOverlayDialog();
return false;
}
// 新增一個新的許可權 group 到Togglelist中
addToggledGroup(group);
// 檢測是否添加了Manifest.permission_group.LOCATION 許可權組並且該應用為系統應用,包名 equals(ILocationManager.Stub.asInterface(
ServiceManager.getService(Context.LOCATION_SERVICE)).getNetworkProviderPackage())
if (LocationUtils.isLocationGroupAndProvider(group.getName(), group.getApp().packageName)) {
// 彈出一個警告,提示:是此裝置的一個位置資訊服務提供程式。您可以在位置資訊設定中修改位置資訊使用權
LocationUtils.showLocationDialog(getContext(), mAppPermissions.getAppLabel());
return false;
}
// Object newValue == true,使用者賦予許可權
if (newValue == Boolean.TRUE) {
/**
*
* 此處由AppPermissionGroup間接呼叫抽象類PackageManager的grantRuntimePermissions函式後面會細說
*
*/
group.grantRuntimePermissions(false);
} else {
// grantedByDefault為false 提示:此應用專為舊版 Android 打造。拒絕許可權可能會導致其無法正常執行
// grantedByDefault為true 提示:如果您拒絕此許可權,您裝置的基本功能可能會無法正常使用
final boolean grantedByDefault = group.hasGrantedByDefaultPermission();
// grantedByDefault || 當前app targetSdkVersion > Build.VERSION_CODES.LOLLIPOP_MR1 && 是否為舊版(即SDK<23),預設 false
if (grantedByDefault || (!group.hasRuntimePermission() && !mHasConfirmedRevoke)) {
new AlertDialog.Builder(getContext())
.setMessage(grantedByDefault ? R.string.system_warning
: R.string.old_sdk_deny_warning)
.setNegativeButton(R.string.cancel, null)
.setPositiveButton(R.string.grant_dialog_button_deny,
new OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
// 更新許可權開關
((SwitchPreference) preference).setChecked(false);
// 關閉許可權
/**
*
* 此處由AppPermissionGroup間接呼叫抽象類PackageManager的revokeRuntimePermissions函式後面會細說
*
*/
group.revokeRuntimePermissions(false);
// 倘若是舊版,mHasConfirmedRevoke = true
if (!grantedByDefault) {
mHasConfirmedRevoke = true;
}
}
})
.show();
return false;
} else {
group.revokeRuntimePermissions(false);
}
}
return true;
}
15.AppPermissionGroup grantRuntimePermissions() revokeRuntimePermissions() 函式具體實現
public boolean grantRuntimePermissions(boolean fixedByTheUser) {
final boolean isSharedUser = mPackageInfo.sharedUserId != null;
final int uid = mPackageInfo.applicationInfo.uid;
// We toggle permissions only to apps that support runtime
// permissions, otherwise we toggle the app op corresponding
// to the permission if the permission is granted to the app.
for (Permission permission : mPermissions.values()) {
// 具備執行時許可權的 app
if (mAppSupportsRuntimePermissions) {
// Do not touch permissions fixed by the system.
if (permission.isSystemFixed()) {
return false;
}
// Ensure the permission app op enabled before the permission grant.
if (permission.hasAppOp() && !permission.isAppOpAllowed()) {
permission.setAppOpAllowed(true);
mAppOps.setUidMode(permission.getAppOp(), uid, AppOpsManager.MODE_ALLOWED);
}
// Grant the permission if needed.
// 如果沒有授權,授予許可權
// mPackageManager 來自 Context.getPackageManager(),而 Context 是一個抽象類,具體方法實現由其子類 ContextImpl 實現
// ContextImpl 下 getPackageManager() 函式呼叫 ActivityThread.getPackageManager() 函式得到 IPackageManager 例項,倘若這個例項不為 null ,函式返回new ApplicationPackageManager(this, pm)
// ApplicationPackageManager 物件,但是ApplicationPackageManager也繼承自 PackageManager 且間接呼叫 IPackageManager ,而 IPackageManager 最終是以aidl的形式通過 IBinder 傳遞,由 PackageManagerService 繼承
// 抽象方法在該類實現
if (!permission.isGranted()) {
permission.setGranted(true);
mPackageManager.grantRuntimePermission(mPackageInfo.packageName,
permission.getName(), mUserHandle);
}
// Update the permission flags.
if (!fixedByTheUser) {
// Now the apps can ask for the permission as the user
// no longer has it fixed in a denied state.
if (permission.isUserFixed() || permission.isUserSet()) {
permission.setUserFixed(false);
permission.setUserSet(true);
mPackageManager.updatePermissionFlags(permission.getName(),
mPackageInfo.packageName,
PackageManager.FLAG_PERMISSION_USER_FIXED
| PackageManager.FLAG_PERMISSION_USER_SET,
0, mUserHandle);
}
}
} else {
// Legacy apps cannot have a not granted permission but just in case.
// Also if the permissions has no corresponding app op, then it is a
// third-party one and we do not offer toggling of such permissions.
if (!permission.isGranted() || !permission.hasAppOp()) {
continue;
}
if (!permission.isAppOpAllowed()) {
permission.setAppOpAllowed(true);
// It this is a shared user we want to enable the app op for all
// packages in the shared user to match the behavior of this
// shared user having a runtime permission.
if (isSharedUser) {
// Enable the app op.
String[] packageNames = mPackageManager.getPackagesForUid(uid);
for (String packageName : packageNames) {
mAppOps.setUidMode(permission.getAppOp(), uid,
AppOpsManager.MODE_ALLOWED);
}
} else {
// Enable the app op.
mAppOps.setUidMode(permission.getAppOp(), uid, AppOpsManager.MODE_ALLOWED);
}
// Mark that the permission should not be be granted on upgrade
// when the app begins supporting runtime permissions.
if (permission.shouldRevokeOnUpgrade()) {
permission.setRevokeOnUpgrade(false);
mPackageManager.updatePermissionFlags(permission.getName(),
mPackageInfo.packageName,
PackageManager.FLAG_PERMISSION_REVOKE_ON_UPGRADE,
0, mUserHandle);
}
// Legacy apps do not know that they have to retry access to a
// resource due to changes in runtime permissions (app ops in this
// case). Therefore, we restart them on app op change, so they
// can pick up the change.
mActivityManager.killUid(uid, KILL_REASON_APP_OP_CHANGE);
}
}
}
return true;
}
public boolean revokeRuntimePermissions(boolean fixedByTheUser) {
final boolean isSharedUser = mPackageInfo.sharedUserId != null;
final int uid = mPackageInfo.applicationInfo.uid;
// We toggle permissions only to apps that support runtime
// permissions, otherwise we toggle the app op corresponding
// to the permission if the permission is granted to the app.
for (Permission permission : mPermissions.values()) {
// 具備執行時許可權的 app
if (mAppSupportsRuntimePermissions) {
// Do not touch permissions fixed by the system.
if (permission.isSystemFixed()) {
return false;
}
// Revoke the permission if needed.
// 如果有授權,取消授予許可權
// mPackageManager 來自 Context.getPackageManager(),而 Context 是一個抽象類,具體方法實現由其子類 ContextImpl 實現
// ContextImpl 下 getPackageManager() 函式呼叫 ActivityThread.getPackageManager() 函式得到 IPackageManager 例項,倘若這個例項不為 null ,函式返回new ApplicationPackageManager(this, pm)
// ApplicationPackageManager 物件,但是ApplicationPackageManager也繼承自 PackageManager 且間接呼叫 IPackageManager ,而 IPackageManager 最終是以aidl的形式通過 IBinder 傳遞,由 PackageManagerService 繼承
// 抽象方法在該類實現
if (permission.isGranted()) {
permission.setGranted(false);
mPackageManager.revokeRuntimePermission(mPackageInfo.packageName,
permission.getName(), mUserHandle);
}
// Update the permission flags.
if (fixedByTheUser) {
// Take a note that the user fixed the permission.
if (permission.isUserSet() || !permission.isUserFixed()) {
permission.setUserSet(false);
permission.setUserFixed(true);
mPackageManager.updatePermissionFlags(permission.getName(),
mPackageInfo.packageName,
PackageManager.FLAG_PERMISSION_USER_SET
| PackageManager.FLAG_PERMISSION_USER_FIXED,
PackageManager.FLAG_PERMISSION_USER_FIXED,
mUserHandle);
}
} else {
if (!permission.isUserSet()) {
permission.setUserSet(true);
// Take a note that the user already chose once.
mPackageManager.updatePermissionFlags(permission.getName(),
mPackageInfo.packageName,
PackageManager.FLAG_PERMISSION_USER_SET,
PackageManager.FLAG_PERMISSION_USER_SET,
mUserHandle);
}
}
} else {
// Legacy apps cannot have a non-granted permission but just in case.
// Also if the permission has no corresponding app op, then it is a
// third-party one and we do not offer toggling of such permissions.
if (!permission.isGranted() || !permission.hasAppOp()) {
continue;
}
if (permission.isAppOpAllowed()) {
permission.setAppOpAllowed(false);
// It this is a shared user we want to enable the app op for all
// packages the the shared user to match the behavior of this
// shared user having a runtime permission.
if (isSharedUser) {
String[] packageNames = mPackageManager.getPackagesForUid(uid);
for (String packageName : packageNames) {
// Disable the app op.
mAppOps.setUidMode(permission.getAppOp(), uid,
AppOpsManager.MODE_IGNORED);
}
} else {
// Disable the app op.
mAppOps.setUidMode(permission.getAppOp(), uid, AppOpsManager.MODE_IGNORED);
}
// Mark that the permission should not be granted on upgrade
// when the app begins supporting runtime permissions.
if (!permission.shouldRevokeOnUpgrade()) {
permission.setRevokeOnUpgrade(true);
mPackageManager.updatePermissionFlags(permission.getName(),
mPackageInfo.packageName,
PackageManager.FLAG_PERMISSION_REVOKE_ON_UPGRADE,
PackageManager.FLAG_PERMISSION_REVOKE_ON_UPGRADE,
mUserHandle);
}
// Disabling an app op may put the app in a situation in which it
// has a handle to state it shouldn't have, so we have to kill the
// app. This matches the revoke runtime permission behavior.
mActivityManager.killUid(uid, KILL_REASON_APP_OP_CHANGE);
}
}
}
return true;
}
16.PackageManagerService grantRuntimePermissions() revokeRuntimePermissions() 函式的具體實現
/**
*
* ApplicationPackageManager
* 在介紹 PackageManagerService grantRuntimePermissions() revokeRuntimePermissions() 函式之前需要介紹如下兩個函式
* 即繼承至 PackageManager 的 ApplicationPackageManager 類,因為抽象類 Context.getPackageManager()函式由其子類ContextImpl實現,並且呼叫 ActivityThread.getPackageManager() 函式得到 IPackageManager
* 例項,當例項不為 null ,連同 ContextImpl 一起傳入建構函式new ApplicationPackageManager(this, pm)得到 PackageManager 子類物件 ApplicationPackageManager
* this 指 ContextImpl 物件,pm 為 IPackageManager 物件,最後又由 ApplicationPackageManager 物件間接呼叫和執行IPackageManager的 grantRuntimePermission() revokeRuntimePermission() 函式
* 並且取出 UserHandle 物件的一個整型值傳入,IPackageManager 的aidl物件類的子類 PackageManagerService ,這就是為什麼 PackageManagerService 最後一個函式為什麼有泛型UserHandle變成了 int 型別
* UserHandle implements Parcelable ,有點類似 Bundle 的作用,只不過 Bundle 用於上層,UserHandle 用於系統層這個樣子
*/
@Override
public void grantRuntimePermission(String packageName, String permissionName,
UserHandle user) {
try {
// mPM 為 IPackageManager 物件
mPM.grantRuntimePermission(packageName, permissionName, user.getIdentifier());
} catch (RemoteException e) {
throw new RuntimeException("Package manager has died", e);
}
}
@Override
public void revokeRuntimePermission(String packageName, String permissionName,
UserHandle user) {
try {
// mPM 為 IPackageManager 物件
mPM.revokeRuntimePermission(packageName, permissionName, user.getIdentifier());
} catch (RemoteException e) {
throw new RuntimeException("Package manager has died", e);
}
}
/**
*
* UserHandle.java
* Returns the userId stored in this UserHandle.
* @hide
*/
@SystemApi
public int getIdentifier() {
return mHandle;
}
// ++++++++++++++++++++++++++++++++++ 分割線 +++++++++++++++++++++++++++++++++++++++++++++++++
// PackageManagerService.java
@Override
public void grantRuntimePermission(String packageName, String name, final int userId) {
if (!sUserManager.exists(userId)) {
Log.e(TAG, "No such user:" + userId);
return;
}
mContext.enforceCallingOrSelfPermission(
android.Manifest.permission.GRANT_RUNTIME_PERMISSIONS,
"grantRuntimePermission");
enforceCrossUserPermission(Binder.getCallingUid(), userId, true, false,
"grantRuntimePermission");
final int uid;
final SettingBase sb;
synchronized (mPackages) {
// 檢測包
final PackageParser.Package pkg = mPackages.get(packageName);
if (pkg == null) {
throw new IllegalArgumentException("Unknown package: " + packageName);
}
// 檢測許可權
final BasePermission bp = mSettings.mPermissions.get(name);
if (bp == null) {
throw new IllegalArgumentException("Unknown permission: " + name);
}
enforceDeclaredAsUsedAndRuntimeOrDevelopmentPermission(pkg, bp);
uid = UserHandle.getUid(userId, pkg.applicationInfo.uid);
sb = (SettingBase) pkg.mExtras;
if (sb == null) {
throw new IllegalArgumentException("Unknown package: " + packageName);
}
// 許可權狀態
final PermissionsState permissionsState = sb.getPermissionsState();
// 許可權狀態標識
final int flags = permissionsState.getPermissionFlags(name, userId);
// PackageManager.FLAG_PERMISSION_SYSTEM_FIXED 為系統固定許可權
if ((flags & PackageManager.FLAG_PERMISSION_SYSTEM_FIXED) != 0) {
throw new SecurityException("Cannot grant system fixed permission: "
+ name + " for package: " + packageName);
}
if (bp.isDevelopment()) {
// Development permissions must be handled specially, since they are not
// normal runtime permissions. For now they apply to all users.
if (permissionsState.grantInstallPermission(bp) !=
PermissionsState.PERMISSION_OPERATION_FAILURE) {
scheduleWriteSettingsLocked();
}
return;
}
final int result = permissionsState.grantRuntimePermission(bp, userId);
switch (result) {
// 操作失敗
case PermissionsState.PERMISSION_OPERATION_FAILURE: {
return;
}
// 操作成功,根據當前 uid 獲取 appid 即包名
case PermissionsState.PERMISSION_OPERATION_SUCCESS_GIDS_CHANGED: {
final int appId = UserHandle.getAppId(pkg.applicationInfo.uid);
mHandler.post(new Runnable() {
@Override
public void run() {
// 將包名,uid,以及操作標誌位傳遞給 killUid 函式,實質這裡就是,為什麼使用者在設定介面取消許可權,app 莫名奇妙就掛掉了
// 後面會詳細介紹這個 killUid函式
killUid(appId, userId, KILL_APP_REASON_GIDS_CHANGED);
}
});
} break;
}
// 許可權改變監聽回撥
mOnPermissionChangeListeners.onPermissionsChanged(uid);
// Not critical if that is lost - app has to request again.
mSettings.writeRuntimePermissionsForUserLPr(userId, false);
}
// Only need to do this if user is initialized. Otherwise it's a new user
// and there are no processes running as the user yet and there's no need
// to make an expensive call to remount processes for the changed permissions.
if (READ_EXTERNAL_STORAGE.equals(name)
|| WRITE_EXTERNAL_STORAGE.equals(name)) {
final long token = Binder.clearCallingIdentity();
try {
if (sUserManager.isInitialized(userId)) {
MountServiceInternal mountServiceInternal = LocalServices.getService(
MountServiceInternal.class);
mountServiceInternal.onExternalStoragePolicyChanged(uid, packageName);
}
} finally {
Binder.restoreCallingIdentity(token);
}
}
}
@Override
public void revokeRuntimePermission(String packageName, String name, int userId) {
if (!sUserManager.exists(userId)) {
Log.e(TAG, "No such user:" + userId);
return;
}
mContext.enforceCallingOrSelfPermission(
android.Manifest.permission.REVOKE_RUNTIME_PERMISSIONS,
"revokeRuntimePermission");
enforceCrossUserPermission(Binder.getCallingUid(), userId, true, false,
"revokeRuntimePermission");
final int appId;
synchronized (mPackages) {
final PackageParser.Package pkg = mPackages.get(packageName);
// 檢測包
if (pkg == null) {
throw new IllegalArgumentException("Unknown package: " + packageName);
}
final BasePermission bp = mSettings.mPermissions.get(name);
// 檢測許可權
if (bp == null) {
throw new IllegalArgumentException("Unknown permission: " + name);
}
enforceDeclaredAsUsedAndRuntimeOrDevelopmentPermission(pkg, bp);
SettingBase sb = (SettingBase) pkg.mExtras;
if (sb == null) {
throw new IllegalArgumentException("Unknown package: " + packageName);
}
// 許可權狀態
final PermissionsState permissionsState = sb.getPermissionsState();
// 許可權狀態標識
final int flags = permissionsState.getPermissionFlags(name, userId);
if ((flags & PackageManager.FLAG_PERMISSION_SYSTEM_FIXED) != 0) {
throw new SecurityException("Cannot revoke system fixed permission: "
+ name + " for package: " + packageName);
}
if (bp.isDevelopment()) {
// Development permissions must be handled specially, since they are not
// normal runtime permissions. For now they apply to all users.
if (permissionsState.revokeInstallPermission(bp) !=
PermissionsState.PERMISSION_OPERATION_FAILURE) {
scheduleWriteSettingsLocked();
}
return;
}
// 失敗,return
if (permissionsState.revokeRuntimePermission(bp, userId) ==
PermissionsState.PERMISSION_OPERATION_FAILURE) {
return;
}
// 許可權改變監聽回撥
mOnPermissionChangeListeners.onPermissionsChanged(pkg.applicationInfo.uid);
// Critical, after this call app should never have the permission.
mSettings.writeRuntimePermissionsForUserLPr(userId, true);
appId = UserHandle.getAppId(pkg.applicationInfo.uid);
}
// 將包名,uid,以及操作標誌位傳遞給 killUid 函式,實質這裡就是,為什麼使用者在設定介面取消許可權,app 莫名奇妙就掛掉了
// 後面會詳細介紹這個 killUid函式
killUid(appId, userId, KILL_APP_REASON_PERMISSIONS_REVOKED);
}
17 killUid 函式具體實現
/**
* PackageManagerService
* 關於這個函式,官方給出的解釋如下,若翻譯不對,請見諒!
* 大意:舊版應用程式不知道由於執行時許可權(在此情況下為應用操作)的更改,他們必須重試對資源的訪問許可權。 因此,我們在應用程式更改時重新啟動它們,以便他們可以接受更改
*/
private void killUid(int appId, int userId, String reason) {
final long identity = Binder.clearCallingIdentity();
try {
// getDefault() 函式返回 IActivityManager
IActivityManager am = ActivityManagerNative.getDefault();
if (am != null) {
try {
// 由其實現類 ActivityManagerProxy killUid() 函式實現,ActivityManagerProxy 為 ActivityManagerNative 的內部類
am.killUid(appId, userId, reason);
} catch (RemoteException e) {
/* ignore - same process */
}
}
} finally {
Binder.restoreCallingIdentity(identity);
}
}
/**
* ActivityManagerNative 的內部類 ActivityManagerProxy
* killUid 函式實現
*
*/
public void killUid(int appId, int userId, String reason) throws RemoteException {
Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
data.writeInterfaceToken(IActivityManager.descriptor);
data.writeInt(appId);
data.writeInt(userId);
data.writeString(reason);
// mRemote 為 IBinder ,該 IBinder 來自 ActivityManagerProxy 建構函式,該建構函式由外部類 ActivityManagerNative asInterface(IBinder obj) 呼叫
mRemote.transact(KILL_UID_TRANSACTION, data, reply, 0);
reply.readException();
data.recycle();
reply.recycle();
}
/**
*
* ActivityManagerNative asInterface(IBinder obj) 函式
* 該靜態函式由 frameworks/base/services/core/java/com/android/server/power/ShutdownThread.java
* 呼叫
*
*/
/**
* Cast a Binder object into an activity manager interface, generating
* a proxy if needed.
*/
static public IActivityManager asInterface(IBinder obj) {
if (obj == null) {
return null;
}
IActivityManager in =
(IActivityManager)obj.queryLocalInterface(descriptor);
if (in != null) {
return in;
}
return new ActivityManagerProxy(obj);
}
/**
*
* ShutdownThread.java
*
*/
// 通過 ServiceManager checkService() 函式傳入String值,得到對應的 Service IBinder 例項
final IActivityManager am =
ActivityManagerNative.asInterface(ServiceManager.checkService("activity"));
if (am != null) {
try {
am.shutdown(MAX_BROADCAST_TIME);
} catch (RemoteException e) {
}
}
if (mRebootUpdate) {
sInstance.setRebootProgress(ACTIVITY_MANAGER_STOP_PERCENT, null);
}
/**
*
* ServiceManager.java
*
*/
/**
* Retrieve an existing service called @a name from the
* service manager. Non-blocking.
*/
public static IBinder checkService(String name) {
try {
IBinder service = sCache.get(name);
if (service != null) {
return service;
} else {
// 間接呼叫 aidl IServiceManager 的函式,而 IServiceManager 由 ServiceManagerNative 實現,ServiceManagerNative 也繼承 Binder
// ServiceManagerNative 內部類 ServiceManagerProxy 也實現 IServiceManager 類
return getIServiceManager().checkService(name);
}
} catch (RemoteException e) {
Log.e(TAG, "error in checkService", e);
return null;
}
}
/**
* Context.java
* ServiceManagerNative checkService() 函式最終呼叫的系統服務為 android.app.ActivityManager 類
* Use with {@link #getSystemService} to retrieve a
* {@link android.app.ActivityManager} for interacting with the global
* system state.
*
* @see #getSystemService
* @see android.app.ActivityManager
*/
public static final String ACTIVITY_SERVICE = "activity";
至此整個應用的一些activity等會被回收,如果回收不夠全面就會出現重引用的問題,我之前的主頁activity就是存在自定義的靜態activity堆疊裡面管理的,後來改
為自定義 application來管理,然後去掉了靜態activity的代理管理類ActivityManger類以及該專案下所有的 Fragment ,改為全自定義 view,才把這個問題從根本上
解決!
造成這個原因我個人認為,當用戶手動去設定介面取消許可權的時候(你的專案已經正常執行的情況下,且已經授權過了),系統會獲取該app的applicationinfo下的相
關信息,然後進行熱啟動,回收掉了一些不必要不可見的物件和view,但如果存在類似靜態強引用等,可能會造成gc失敗,在jvm上也不是不可能!反正最終這個
bug我是完美搞定了!
下面還分享一下關於這個問題的一些心得:
當我們的應用程式在targetSdkVersion>=23的時候就會有很多需要我們考慮的東西,比如一個應用需要在 application 下初始化一個 imageloader 或者百度地圖,但
是 google 官方提供的 v4 許可權檢測的相容庫需要這個許可權判斷新增在 Activity 或 Fragment ,而整個程式的開始初始化application的時候,介面都還沒起來,這時候
該怎麼辦呢?而邏輯恰好又需要這個操作在application中初始化,這時候我們可以採用 google 官方提供的一個目錄進行讀寫,可以不需要 Storage 許可權組,
即 android.content.Context.getExternalCacheDir() 目錄,而百度的SDK初始化需要 read_phone_state 這個許可權,但這個許可權是被動,意思就是說,當我去掉用百度
地圖的時候才會用到那個讀取電話狀態的許可權,雖然百度SDK初始化的時候也會調一次,但不會 crash 掉,也是不影響的
還有就是當app已經啟動了,使用者手動按下 home 鍵之後,回到桌面,開啟設定,進入該 app 的許可權管理介面,對該app的許可權進行手動取消和給與許可權,這時候
應用就會莫名其妙的重啟,且不會執行 onDestroy onDestroyView onDetach 類似的函式,但實質 app 已經掛掉了,很奇怪的是 app 不會被crash 掉,且沒有任何日
志,這時候如果你去做一些之前初始化的物件的相關操作,比如在 Fragment 中寫了一個下拉重新整理的操作,這時候就會報空,很不能讓人理解!按道理這個物件應
該也隨上一次使用者手動去設定介面取消授權的時候,應用莫名奇妙異常那次gc掉了啊!為什麼會這樣呢?那麼問題來了。。。
這個問題我也遇到了,而且還去了 segmentfault 求助,發現沒有什麼卵用,全都沒答到點上,一點幫助都沒有。。。
還是自己琢磨,把所有可能涉及的問題一個一個的過,首先 Fragment 既然沒有隨應用一起gc,那我就去掉 Fragment ,於是我把專案所有的 Fragment 全部去掉
了!採用自定義的view來取代 Fragment ,然後去掉了所有不相關的且無關緊要或沒有被gc的 static 欄位修飾的變數,並且把靜態的 ActivityStack 堆疊管理寫到了
自定義的 application 下,再次走那樣的不正常的流程的時候,我發現問題就已經被我解決掉了!真是坑爹啊!花了2天的時間去研究原始碼,還是有些作用的!哈哈...