公司最近上马了Android 9和10的平台,我们也得哼哧哼哧的进行相关的开发。我只能说谷歌的工程师为了KPI考核对Android修改的老开心了,可苦了我们啊。这不今天在进行Android的静默安装的API封装,尼玛原来的相关接口都没有了。那么今天要说的就是在Android P上面怎么实施静默安装/卸载接口的封装。这个感觉直接上代码有点太残暴了,还是先分析下基本面,然后上封装好的接口比较好。
在开始上代码之前,让我们来先说说Android P的安装和卸载原理:
Android P 上采用类似 socket 的方式与 server 端通信完成安装,其中Session是重点
其中有如下几个点是我们要重点关注的如下:
PackageInstaller
Session
如何安装?
静默安装时比较高的权限,一般应用是不能的,所以必须具备system权限,这个是前提。好了不多说啥了,直接上代码。
/*
* Copyright (C) 2017 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.xxx.android9_api;
// Need the following import to get access to the app resources, since this
// class is in a sub-package.
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import android.app.Activity;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.content.IntentSender;
import android.content.pm.PackageInstaller;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.Toast;
/**
* Demonstration of package installation and uninstallation using the package installer Session
* API.
*
* @see InstallApk for a demo of the original (non-Session) API.
*/
public class InstallApkSessionApi extends Activity {
private static final String PACKAGE_INSTALLED_ACTION =
"com.xxx.install";
private static final String PACKAGE_UNINSTALLED_ACTION =
"com.xxx.uninstall";
private static final String TAG = "install";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.install_apk_session_api);
// Watch for button clicks.
Button button = (Button) findViewById(R.id.install);
button.setOnClickListener(new OnClickListener() {
public void onClick(View v) {
PackageInstaller.Session session = null;
try {
//获取PackageInstaller对象
PackageInstaller packageInstaller = getPackageManager().getPackageInstaller();
PackageInstaller.SessionParams params = new PackageInstaller.SessionParams(
PackageInstaller.SessionParams.MODE_FULL_INSTALL);
//创建一个Session
int sessionId = packageInstaller.createSession(params);
//建立和PackageManager的socket通道,Android中的通信不仅仅有Binder还有很多其它的
session = packageInstaller.openSession(sessionId);
//将App的内容通过session传输
addApkToInstallSession("HelloActivity.apk", session);
// Create an install status receiver.
Context context = InstallApkSessionApi.this;
Intent intent = new Intent(context, InstallApkSessionApi.class);
intent.setAction(PACKAGE_INSTALLED_ACTION);
PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, intent, 0);
IntentSender statusReceiver = pendingIntent.getIntentSender();
// Commit the session (this will start the installation workflow).
//开启安装
session.commit(statusReceiver);
} catch (IOException e) {
throw new RuntimeException("Couldn't install package", e);
} catch (RuntimeException e) {
if (session != null) {
session.abandon();
}
throw e;
}
}
});
Button uninstall = (Button)findViewById(R.id.uninstall);
uninstall.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View arg0) {
// TODO Auto-generated method stub
uninstall("com.example.android.helloactivity");
}
});
}
private void addApkToInstallSession(String assetName, PackageInstaller.Session session)
throws IOException {
// It's recommended to pass the file size to openWrite(). Otherwise installation may fail
// if the disk is almost full.
try (OutputStream packageInSession = session.openWrite("package", 0, -1);
InputStream is = getAssets().open(assetName)) {
byte[] buffer = new byte[16384];
int n;
while ((n = is.read(buffer)) >= 0) {
packageInSession.write(buffer, 0, n);
}
}
}
// Note: this Activity must run in singleTop launchMode for it to be able to receive the intent
// in onNewIntent().
//此处一定要运行单例模式或者singleTop模式,否则会一直创建该Activity
@Override
protected void onNewIntent(Intent intent) {
Bundle extras = intent.getExtras();
Log.e(TAG, intent.toString());
if (PACKAGE_INSTALLED_ACTION.equals(intent.getAction())) {
Log.e(TAG, intent.getAction());
int status = extras.getInt(PackageInstaller.EXTRA_STATUS);
String message = extras.getString(PackageInstaller.EXTRA_STATUS_MESSAGE);
switch (status) {
case PackageInstaller.STATUS_PENDING_USER_ACTION:
// This test app isn't privileged, so the user has to confirm the install.
Intent confirmIntent = (Intent) extras.get(Intent.EXTRA_INTENT);
startActivity(confirmIntent);
break;
case PackageInstaller.STATUS_SUCCESS:
Toast.makeText(this, "Install succeeded!", Toast.LENGTH_SHORT).show();
Log.e(TAG,"Install succeeded!");
break;
case PackageInstaller.STATUS_FAILURE:
case PackageInstaller.STATUS_FAILURE_ABORTED:
case PackageInstaller.STATUS_FAILURE_BLOCKED:
case PackageInstaller.STATUS_FAILURE_CONFLICT:
case PackageInstaller.STATUS_FAILURE_INCOMPATIBLE:
case PackageInstaller.STATUS_FAILURE_INVALID:
case PackageInstaller.STATUS_FAILURE_STORAGE:
Toast.makeText(this, "Install failed! " + status + ", " + message,
Toast.LENGTH_SHORT).show();
Log.e(TAG,"Install failed! " + status + ", " + message);
break;
default:
Toast.makeText(this, "Unrecognized status received from installer: " + status,
Toast.LENGTH_SHORT).show();
Log.e(TAG,"Unrecognized status received from installer: " + status);
}
}
else if(PACKAGE_UNINSTALLED_ACTION.equals(intent.getAction())){
Log.e(TAG, intent.getAction());
int status = extras.getInt(PackageInstaller.EXTRA_STATUS);
String message = extras.getString(PackageInstaller.EXTRA_STATUS_MESSAGE);
switch (status) {
case PackageInstaller.STATUS_PENDING_USER_ACTION:
// This test app isn't privileged, so the user has to confirm the install.
Intent confirmIntent = (Intent) extras.get(Intent.EXTRA_INTENT);
startActivity(confirmIntent);
break;
case PackageInstaller.STATUS_SUCCESS:
Toast.makeText(this, "Uninstall succeeded!", Toast.LENGTH_SHORT).show();
Log.e(TAG,"Uninstall succeeded!");
break;
case PackageInstaller.STATUS_FAILURE:
case PackageInstaller.STATUS_FAILURE_ABORTED:
case PackageInstaller.STATUS_FAILURE_BLOCKED:
case PackageInstaller.STATUS_FAILURE_CONFLICT:
case PackageInstaller.STATUS_FAILURE_INCOMPATIBLE:
case PackageInstaller.STATUS_FAILURE_INVALID:
case PackageInstaller.STATUS_FAILURE_STORAGE:
Toast.makeText(this, "Install failed! " + status + ", " + message,
Toast.LENGTH_SHORT).show();
Log.e(TAG,"Uninstall failed! " + status + ", " + message);
break;
default:
Toast.makeText(this, "Unrecognized status received from installer: " + status,
Toast.LENGTH_SHORT).show();
Log.e(TAG,"Unrecognized status received from installer: " + status);
}
}
}
/**
* 根据包名卸载应用
*
* @param packageName
*/
public void uninstall(String packageName) {
Intent broadcastIntent = new Intent(this, InstallApkSessionApi.class);
broadcastIntent.setAction(PACKAGE_UNINSTALLED_ACTION);
PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, broadcastIntent, PendingIntent.FLAG_UPDATE_CURRENT);
PackageInstaller packageInstaller = getPackageManager().getPackageInstaller();
packageInstaller.uninstall(packageName, pendingIntent.getIntentSender());
}
}
好了实例代码,已经编写OK,让我们简单分析一下,及其步骤:
安装流程
卸载流程:比较简单就不细述了。
这里需要注意的是这个测试Activiyt的Mode必须是此处一定要运行单例模式或者singleTop模式,否则会一直创建该Activity。
运行实例,静默安装和卸载成功。
msm8953_64:/ # logcat -s install
--------- beginning of main
--------- beginning of system
01-15 02:10:45.258 7087 7087 E install : Intent { act=com.xxx.install flg=0x10000000 cmp=com.xxx.android9_api/.InstallApkSessionApi (has extras) }
01-15 02:10:45.259 7087 7087 E install : com.xxx.install
01-15 02:10:45.304 7087 7087 E install : Install succeeded!
01-15 02:10:48.340 7087 7087 E install : Intent { act=com.xxx.uninstall flg=0x10000000 cmp=com.xxx.android9_api/.InstallApkSessionApi (has extras) }
01-15 02:10:48.340 7087 7087 E install : com.xxx.uninstall
01-15 02:10:48.372 7087 7087 E install : Uninstall succeeded!
上边的是通过广播异步的完成静默安装和卸载,那么有没有同步的呢!我这暴脾气就不信了,没有同步的方式可以做到。我头悬梁,锥刺股终于实现了。下面提供一下我封装的接口,供各位参考,希望能对大家有帮助。
package com.xxxdroid.compat;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.concurrent.SynchronousQueue;
import java.util.concurrent.TimeUnit;
import android.annotation.SuppressLint;
import android.content.Context;
import android.content.IIntentReceiver;
import android.content.IIntentSender;
import android.content.Intent;
import android.content.IntentSender;
import android.content.pm.PackageInstaller;
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.os.IBinder;
import android.util.Log;
public class PackageMgrCompat {
private static final String TAG = "PackageMgrCompat_v28_later";
private Context mContext;
private PackageManager mPackageManager;
public PackageMgrCompat(Context context) {
mContext = context;
mPackageManager = context.getPackageManager();
}
@SuppressLint("InlinedApi")
public boolean deletePackage(String pkgName, int unInstallFlags) {
final LocalIntentReceiver localReceiver = new LocalIntentReceiver();
getPi().uninstall(pkgName,
unInstallFlags | PackageManager.DELETE_ALL_USERS,
localReceiver.getIntentSender());
final Intent result = localReceiver.getResult();
synchronized (localReceiver) {
final int status = result.getIntExtra(PackageInstaller.EXTRA_STATUS,
PackageInstaller.STATUS_FAILURE);
if (status != PackageInstaller.STATUS_SUCCESS) {
Log.e(TAG, "UnInstallation should have succeeded, but got code "
+ status);
return false;
} else {
Log.e(TAG, "UnInstallation have succeeded");
return true;
}
}
}
@SuppressLint("InlinedApi")
public int installPackage(File apkFilePath) {
Log.w(TAG, "installPackage pkg: " + apkFilePath);
PackageInstaller.SessionParams params = new PackageInstaller.SessionParams(
PackageInstaller.SessionParams.MODE_FULL_INSTALL);
PackageInstaller.Session session = null;
// 创建一个Session
try {
int sessionId = getPi().createSession(params);
// 建立和PackageManager的socket通道,Android中的通信不仅仅有Binder还有很多其它的
session = getPi().openSession(sessionId);
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
return PackageManager.INSTALL_FAILED_INVALID_APK;
}
addApkToInstallSession(apkFilePath, session);
final LocalIntentReceiver localReceiver = new LocalIntentReceiver();
session.commit(localReceiver.getIntentSender());
final Intent result = localReceiver.getResult();
synchronized (localReceiver) {
final int status = result.getIntExtra(PackageInstaller.EXTRA_STATUS,
PackageInstaller.STATUS_FAILURE);
if (session != null) {
session.close();
}
if (status != PackageInstaller.STATUS_SUCCESS) {
Log.e(TAG, "Installation should have succeeded, but got code "
+ status);
return status;
} else {
Log.e(TAG, "Installation have succeeded");
return status;
}
}
}
private boolean addApkToInstallSession(File apkFilePath,
PackageInstaller.Session session) {
InputStream in = null;
OutputStream out = null;
boolean success = false;
try {
out = session.openWrite("base.apk", 0, apkFilePath.length());
in = new FileInputStream(apkFilePath);
int total = 0, c;
byte[] buffer = new byte[1024 * 1024];
while ((c = in.read(buffer)) != -1) {
total += c;
out.write(buffer, 0, c);
}
session.fsync(out);
Log.d(TAG, "streamed " + total + " bytes");
success = true;
} catch (IOException e) {
e.printStackTrace();
} finally {
if (null != session) {
session.close();
}
try {
if (null != out) {
out.close();
}
if (null != in) {
in.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
return success;
}
private static class LocalIntentReceiver {
private final SynchronousQueue<Intent> mResult = new SynchronousQueue<>();
private IIntentSender.Stub mLocalSender = new IIntentSender.Stub() {
@Override
public void send(int code, Intent intent, String resolvedType,
IBinder whitelistToken, IIntentReceiver finishedReceiver,
String requiredPermission, Bundle options) {
try {
mResult.offer(intent, 5, TimeUnit.SECONDS);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
};
public IntentSender getIntentSender() {
return new IntentSender((IIntentSender) mLocalSender);
}
public Intent getResult() {
try {
return mResult.take();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
private PackageManager getPm() {
return mContext.getPackageManager();
}
private PackageInstaller getPi() {
return getPm().getPackageInstaller();
}
}
运行实例,静默安装和卸载成功。
λ adb logcat -s PackageMgrCompat_v28_later
--------- beginning of main
--------- beginning of system
01-15 07:59:49.035 6251 6251 W PackageMgrCompat_v28_later: installPackage pkg: /data/HelloActivity.apk
01-15 07:59:49.329 6251 6251 D PackageMgrCompat_v28_later: streamed 12770 bytes
01-15 07:59:50.801 6251 6251 E PackageMgrCompat_v28_later: Installation have succeeded
01-15 07:59:53.111 6251 6251 E PackageMgrCompat_v28_later: deletePackage not implemented yet!
01-15 07:59:53.593 6251 6251 E PackageMgrCompat_v28_later: UnInstallation have succeeded
文章至此,关于Android P静默安装和卸载的封装就结束了,如果想更详细的了解那么就只能跟读Android FrameWork的源码了。我也在跟进中,各位如果有兴趣也可以一起聊聊。如果对各位有帮助的话麻烦点个赞或者拍个砖。