Android 6.0 動態權限(二 - 終結章 )------ 關於動態權限的正確使用與理解

關於上一章節提到的 23.06ADT和6.0的SDK 資源,現在馬上貼出來

鏈接:http://pan.baidu.com/s/1dEO5eb3 密碼:kkwr

鏈接:http://pan.baidu.com/s/1bOBhDk 密碼:nsv5

如果鏈接不存在,或者資源有問題,請在本博留言,勿發私信,謝謝!大笑

在本篇開篇前先大致瞭解一下 6.0 動態權限的大致過程,廢話不多說,linux打開源碼,開始探究

1.首先,比如我們需要手動授權的時候,怎麼去到對應包名的那個設置的界面呢?

我這裏以 EasyPermissions 的跳轉設置爲例

 

		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);
			}
		});

其中 Settings.ACTION_APPLICATION_DETAILS_SETTINGS 來自 Settings 的一個靜態的字符串類型,這個就不多說了,可以看到該行的下一行是將自己的包名打包通過uri以Intent的形式發送出去了,那到底誰註冊了 Settings.ACTION_APPLICATION_DETAILS_SETTINGS 這個action呢?我們接着往下看

 

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.查看接收包名的 action 

 

9.查看 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 求助,發現沒有什麼卵用,全都沒答到點上,一點幫助都沒有。。。

 

地址還在:https://segmentfault.com/q/1010000007930966 就連神奇的 stackoverflow 也沒有找到相關的資料,有一個類似的提問,但是回答也不令人滿意,最後

還是自己琢磨,把所有可能涉及的問題一個一個的過,首先 Fragment 既然沒有隨應用一起gc,那我就去掉 Fragment ,於是我把項目所有的 Fragment 全部去掉

了!採用自定義的view來取代 Fragment ,然後去掉了所有不相關的且無關緊要或沒有被gc的 static 字段修飾的變量,並且把靜態的 ActivityStack 堆棧管理寫到了

自定義的 application 下,再次走那樣的不正常的流程的時候,我發現問題就已經被我解決掉了!真是坑爹啊!花了2天的時間去研究源碼,還是有些作用的!哈哈...

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章