\packages\apps\PackageInstaller
一、一条真实的修改记录
TVOS基于的是一套板卡厂商原有的源码(mstar android8.0版本)原生的这个app安装界面,存在俩个比较严重的用户体验问题,
1’、下面那俩按钮太小了,而且都在右下角干嘛,这么大的屏幕往中间来啊
2、每次安装的时候遥控器的焦点不在安装按钮和完成按钮上,而是在权限列表上,用户还要自己点下去
很好解决,找到布局然后修改布局就可,
install_installing
install_staging
install_failed
install_confirm_perm
install_confirm_perm_update
install_success
install_confirm
然后我就把按钮改大一点,这几个布局都要改哦
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"android:layout_width="match_parent"android:layout_height="match_parent"android:orientation="vertical"><LinearLayout android:id="@+id/app_snippet"android:background="?android:attr/colorPrimary"android:layout_width="match_parent"android:layout_height="?android:attr/actionBarSize"android:orientation="horizontal"android:elevation="@dimen/headerElevation"android:gravity="center_vertical"><ImageViewandroid:id="@+id/app_icon"android:layout_width="24dp"android:layout_height="24dp"android:layout_marginStart="16dp"android:scaleType="fitCenter" /><TextViewandroid:id="@+id/app_name"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_marginStart="32dp"android:layout_marginEnd="16dp"android:ellipsize="end"android:singleLine="true"android:textAppearance="?android:attr/titleTextStyle" /></LinearLayout><LinearLayoutandroid:layout_width="match_parent"android:layout_height="wrap_content"android:layout_weight="1"android:gravity="center"android:orientation="vertical"android:paddingLeft="16dip"android:paddingRight="16dip"><ImageViewandroid:layout_width="92dp"android:layout_height="92dp"android:layout_marginBottom="12dp"android:contentDescription="@null"android:tint="@color/bigIconColor"android:src="@drawable/ic_file_download" /><ProgressBarandroid:id="@+id/progress_bar"style="?android:attr/progressBarStyleHorizontal"android:layout_width="250dp"android:layout_height="wrap_content"android:indeterminate="false" /><TextViewandroid:id="@+id/center_text"android:layout_width="wrap_content"android:layout_height="wrap_content"android:gravity="center_horizontal"android:text="@string/installing"android:textAppearance="?android:attr/textAppearanceMedium" /></LinearLayout><LinearLayoutandroid:id="@+id/buttons_panel"style="?android:attr/buttonBarStyle"android:layout_width="match_parent"android:layout_height="wrap_content"android:measureWithLargestChild="true"android:orientation="horizontal"android:gravity="center"android:padding="8dip"><!-- <Viewandroid:layout_width="0dp"android:layout_height="0dp"android:layout_weight="1" />--><Buttonandroid:id="@+id/cancel_button"style="?android:attr/buttonBarButtonStyle"android:layout_width="320dp"android:layout_height="wrap_content"android:maxLines="2"android:text="@string/cancel" /></LinearLayout></LinearLayout>
\packages\apps\PackageInstaller\src\com\android\packageinstaller\PackageInstallerActivity.java
让他每次进入都获取焦点,完事
二、然后呢!不妨一起来看一下PackageInstaller
调用安装界面
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setDataAndType(xx, "application/vnd.android.package-archive");
没错,就是type--》"application/vnd.android.package-archive"
packages\apps\PackageInstaller\src\com\android\packageinstaller\InstallStart
Intent nextActivity = new Intent(intent);nextActivity.setFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT);// The the installation source as the nextActivity thinks this activity is the source, hence// set the originating UID and sourceInfo explicitlynextActivity.putExtra(PackageInstallerActivity.EXTRA_CALLING_PACKAGE, callingPackage);nextActivity.putExtra(PackageInstallerActivity.EXTRA_ORIGINAL_SOURCE_INFO, sourceInfo);nextActivity.putExtra(Intent.EXTRA_ORIGINATING_UID, originatingUid);if (PackageInstaller.ACTION_CONFIRM_PERMISSIONS.equals(intent.getAction())) {nextActivity.setClass(this, PackageInstallerActivity.class);} else {Uri packageUri = intent.getData();if (packageUri == null) {//uri是空的就退出了// if there's nothing to do, quietly slip into the etherIntent result = new Intent();result.putExtra(Intent.EXTRA_INSTALL_RESULT,PackageManager.INSTALL_FAILED_INVALID_URI);setResult(RESULT_FIRST_USER, result);nextActivity = null;} else {//判断Uri的Scheme,如果是就跳转到InstallStagingif (packageUri.getScheme().equals(SCHEME_CONTENT)) {nextActivity.setClass(this, InstallStaging.class);} else {nextActivity.setClass(this, PackageInstallerActivity.class);}}}if (nextActivity != null) {startActivity(nextActivity);}finish();
跳转根据uri的类型跳转吧,InstallStaging与PackageInstallerActivity
7.0及以后的系统,file://ur就会报FileUriException异常,所以需要使用FileContentProvider来将file://uri替换成content://uri,
packageUri.getScheme().equals(ContentResolver.SCHEME_CONTENT))就是这样的一个判断条件,7.0以上走InstallStaging。
看看InstallStaging ,来看下onresume
@Overrideprotected void onResume() {super.onResume();// This is the first onResume in a single life of the activityif (mStagingTask == null) {// File does not exist, or became invalidif (mStagedFile == null) {// Create file delayed to be able to show errortry {mStagedFile = TemporaryFileManager.getStagedFile(this);} catch (IOException e) {showError();return;}}mStagingTask = new StagingAsyncTask();mStagingTask.execute(getIntent().getData());}}
哦吼,你会发现起了个stagintask,把uri直接传进去了
private final class StagingAsyncTask extends AsyncTask<Uri, Void, Boolean> {@Overrideprotected Boolean doInBackground(Uri... params) {//取,存-----缓存安装包文件----到mStagedFileif (params == null || params.length <= 0) {return false;}Uri packageUri = params[0];try (InputStream in = getContentResolver().openInputStream(packageUri)) {// Despite the comments in ContentResolver#openInputStream the returned stream can// be null.if (in == null) {return false;}try (OutputStream out = new FileOutputStream(mStagedFile)) {byte[] buffer = new byte[4096];int bytesRead;while ((bytesRead = in.read(buffer)) >= 0) {// Be nice and respond to a cancellationif (isCancelled()) {return false;}out.write(buffer, 0, bytesRead);}}} catch (IOException | SecurityException e) {Log.w(LOG_TAG, "Error staging apk from content URI", e);return false;}return true;}@Overrideprotected void onPostExecute(Boolean success) {if (success) {// Now start the installation again from a file
//哦吼,又跳转了!!!!!!!还是PackageInstallerActivityIntent installIntent = new Intent(getIntent());installIntent.setClass(InstallStaging.this, PackageInstallerActivity.class);installIntent.setData(Uri.fromFile(mStagedFile));installIntent.setFlags(installIntent.getFlags() & ~Intent.FLAG_ACTIVITY_FORWARD_RESULT);installIntent.addFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION);startActivityForResult(installIntent, 0);} else {showError();}}}
将content://uri 转成了file://uri,又跳转了PackageInstallerActivity
PackageInstallerActivity oncrate
bindUi(R.layout.install_confirm, false);checkIfAllowedAndInitiateInstall();
checkIfAllowedAndInitiateInstall检查是不是已经允许了安装位置源应用
然后initiateInstall
然后startInstallConfirm
private void startInstallConfirm() {// We might need to show permissions, load layout with permissionsif (mAppInfo != null) {//加载app更新布局bindUi(R.layout.install_confirm_perm_update, true);} else {//加载确认安装布局bindUi(R.layout.install_confirm_perm, true);}((TextView) findViewById(R.id.install_confirm_question)).setText(R.string.install_confirm_question);TabHost tabHost = (TabHost)findViewById(android.R.id.tabhost);tabHost.setup();ViewPager viewPager = (ViewPager)findViewById(R.id.pager);TabsAdapter adapter = new TabsAdapter(this, tabHost, viewPager);// If the app supports runtime permissions the new permissions will// be requested at runtime, hence we do not show them at install.boolean supportsRuntimePermissions = mPkgInfo.applicationInfo.targetSdkVersion>= Build.VERSION_CODES.M;boolean permVisible = false;mScrollView = null;mOkCanInstall = false;int msg = 0;//加载需要的权限,显示到布局中mScrollViewAppSecurityPermissions perms = new AppSecurityPermissions(this, mPkgInfo);final int N = perms.getPermissionCount(AppSecurityPermissions.WHICH_ALL);if (mAppInfo != null) {msg = (mAppInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0? R.string.install_confirm_question_update_system: R.string.install_confirm_question_update;mScrollView = new CaffeinatedScrollView(this);mScrollView.setFillViewport(true);boolean newPermissionsFound = false;if (!supportsRuntimePermissions) {newPermissionsFound =(perms.getPermissionCount(AppSecurityPermissions.WHICH_NEW) > 0);if (newPermissionsFound) {permVisible = true;mScrollView.addView(perms.getPermissionsView(AppSecurityPermissions.WHICH_NEW));}}if (!supportsRuntimePermissions && !newPermissionsFound) {LayoutInflater inflater = (LayoutInflater)getSystemService(Context.LAYOUT_INFLATER_SERVICE);TextView label = (TextView)inflater.inflate(R.layout.label, null);label.setText(R.string.no_new_perms);mScrollView.addView(label);}adapter.addTab(tabHost.newTabSpec(TAB_ID_NEW).setIndicator(getText(R.string.newPerms)), mScrollView);}if (!supportsRuntimePermissions && N > 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);}((ViewGroup)root.findViewById(R.id.permission_list)).addView(perms.getPermissionsView(AppSecurityPermissions.WHICH_ALL));adapter.addTab(tabHost.newTabSpec(TAB_ID_ALL).setIndicator(getText(R.string.allPerms)), root);}if (!permVisible) {if (mAppInfo != null) {// This is an update to an application, but there are no// permissions at all.msg = (mAppInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0? R.string.install_confirm_question_update_system_no_perms: R.string.install_confirm_question_update_no_perms;} else {// This is a new application with no permissions.msg = R.string.install_confirm_question_no_perms;}// We do not need to show any permissions, load layout without permissionsbindUi(R.layout.install_confirm, true);mScrollView = null;}if (msg != 0) {((TextView)findViewById(R.id.install_confirm_question)).setText(msg);}if (mScrollView == null) {// There is nothing to scroll view, so the ok button is immediately// set to install.mOk.setText(R.string.install);mOkCanInstall = true;} else {mScrollView.setFullScrollAction(new Runnable() {@Overridepublic void run() {mOk.setText(R.string.install);mOkCanInstall = true;}});}}
public void onClick(View v) {if (v == mOk) {//ok被点击,开始安装if (mOk.isEnabled()) {if (mOkCanInstall || mScrollView == null) {if (mSessionId != -1) {mInstaller.setPermissionsResult(mSessionId, true);finish();} else {startInstall();}} else {mScrollView.pageScroll(View.FOCUS_DOWN);}}} else if (v == mCancel) {// Cancel and finishsetResult(RESULT_CANCELED);if (mSessionId != -1) {mInstaller.setPermissionsResult(mSessionId, false);}finish();}}
startInstall();跳转到InstallInstalling
InstallInstalling onresunme启动InstallingAsyncTask
InstallingAsyncTask的doInBackground方法中会根据包的Uri,将APK的信息通过IO流的形式写入到PackageInstaller.Session中
/*** Send the package to the package installer and then register a event result observer that* will call {@link #launchFinishBasedOnResult(int, int, String)}*/private final class InstallingAsyncTask extends AsyncTask<Void, Void,PackageInstaller.Session> {volatile boolean isDone;@Overrideprotected PackageInstaller.Session doInBackground(Void... params) {PackageInstaller.Session session;try {session = getPackageManager().getPackageInstaller().openSession(mSessionId);} catch (IOException e) {return null;}session.setStagingProgress(0);try {File file = new File(mPackageURI.getPath());try (InputStream in = new FileInputStream(file)) {long sizeBytes = file.length();try (OutputStream out = session.openWrite("PackageInstaller", 0, sizeBytes)) {byte[] buffer = new byte[4096];while (true) {int numRead = in.read(buffer);if (numRead == -1) {session.fsync(out);break;}if (isCancelled()) {session.close();break;}out.write(buffer, 0, numRead);if (sizeBytes > 0) {float fraction = ((float) numRead / (float) sizeBytes);session.addProgress(fraction);}}}}return session;} catch (IOException | SecurityException e) {Log.e(LOG_TAG, "Could not write package", e);session.close();return null;} finally {synchronized (this) {isDone = true;notifyAll();}}}@Overrideprotected void onPostExecute(PackageInstaller.Session session) {if (session != null) {Intent broadcastIntent = new Intent(BROADCAST_ACTION);broadcastIntent.setPackage(getPackageManager().getPermissionControllerPackageName());broadcastIntent.putExtra(EventResultPersister.EXTRA_ID, mInstallId);PendingIntent pendingIntent = PendingIntent.getBroadcast(InstallInstalling.this,mInstallId,broadcastIntent,PendingIntent.FLAG_UPDATE_CURRENT);session.commit(pendingIntent.getIntentSender());mCancelButton.setEnabled(false);setFinishOnTouchOutside(false);} else {getPackageManager().getPackageInstaller().abandonSession(mSessionId);if (!isCancelled()) {launchFailure(PackageManager.INSTALL_FAILED_INVALID_APK, null);}}}}
这里出现了一个 session = getPackageManager().getPackageInstaller().openSession(mSessionId);调用PackageInstaller.Session的commit方法,将APK的信息交由PMS处理。
也就是说安装的逻辑就跑到PM那里去咯 frameworks/base/core/java/android/content/pm/PackageInstaller.java
然后呢结果通过InstallEventReceiver监听安装结果,这样基本上就完成了。所以PackageInstaller想要知道是如何安装的,还要去看pm。