真正的静默安装是不需要用户进行任何点击操作的,目前大部分博客所说的静默安装还是需要弹出确认界面,让用户点击授权安装,我认为真正的静默安装应该是不需要用户任何确定操作,就比如现在的好多应用市场安装软件才是真正的静默安装.
目前公司也想要实现和应用市场类似的安装方式,我们不难发现 像小米和华为 google都有个人的应用市场,而这写应用市场的app并不互通,比如我将小米应用市场app安装到华为手机上面就不能像小米手机里的自带的应用市场那样实现静默安装.
为了不影响正常的权限检查,我们需要对系统源码就行修改,自己添加了一安装的接口
具体修改如下:
修改packages/apps/PackageInstaller/AndroidManifest.xml
增加如下权限
<uses-permission android:name="android.permission.MANAGE_DOCUMENT" />
创建如下 receiver
<receiver android:name=".SilentInstallerReceiver" android:exported="true" >
<intent-filter>
<action android:name="xxx.intent.action.SILENT.INSTALLER" ></action>
<action android:name="xxx.intent.action.SILENT.INSTALLER.COMMIT"></action>
</intent-filter>
</receiver>
增加如下文件 packages/apps/PackageInstaller/src/com/android/packageinstaller/SilentInstallerReceiver.java
package com.android.packageinstaller;
import android.app.PendingIntent;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageInfo;
import android.content.pm.PackageInstaller;
import android.content.pm.PackageManager;
import android.content.pm.PackageParser;
import android.content.pm.PackageUserState;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Handler;
import android.os.HandlerThread;
import android.text.TextUtils;
import android.util.Log;
import com.android.internal.content.PackageHelper;
import com.android.packageinstaller.permission.utils.IoUtils;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
public class SilentInstallerReceiver extends BroadcastReceiver {
private static final String TAG = "SilentInstallerReceiver";
private static final String BROADCAST_ACTION = "xxx.intent.action.SILENT.INSTALLER.COMMIT";
private static final String PACKAGENAME = "slient_name";
private static final String INSTLLPATH = "instll_path";
private static final String SCHEME_FILE = "file";
private static final String SCHEME_CONTENT = "content";
private static final String SCHEME_PACKAGE = "package";
private Context mContext;
private Uri mPackageURI;
private Intent mLaunchIntent;
private HandlerThread mInstallThread;
private Handler mInstallHandler;
private AsyncTask<Uri, Void, File> mStagingAsynTask;
private PackageManager mPm;
private PackageInfo mPkgInfo;
@Override
public void onReceive(Context context, Intent intent) {
mContext = context;
boolean wasSetUp = false;
mInstallThread = new HandlerThread("InstallThread");
mInstallThread.start();
mInstallHandler = new Handler(mInstallThread.getLooper());
mPm = context.getPackageManager();
String instllpath = intent.getStringExtra(INSTLLPATH);
// TODO this is lunch APP
if (!BROADCAST_ACTION.equals(intent.getAction())) {
if (TextUtils.isEmpty(instllpath)) {
return;
}
mPackageURI = Uri.parse(instllpath);
if (mPackageURI.getScheme() == null) {
instllpath = "file://" + instllpath;
mPackageURI = Uri.parse(instllpath);
}
Log.d(TAG, " mPackageURI = " + mPackageURI + " , intent getAction = " + intent.getAction());
wasSetUp = processPackageUri(mPackageURI);
}
if (BROADCAST_ACTION.equals(intent.getAction())) {
Log.d(TAG, " install app , so return !!! ");
onPackageInstalled(intent);
return;
}
if (mPackageURI == null) {
Log.d(TAG, " mPackageURI is null , so return !!! ");
return;
}
if (!wasSetUp) {
Log.d(TAG, " wasSetUp is false, return!!! ");
return;
}
initiateInstall();
}
private void initiateInstall() {
String pkgName = mPkgInfo.packageName;
// Check if there is already a package on the device with this name
// but it has been renamed to something else.
String[] oldName = mPm.canonicalToCurrentPackageNames(new String[]{pkgName});
if (oldName != null && oldName.length > 0 && oldName[0] != null) {
pkgName = oldName[0];
mPkgInfo.packageName = pkgName;
mPkgInfo.applicationInfo.packageName = pkgName;
}
Log.d(TAG, " cdownloaded app uri= " + mPackageURI);
startSilentInstaller();
}
int getInstallFlags(String packageName) {
PackageManager pm = mContext.getPackageManager();
try {
PackageInfo pi =
pm.getPackageInfo(packageName, PackageManager.GET_UNINSTALLED_PACKAGES);
if (pi != null) {
return PackageManager.INSTALL_REPLACE_EXISTING;
}
} catch (PackageManager.NameNotFoundException e) {
}
return 0;
}
void startSilentInstaller() {
final PackageManager pm = mContext.getPackageManager();
final int installFlags = getInstallFlags(mPkgInfo.applicationInfo.packageName);
if ((installFlags & PackageManager.INSTALL_REPLACE_EXISTING) != 0) {
Log.w(TAG, "Replacing package:" + mPkgInfo.applicationInfo.packageName);
}
Log.d(TAG, " starSilentInstaller + packageName " + mPkgInfo.applicationInfo.packageName);
final PackageInstaller.SessionParams params = new PackageInstaller.SessionParams(
PackageInstaller.SessionParams.MODE_FULL_INSTALL);
File file = new File(mPackageURI.getPath());
try {
PackageParser.PackageLite pkg = PackageParser.parsePackageLite(file, 0);
params.setAppPackageName(pkg.packageName);
params.setInstallLocation(pkg.installLocation);
params.setSize(
PackageHelper.calculateInstalledSize(pkg, false, params.abiOverride));
} catch (PackageParser.PackageParserException e) {
Log.e(TAG, "Cannot parse package " + file + ". Assuming defaults.");
Log.e(TAG, "Cannot calculate installed size " + file + ". Try only apk size.");
params.setSize(file.length());
} catch (IOException e) {
Log.e(TAG, "Cannot calculate installed size " + file + ". Try only apk size.");
params.setSize(file.length());
}
mInstallHandler.post(new Runnable() {
@Override
public void run() {
doPackageStage(pm, params);
}
});
}
private void doPackageStage(PackageManager pm, PackageInstaller.SessionParams params) {
final PackageInstaller packageInstaller = pm.getPackageInstaller();
PackageInstaller.Session session = null;
try {
final String packageLocation = mPackageURI.getPath();
final File file = new File(packageLocation);
final int sessionId = packageInstaller.createSession(params);
final byte[] buffer = new byte[65536];
session = packageInstaller.openSession(sessionId);
final InputStream in = new FileInputStream(file);
final long sizeBytes = file.length();
final OutputStream out = session.openWrite("PackageInstaller", 0, sizeBytes);
try {
int c;
while ((c = in.read(buffer)) != -1) {
out.write(buffer, 0, c);
if (sizeBytes > 0) {
final float fraction = ((float) c / (float) sizeBytes);
session.addProgress(fraction);
}
}
session.fsync(out);
} finally {
IoUtils.closeQuietly(in);
IoUtils.closeQuietly(out);
}
// TODO this is lunch APP Broast
// Create a PendingIntent and use it to generate the IntentSender
Intent broadcastIntent = new Intent(BROADCAST_ACTION);
broadcastIntent.putExtra(PACKAGENAME, mPkgInfo.applicationInfo.packageName);
PendingIntent pendingIntent = PendingIntent.getBroadcast(
mContext /*context*/,
sessionId,
broadcastIntent,
PendingIntent.FLAG_UPDATE_CURRENT);
session.commit(pendingIntent.getIntentSender());
} catch (IOException e) {
} finally {
IoUtils.closeQuietly(session);
}
}
/**
* TODO this is lunch APP Receiver Broast
* after app installed ,open the install app
*
* @param intent get insalled app name {@param BROADCAST_ACTION}
*/
void onPackageInstalled(Intent intent) {
Log.d(TAG, " install app is finishing, start luncher install app");
mLaunchIntent = mContext.getPackageManager().getLaunchIntentForPackage(
intent.getStringExtra(PACKAGENAME));
if (mLaunchIntent == null) return;
mLaunchIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
mContext.startActivity(mLaunchIntent);
}
/**
* delete the apk
*/
private void clearCachedApkIfNeededAndFinish() {
if ("file".equals(mPackageURI.getScheme()) && mPackageURI.getPath() != null
&& mPackageURI.getPath().startsWith(mContext.getCacheDir().toString())) {
File file = new File(mPackageURI.getPath());
file.delete();
}
}
/**
* Parse the Uri and set up the installer for this package.
*
* @param packageUri The URI to parse
* @return {@code true} iff the installer could be set up
*/
private boolean processPackageUri(final Uri packageUri) {
mPackageURI = packageUri;
final String scheme = packageUri.getScheme();
Log.d(TAG, " scheme = " + scheme + " packageUri " + packageUri);
switch (scheme) {
case SCHEME_PACKAGE: {
try {
mPkgInfo = mPm.getPackageInfo(packageUri.getSchemeSpecificPart(),
PackageManager.GET_PERMISSIONS
| PackageManager.GET_UNINSTALLED_PACKAGES);
} catch (PackageManager.NameNotFoundException e) {
}
if (mPkgInfo == null) {
Log.w(TAG, "Requested package " + packageUri.getScheme()
+ " not available. Discontinuing installation");
return false;
}
}
break;
case SCHEME_FILE: {
File sourceFile = new File(packageUri.getPath());
PackageParser.Package parsed = PackageUtil.getPackageInfo(sourceFile);
// Check for parse errors
if (parsed == null) {
Log.w(TAG, "Parse error when parsing manifest. Discontinuing installation");
return false;
}
mPkgInfo = PackageParser.generatePackageInfo(parsed, null,
PackageManager.GET_PERMISSIONS, 0, 0, null,
new PackageUserState());
}
break;
case SCHEME_CONTENT: {
mStagingAsynTask = new SilentInstallerReceiver.StagingAsyncTask();
mStagingAsynTask.execute(packageUri);
return false;
}
default: {
Log.w(TAG, "Unsupported scheme " + scheme);
clearCachedApkIfNeededAndFinish();
return false;
}
}
return true;
}
private final class StagingAsyncTask extends AsyncTask<Uri, Void, File> {
@Override
protected void onPreExecute() {
// TODO nothing to do
}
@Override
protected File doInBackground(Uri... params) {
if (params == null || params.length <= 0) {
return null;
}
Uri packageUri = params[0];
File sourceFile = null;
try {
sourceFile = File.createTempFile("package", ".apk", mContext.getApplicationContext().getCacheDir());
try (
InputStream in = mContext.getApplicationContext().getContentResolver().openInputStream(packageUri);
OutputStream out = (in != null) ? new FileOutputStream(
sourceFile) : null;
) {
// Despite the comments in ContentResolver#openInputStream
// the returned stream can be null.
if (in == null) {
return null;
}
byte[] buffer = new byte[4096];
int bytesRead;
while ((bytesRead = in.read(buffer)) >= 0) {
// Be nice and respond to a cancellation
if (isCancelled()) {
return null;
}
out.write(buffer, 0, bytesRead);
}
}
} catch (IOException ioe) {
Log.w(TAG, "Error staging apk from content URI", ioe);
if (sourceFile != null) {
sourceFile.delete();
}
}
return sourceFile;
}
@Override
protected void onPostExecute(File file) {
if (file == null) {
return;
}
Uri fileUri = Uri.fromFile(file);
boolean wasSetUp = processPackageUri(fileUri);
if (wasSetUp) {
initiateInstall();
}
}
@Override
protected void onCancelled(File file) {
// TODO nothing to do
}
}
}
创建完成之后重新编译 PackageInstaller apk push到手机里面重启手机.
使用adb发送广播进行静默安装操作验证,当下载好文件后下,下发如下操作即可.
adb shell am broadcast -a xxx.intent.action.SILENT.INSTALLER --es instllpath "apk路径"
执行此命令许等待 5-10秒钟,等待的时间和手机的性能有关系,性能越好等待越短.安装完成之后直接将apk拉起,不需要任何操作,
如果不想安装完成之后被拉起来将如下代码注注释掉即可.
<action android:name="xxx.intent.action.SILENT.INSTALLER.COMMIT"></action>