- 静默安装是指apk安装不需要用户手动点击,直接安装。
- 但此文方法中,在安装过程仍需要手动点击安装(无系统签名)。
- 需开启未知应用安装权限(非真正静默安装)
参考博客:
Android 9 静默安装、卸载App
android9.0 apk静默安装
安卓9.0静默安装的方法
...
安装流程
- 通过PackageManagerService获取getPackageInstaller对象
- 通过packageInstaller调用openSession创建PackageInstaller.Session
- 将要静默安装的app写入Session中
- 然后调用Session的commit开始安装
- 确认安卓系统是否有
root
,并非能够执行pm
指令。
如:只需判断是否有su
等文件夹即可。 - 无需系统签名
android:sharedUserId="android.uid.system" - 开启Android 8.0 允许安装未知来源权限
- 页面启动模式设置成singleTop、singleTask、singleInstance防止多次创建
public boolean isDeviceRooted() {
String su = "su";
String[] locations = {"/system/bin/", "/system/xbin/", "/sbin/", "/system/sd/xbin/", "/system/bin/failsafe/", "/data/local/xbin/", "/data/local/bin/", "/data/local/"};
for (String location : locations) {
if (new File(location + su).exists()) {
return true;
}
}
return false;
}
其他判断方式
private String[] rootRelatedDirs = new String[]{
"/su", "/su/bin/su", "/sbin/su",
"/data/local/xbin/su", "/data/local/bin/su", "/data/local/su",
"/system/xbin/su",
"/system/bin/su", "/system/sd/xbin/su", "/system/bin/failsafe/su",
"/system/bin/cufsdosck", "/system/xbin/cufsdosck", "/system/bin/cufsmgr",
"/system/xbin/cufsmgr", "/system/bin/cufaevdd", "/system/xbin/cufaevdd",
"/system/bin/conbb", "/system/xbin/conbb"};
public boolean hasRootPrivilege() {
boolean hasRootDir = false;
String[] rootDirs;
int dirCount = (rootDirs = rootRelatedDirs).length;
for (int i = 0; i < dirCount; ++i) {
String dir = rootDirs[i];
if ((new File(dir)).exists()) {
hasRootDir = true;
break;
}
}
return Build.TAGS != null && Build.TAGS.contains("test-keys") || hasRootDir;
}
AndroidManifest.xml配置
android:sharedUserId="android.uid.system" 不需要
android:sharedUserId="android.uid.system" // 【不需要】加这个
<!-- 网络访问权限 -->
<uses-permission android:name="android.permission.INTERNET" />
<!-- 静默安装权限 -->
<uses-permission android:name="android.permission.INSTALL_PACKAGES" />
<!-- 读写外部存储权限 -->
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
...
<activity
android:name=".InstallApkActivity"
android:exported="true"
android:launchMode="singleTask" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
...
<receiver
android:name=".InstallResultReceiver"
android:enabled="true"
android:exported="true">
<intent-filter>
<action android:name="android.content.pm.extra.STATUS" />
<action android:name="android.intent.action.PACKAGE_REPLACED" />
<action android:name="android.intent.action.MY_PACKAGE_REPLACED" />
<data android:scheme="package" />
</intent-filter>
</receiver>
下载APK
private boolean mIsCancel;
private String url = "http://xxx.xxx.x.xxx/app.apk";
new Thread(() -> {
try {
if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
//文件保存路径
String sdPath = Environment.getExternalStorageDirectory() + "/Download";
File dir = new File(sdPath);
if (!dir.exists()) {
boolean mkdir = dir.mkdir();
}
//下载文件
HttpURLConnection conn = (HttpURLConnection) new URL(url).openConnection();
conn.connect();
InputStream is = conn.getInputStream();
int length = conn.getContentLength();
File apkFile = new File(mSavePath, apkName);
FileOutputStream fos = new FileOutputStream(apkFile);
int count = 0;
byte[] buffer = new byte[1024];
while (!mIsCancel) {
int numread = is.read(buffer);
count += numread;
// 计算进度条的当前位置
int mProgress = (int) (((float) count / length) * 100);
// 下载完成
if (numread < 0) {
mIsCancel = true;
installApk(apkFile);
break;
}
fos.write(buffer, 0, numread);
}
fos.close();
is.close();
}
} catch (Exception e) {
e.printStackTrace();
}
}).start();
执行安装
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
private void installApk(File file) {
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
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);
Log.d(TAG, "sessionId = " + sessionId);
//建立和PackageManager的socket通道,Android中的通信不仅仅有Binder还有很多其它的
session = packageInstaller.openSession(sessionId);
//将App的内容通过session传输
addApkToInstallSession(file, session);
// Create an install status receiver.
Context context = InstallApkActivity.this;
Intent intent = new Intent(context, InstallApkActivity.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).
//开启安装
Log.d(TAG, "session.commit ");
session.commit(statusReceiver);
} catch (IOException e) {
throw new RuntimeException("Couldn't install package", e);
} catch (RuntimeException e) {
if (session != null) {
session.abandon();
}
throw e;
}
}
});
thread.start();
}
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
private void addApkToInstallSession(File file, PackageInstaller.Session session)throws IOException {
try (OutputStream packageInSession = session.openWrite("package", 0, -1);
InputStream is = new FileInputStream(file)) {
byte[] buffer = new byte[16384];
int n;
while ((n = is.read(buffer)) >= 0) {
packageInSession.write(buffer, 0, n);
}
}
}
//【重要】否则session.commit之后无任何反应
@Override
protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
Bundle extras = intent.getExtras();
if (extras != null && getPackageName().equals(intent.getAction())) {
int status = 0;
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.LOLLIPOP) {
status = extras.getInt(PackageInstaller.EXTRA_STATUS);
}
if (status == PackageInstaller.STATUS_PENDING_USER_ACTION) { //提示去安装(手动安装)
Intent confirmIntent = (Intent) extras.get(Intent.EXTRA_INTENT);
startActivity(confirmIntent);
}
}
}
InstallResultReceiver
public class InstallResultReceiver extends BroadcastReceiver {
private String TAG = InstallResultReceiver.class.getSimpleName();
@Override
public void onReceive(Context context, Intent intent) {
Log.e(TAG, "收到安装反馈广播了:" + intent.getDataString());
int status = intent.getIntExtra(PackageInstaller.EXTRA_STATUS, PackageInstaller.STATUS_FAILURE);
Log.e(TAG, "收到安装反馈广播了:" + status);
String packageName = intent.getDataString();
Log.e(TAG, "action=" + intent.getAction() + " ,status=" + status);
}
}
InstallApkActivity
public class InstallApkActivity extends AppCompatActivity {
private static final String TAG = "install";
private ProgressBar mProgressBar;
private String apkName = "app.apk";
private File apkFile;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_install_apk);
mProgressBar = findViewById(R.id.progress);
initPermission(); //请求权限
}
public void onInstallClick(View view) {
if (apkFile == null || !apkFile.exists()) {
downloadAPK("http://xxx.xxx.x.xx:xxxx/app.apk");
return
}
checkPermission();
}
//请求权限存储等权限
private void initPermission() {
String[] permissions = {
Manifest.permission.READ_PHONE_STATE, Manifest.permission.WRITE_EXTERNAL_STORAGE, Manifest.permission.READ_EXTERNAL_STORAGE
};
ActivityCompat.requestPermissions(this, permissions, 1200);
}
private boolean mIsDownLoading;
private boolean mIsCancel;
private String mSavePath;
/*
* 开启新线程下载apk文件
*/
private void downloadAPK(String url) {
if (mIsDownLoading) return;
mIsDownLoading = true;
mIsCancel = false;
if (mProgressBar != null) mProgressBar.setVisibility(View.VISIBLE);
new Thread(() -> {
try {
if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
//文件保存路径
mSavePath = Environment.getExternalStorageDirectory() + "/Download";
File dir = new File(mSavePath);
if (!dir.exists()) {
boolean mkdir = dir.mkdir();
}
// 下载文件
HttpURLConnection conn = (HttpURLConnection) new URL(url).openConnection();
conn.connect();
InputStream is = conn.getInputStream();
int length = conn.getContentLength();
apkFile = new File(mSavePath, apkName);
FileOutputStream fos = new FileOutputStream(apkFile);
int count = 0;
byte[] buffer = new byte[1024];
while (!mIsCancel) {
int numread = is.read(buffer);
count += numread;
// 计算进度条的当前位置
int mProgress = (int) (((float) count / length) * 100);
if (mProgressBar != null) mProgressBar.setProgress(mProgress);
Log.e("下载中", "" + mProgress);
// 下载完成
if (numread < 0) {
Log.e("下载完成", "==>>");
mIsDownLoading = false;
mIsCancel = true;
checkPermission();//检查权限安装
break;
}
fos.write(buffer, 0, numread);
}
fos.close();
is.close();
}
} catch (Exception e) {
e.printStackTrace();
mIsDownLoading = false;
}
}).start();
}
public void checkPermission() {
boolean haveInstallPermission;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
haveInstallPermission = getPackageManager().canRequestPackageInstalls();
if (!haveInstallPermission) {//没有权限让调到设置页面进行开启权限;
Uri packageURI = Uri.parse("package:" + getPackageName());
Intent intent = new Intent(Settings.ACTION_MANAGE_UNKNOWN_APP_SOURCES, packageURI);
startActivityForResult(intent, 10086);
} else {
installApk();
}
} else {//其他android版本,可以直接执行安装逻辑;
}
}
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
private void installApk() {
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
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);
Log.d(TAG, "sessionId = " + sessionId);
//建立和PackageManager的socket通道,Android中的通信不仅仅有Binder还有很多其它的
session = packageInstaller.openSession(sessionId);
//将App的内容通过session传输
addApkToInstallSession(session);
// Create an install status receiver.
Context context = InstallApkActivity.this;
Intent intent = new Intent(context, InstallApkActivity.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).
//开启安装
Log.d(TAG, "session.commit ");
session.commit(statusReceiver);
} catch (IOException e) {
throw new RuntimeException("Couldn't install package", e);
} catch (RuntimeException e) {
if (session != null) {
session.abandon();
}
throw e;
}
}
});
thread.start();
}
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
private void addApkToInstallSession(PackageInstaller.Session session) throws IOException {
try (OutputStream packageInSession = session.openWrite("package", 0, -1);
InputStream is = new FileInputStream(apkFile)) {
byte[] buffer = new byte[16384];
int n;
while ((n = is.read(buffer)) >= 0) {
packageInSession.write(buffer, 0, n);
}
}
}
//【重要】开启手动点击安装
@Override
protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
Bundle extras = intent.getExtras();
Log.e(TAG, intent.toString());
if (extras != null && getPackageName().equals(intent.getAction())) {
int status = 0;
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.LOLLIPOP) {
status = extras.getInt(PackageInstaller.EXTRA_STATUS);
}
if (status == PackageInstaller.STATUS_PENDING_USER_ACTION) { //提示去安装
Intent confirmIntent = (Intent) extras.get(Intent.EXTRA_INTENT);
startActivity(confirmIntent);
}
}
}
/**
* 根据包名卸载应用
*
* @param packageName
*/
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
public void uninstall(String packageName) {
Intent broadcastIntent = new Intent(this, InstallApkActivity.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());
}
}
验证步骤
1、执行build打包apk,使用adb命令安装程序到安卓系统。
2、修改部分代码区分并打包新apk,并布置服务可链接下载。
3、下载apk并执行应用内部安装(安装未知应用),待覆盖安装后会自动重启。
4、验证前后两个安装包内容即可。
5、以上步骤已测验安装更新成功。
tip:由于非完全root系统,需要授予安装未知应用权限。