积跬步至千里系列之七--应用程序的安装与卸载(二)

应用程序安装与卸载的第一篇主要分析了程序的入口,应用清单配置文件的浏览以及安装时的onCreate方法中主要完成的功能,在PackageInstallerActivity的onCreate方法中,主要完成的功能:
1. 从Intent对象获取Package URL、Scheme信息
2. 校验Scheme,根据Scheme不同的值进行逻辑处理。Scheme为file或者package
3. 获取ApplicationInfo对象,用于获取应用名称,报名,应用图标等信息

在onCreate方法中,调用了另外的一个重要的方法:startInstallConfirm方法,该方法的功能是显示一个确认对话框,该对话框会列出请求的权限列表;

//该字段表示否时显示权限列表 
boolean permVisible = false;
//AppSecurityPermissions 是一个组件 封装了一些列处理权限的功能
AppSecurityPermissions perms = new AppSecurityPermissions(this, mPkgInfo);
//获取与隐私相关的权限数量
final int NP = perms.getPermissionCount(AppSecurityPermissions.WHICH_PERSONAL);
//获取与设备相关的权限数量
final int ND = perms.getPermissionCount(AppSecurityPermissions.WHICH_DEVICE);

//显示权限列表的ScrollView控件
mScrollView = new CaffeinatedScrollView(this);

//当安装的应用已经存在时(更新应用时),获取是否有额外的权限请求 AppSecurityPermissions.WHICH_NEW
boolean newPermissionsFound =
                        (perms.getPermissionCount(AppSecurityPermissions.WHICH_NEW) > 0);

//将要安装的应用设置的权限请求数量大于0时,将设置的权限列表列出来;
if (NP > 0 || ND > 0) {
        permVisible = true;
        LayoutInflater inflater = (LayoutInflater)getSystemService(
                Context.LAYOUT_INFLATER_SERVICE);
        View root = inflater.inflate(R.layout.permissions_list, null);
        if (mScrollView == null) {
            mScrollView = (CaffeinatedScrollView)root.findViewById(R.id.scrollview);
        }
        //向权限列表控件中添加 私有请求权限控件
        if (NP > 0) {
            ((ViewGroup)root.findViewById(R.id.privacylist)).addView(
                    perms.getPermissionsView(AppSecurityPermissions.WHICH_PERSONAL));
        } else {
            //私有请求权限数量为0时,就将控件进行隐藏
            root.findViewById(R.id.privacylist).setVisibility(View.GONE);
        }
        //向权限列表控件中添加 涉及设备相关的权限列表控件
        if (ND > 0) {
            ((ViewGroup)root.findViewById(R.id.devicelist)).addView(
                    perms.getPermissionsView(AppSecurityPermissions.WHICH_DEVICE));
        } else {
            //涉及设备的权限数量为0时,就讲控件进行隐藏
            root.findViewById(R.id.devicelist).setVisibility(View.GONE);
        }
        adapter.addTab(tabHost.newTabSpec(TAB_ID_ALL).setIndicator(
                getText(R.string.allPerms)), root);
}
......
if (mScrollView == null) {
    // There is nothing to scroll view, so the ok button is immediately
    // set to install.
    //若未显示权限列表(应用程序未设置任何权限)则直接将按钮设置为“install"
    mOk.setText(R.string.install);
    mOkCanInstall = true;
} else {
    //如果设置了权限列表,则当滚动到权限列表末尾时,显示“install”按钮,否则显示“next"下一步
    mScrollView.setFullScrollAction(new Runnable() {
        @Override
        public void run() {
            mOk.setText(R.string.install);
            mOkCanInstall = true;
        }
    });
}

在startInstallConfirm方法中,涉及到了AppSecurityPermissions组件,另外涉及权限的还有三类:
- AppSecurityPermissions.WHICH_PERSONAL 涉及隐私的权限项
- AppsecurityPermissions.WHICH_DEVICE 涉及与设备相关的权限项
- AppSecurityPermissions.WHICH_NEW 涉及的新增的权限列表项 应用更新时如果有额外的新的权限增加会使用到该项

二、安装应用程序

尽管安装Android应用前的校验工作非常复杂,但这些工作并不是PackageInstaller的主要工作。PackageInstaller的主要任务是安装Android应用,而且是静默安装。如果不显示用于校验的窗口,PackageInstaller的安装过程可以让用户完全感觉不到,并且可以是后台异步进行。要想实现静默安装Android应用程序,首先我们应该了解PackageInstaller的安装原理。
1.PackageInstaller的安装原理:当我们浏览了权限列表后,点击安装,会出现一个进度条,提示我们正在安装。如果成功安装了应用,会显示应用已安装的提示信息。我们到PackageInstallerActivity的源码中查看源码是如何实现的,以下代码就是PackageInstallerActivity的基于事件驱动的onclick方法中的代码,我们从点击事件开始分析:

//单击“next/ok“按钮浏览权限列表
if (v == mOk) {
    //已经浏览完所有权限 “next"按钮已经变成了“ok”按钮,已经准备好可以显示安装口
    if (mOkCanInstall || mScrollView == null) {
        mInstallFlowAnalytics.setInstallButtonClicked();
        if (mSessionId != -1) {
            mInstaller.setPermissionsResult(mSessionId, true);

            // We're only confirming permissions, so we don't really know how the
            // story ends; assume success.
            mInstallFlowAnalytics.setFlowFinishedWithPackageManagerResult(
                    PackageManager.INSTALL_SUCCEEDED);
        } else {
            //构造开启安装程序的Intent对象
            // Start subactivity to actually install the application
            Intent newIntent = new Intent();
            //传递ApplicationInfo对象
            newIntent.putExtra(PackageUtil.INTENT_ATTR_APPLICATION_INFO,
                    mPkgInfo.applicationInfo);
            //设置安装包的URI
            newIntent.setData(mPackageURI);
            //将要显示的Activity类界面
            newIntent.setClass(this, InstallAppProgress.class);
            newIntent.putExtra(InstallAppProgress.EXTRA_MANIFEST_DIGEST, mPkgDigest);
            newIntent.putExtra(
                    InstallAppProgress.EXTRA_INSTALL_FLOW_ANALYTICS, mInstallFlowAnalytics);
            String installerPackageName = getIntent().getStringExtra(
                    Intent.EXTRA_INSTALLER_PACKAGE_NAME);
            if (mOriginatingURI != null) {
                newIntent.putExtra(Intent.EXTRA_ORIGINATING_URI, mOriginatingURI);
            }
            if (mReferrerURI != null) {
                newIntent.putExtra(Intent.EXTRA_REFERRER, mReferrerURI);
            }
            if (mOriginatingUid != VerificationParams.NO_UID) {
                newIntent.putExtra(Intent.EXTRA_ORIGINATING_UID, mOriginatingUid);
            }
            if (installerPackageName != null) {
                newIntent.putExtra(Intent.EXTRA_INSTALLER_PACKAGE_NAME,
                        installerPackageName);
            }
            if (getIntent().getBooleanExtra(Intent.EXTRA_RETURN_RESULT, false)) {
                newIntent.putExtra(Intent.EXTRA_RETURN_RESULT, true);
                newIntent.addFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT);
            }
            if(localLOGV) Log.i(TAG, "downloaded app uri="+mPackageURI);
            //启动安装窗口界面
            startActivity(newIntent);
        }
        finish();
    } else {//下一步按钮显示后面的权限列表
        mScrollView.pageScroll(View.FOCUS_DOWN);
    }
} else if(v == mCancel) {//点击取消按钮
    // Cancel and finish
    setResult(RESULT_CANCELED);
    if (mSessionId != -1) {
        mInstaller.setPermissionsResult(mSessionId, false);
    }
    mInstallFlowAnalytics.setFlowFinished(
            InstallFlowAnalytics.RESULT_CANCELLED_BY_USER);
    finish();
}

上述方法就是事件驱动型的代码,只是事件的分类逻辑处理并不困难,最重要的就当是构造Intent启动安装窗口对象。我们要进一步分析,下面就要看InstallAppProgress类中做的如何处理的:
同样的InstallAppProgress位于apps/PackageInstaller/src/com/android/packageinstaller/InstallAppProgress.java路径下,onCreate方法中主要就是获取在PackageInstallerActivity的点击方法中构造的intent对象,然后获取传递的构造的ApplicationInfo对象,将要安装的应用包的URI,获取将要安装的应用包的Scheme的具体对应的值(file或者package),最后调用初始化视图操作initview。主要分析initView方法如下:

......
//如果将要安装的应用已经安装,会返回PackageInfo对象,否则返回null
PackageInfo pi = pm.getPackageInfo(mAppInfo.packageName, 
                    PackageManager.GET_UNINSTALLED_PACKAGES);

if(pi != null) {
    //如果应用已安装, 则设置应用的安装模式为更新
    installFlags |= PackageManager.INSTALL_REPLACE_EXISTING;
}
......
//如果scheme为package,则意味着是更新程序
if ("package".equals(mPackageURI.getScheme())) {
    as = new PackageUtil.AppSnippet(pm.getApplicationLabel(mAppInfo),
            pm.getApplicationIcon(mAppInfo));
} else {
    //否则scheme为file,则通过APK文件的路径获取显示Android应用相关信息的视图
    final File sourceFile = new File(mPackageURI.getPath());
    as = PackageUtil.getAppSnippet(this, mAppInfo, sourceFile);
}
......
//获取安装应用包的包名
String installerPackageName = getIntent().getStringExtra(Intent.EXTRA_INSTALLER_PACKAGE_NAME);
Uri originatingURI = getIntent().getParcelableExtra(Intent.EXTRA_ORIGINATING_URI);
Uri referrer = getIntent().getParcelableExtra(Intent.EXTRA_REFERRER);
//如上的两个Uri,对于普通的Anroid应用来说为null。

if ("package".equals(mPackageURI.getScheme())) {
    try {
        //scheme为package时,调用更新方法更新程序
        pm.installExistingPackage(mAppInfo.packageName);
        observer.packageInstalled(mAppInfo.packageName,
                PackageManager.INSTALL_SUCCEEDED);
    } catch (PackageManager.NameNotFoundException e) {
        observer.packageInstalled(mAppInfo.packageName,
                PackageManager.INSTALL_FAILED_INVALID_APK);
    }
} else {
    //scheme为file时,调用安装方法
    pm.installPackageWithVerificationAndEncryption(mPackageURI, observer, installFlags,
            installerPackageName, verificationParams, null);
}

initView方法一如既往的还是获取将要安装程序的一些信息,然后进行做了一些操作,该方法最重要的操作和调用发生在最后,如果scheme为package,则调用pm.installExistingPackage()方法更新应用,反之如果scheme为file,则调用pm.installPackageWithVerificationAndEncryption()方法进行应用的安装。上面说到的两个方法都是PakcageManager中的方法,两个方法都是静默安装,在安装的时候不会出现任何的提示。但是由于上述两个方法在PackageManager中是被注释为@hide的,所以,普通的Android应用中无法调用和访问。前面说过静默安装是一个异步过程,所以,无论安装成功或者安装失败,都会向用户弹出结果,所以我们在调用方法中看到有个行参是observer,为PackageInstallObserver实例。PackageInstallObserver主要就是处理安装的结果,其中定义了Handler变量mHandler来进行UI操作,用于实现对用户提示。
总结:从技术上来说,实现静默安装Android应用就是调用PackageManager.installPackageWithVerificationAndEncryption方法即可。该方法的参数较多,最重要的是需要一个异步安装结果监听器用于处理安装结果。此监听器必须是IPackageInstallObserver.Stub的子类。
最后还要说明的是:PackageInstaller安装应用程序实现安装应用的功能是需要申请权限:android.permission.INSTALL_PACKAGES.该权限属于系统级别的权限,在普通的应用中无法使用。

三、卸载Android应用

卸载Android应用与安装Android应用相似,也是调用相应的系统提供的相关API,这些API都不能在普通的Android应用中使用。使用安装或者卸载此类系统级别的API可以实现静默安装或者静默卸载Android应用,用户不会得到提示或者通知,但是必须是Android系统应用才可以使用这些API。和安装应用时一样,在卸载应用时,PackageInstaller会首先弹出卸载确认提示框,用户点击确认后,再显示卸载进度提示框,最后显示卸载结果。在分析卸载时,我们就依着分析安装Android应用的步骤,进行简单的核心代码的查看分析:
- 卸载前的确认。UnInstallerActivity.java
卸载前的确认框是一个Dialog,在5.1.1源码中为showDialogFragment(new UninstallAlertDialogFragment());
在UninstallAlertFragment中获取要卸载的应用的名字,图标等信息,然后设置title,icon,PositiveButton和NegativeButton事件等。PositiveButton的点击事件会触发UnInstallerActivity中的startUninstallProgress()方法,如下:

//构造要卸载的应用的Intent对象
Intent newIntent = new Intent(Intent.ACTION_VIEW);
newIntent.putExtra(Intent.EXTRA_USER, mDialogInfo.user);
newIntent.putExtra(Intent.EXTRA_UNINSTALL_ALL_USERS, mDialogInfo.allUsers);
newIntent.putExtra(PackageInstaller.EXTRA_CALLBACK, mDialogInfo.callback);
newIntent.putExtra(PackageUtil.INTENT_ATTR_APPLICATION_INFO, mDialogInfo.appInfo);
if (getIntent().getBooleanExtra(Intent.EXTRA_RETURN_RESULT, false)) {
    newIntent.putExtra(Intent.EXTRA_RETURN_RESULT, true);
    newIntent.addFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT);
}
newIntent.setClass(this, UninstallAppProgress.class);
startActivity(newIntent);
  • UnistallAppProgress类,展示卸载进度界面
    与InstallAppProgress类类似的,onCreate方法中获取了传递的Intent对象中的ApplicationInfo对象以及获取是否删除所有用户数据的标志,重要的卸载方法调用还是在initView方法中,核心调用如下:
IPackageManager packageManager =
                IPackageManager.Stub.asInterface(ServiceManager.getService("package"));
//创建卸载进度监听器
PackageDeleteObserver observer = new PackageDeleteObserver();
try {
    // 静默卸载Android应用 
    packageManager.deletePackageAsUser(mAppInfo.packageName, observer,
            mUser.getIdentifier(),
            mAllUsers ? PackageManager.DELETE_ALL_USERS : 0);
} catch (RemoteException e) {
    // Shouldn't happen.
    Log.e(TAG, "Failed to talk to package manager", e);
}

同样的,deletePackageAsUser是PackageManager类中标注为@hide的方法,在普通的应用中无法访问和调用,需要是系统应用才能调用。另外,PackageDeleteObserver卸载监听器需要继承IPackageDeleteObserver.Stub,这个也是使用了Handler的变量mHandler来处理卸载结果,卸载成功或者卸载失败的UI操作由此mHandler变量来实现。当然和安装应用时一样,需要系统级别的权限申请:android.permission.DELETE_PACKAGES

四、总结

  1. 普通的Android应用程序不允许静默安装和静默卸载程序,如果想要实现静默安装或者静默卸载需要将Android应用变成系统应用(进行系统签名)
  2. 在技术实现上,静默安装调用的是PackageManager类中的installPackageWithVerificationAndEncryption方法,然后通过一个安装监听器来处理安装结果,Handler变量根据安装结果处理UI显示;静默卸载调用的是PackageManager类中的deletePackageAsUser方法,通过设置一个卸载监听器监听卸载结果,Handler变量根据卸载结果处理UI显示。
  3. 静默安装和静默卸载的方法都是标注为@hide的方法,普通应用中无法访问和调用
  4. 安装应用需要使用系统级别的android.permission.INSTALL_PACAGES权限;
    卸载应用需要使用系统级别的android.permission.DELETE_PACKAGES权限。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值