Rayland
主板虽然作为一块基于Android
的工控板,但是很多设备厂商并不想让用户看到Android系统
信息。所以APK
默认设置为开机启动项、img
去除了Android
头部和底部菜单。但是随之带来了APK
更新的问题,传统的插入u盘
,sd卡
手动安装新版本APK
的方式已经不够用了。所以我们需要点自动的东西。
App内检测更新新版本APK
检测新版本APK
我们使用 四大组件之一的BroadcastReceiver
来检测 sd卡
或是u盘设备
的接入。
public class StorageMountListener extends BroadcastReceiver{
@Override
public void onReceive(final Context context, Intent intent) {
if(intent.getAction().equals(Intent.ACTION_MEDIA_MOUNTED)){
// 获取插入设备路径
String path = intent.getData().getPath();
// 检测路径是否有新版本APK
ApkUpdateUtils.getInstance().checkLocalUpdateAtBackground(context, path);
}
}
}
ApkUpdateUtils.java
/**
* 后台检查指定目录下是否有新版本的APK,有则提示安装
* @param context 上下文
* @param path 需要检查的目录
*/
public void checkLocalUpdateAtBackground(final Context context, final String path){
ExecutorService executorService = Executors.newSingleThreadExecutor();
executorService.execute(new Runnable() {
@Override
public void run() {
// 检查指定目录下是否存在高版本的更新包,并返回结果
File apkFile = findUpdatePackage(context, path);
if(apkFile == null){
return;
}
File msg = new File(apkFile.getParent(), apkFile.getName().replace(".apk", ".txt"));
String description = readStringFile(msg);
Intent intent = new Intent(context, UpdateActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
// 新版本apk 路径
intent.putExtra("apk", apkFile.getAbsolutePath());
// 新版本apk 描述信息
intent.putExtra("description", description);
context.startActivity(intent);
}
});
}
/**
* 检查指定目录下是否存在高版本的更新包,并返回结果
* @param path 检查目录
* @return APK文件
*/
public File findUpdatePackage(Context context, String path) {
File parent = new File(path);
if(!parent.exists() || parent.isFile()){
return null;
}
File[] apks = parent.listFiles(new FileFilter() {
@Override
public boolean accept(File pathname) {
return pathname.getName().toLowerCase().endsWith(".apk");
}
});
if(apks == null || apks.length == 0){
return null;
}
try {
/**
* 通过 build.gradle 中的 versionCode 来判断
* 每次版本更新后 修改versionCode
*/
PackageManager packageManager = context.getPackageManager();
PackageInfo packageInfo = packageManager.getPackageInfo(context.getPackageName(), 0);
File apkFile = null;
int versionCode = 0;
for(File apk : apks){
PackageInfo apkInfo = packageManager.getPackageArchiveInfo(apk.getAbsolutePath(), 0);
if(packageInfo.packageName.equals(apkInfo.packageName) && packageInfo.versionCode < apkInfo.versionCode){
if(apkFile == null){
apkFile = apk;
versionCode = apkInfo.versionCode;
}else{
if(versionCode < apkInfo.versionCode){
apkFile = apk;
versionCode = apkInfo.versionCode;
}
}
}
}
return apkFile;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
/**
* 将文件内容读取成String
* @param file 要读取的文件
* @return 文件内容
*/
public String readStringFile(File file){
StringBuilder builder = new StringBuilder();
BufferedReader br = null;
try {
br = new BufferedReader(new InputStreamReader(new FileInputStream(file), "GBK"));
String line;
while((line = br.readLine())!=null){
builder.append(line);
builder.append("\n");
}
} catch (IOException e) {
e.printStackTrace();
}finally {
if(br!=null){
try {
br.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return builder.toString();
}
新版本更新提示
UpdateActivity.java
/**
* 显示更新提示对话框
*/
private void showUpdateMsgDialog(final Context context, final String apk, String descrption ){
PackageInfo apkInfo = getPackageManager().getPackageArchiveInfo(apk, 0);
AlertDialog updateMsgDialog = new AlertDialog.Builder(context).create();
updateMsgDialog.setOnDismissListener(new DialogInterface.OnDismissListener() {
@Override
public void onDismiss(DialogInterface dialog) {
finish();
}
});
updateMsgDialog.setTitle("检测到新版本"+apkInfo.versionName);
updateMsgDialog.setMessage(descrption);
updateMsgDialog.setButton(AlertDialog.BUTTON_NEGATIVE, "取消", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
finish();
}
});
updateMsgDialog.setButton(AlertDialog.BUTTON_POSITIVE, "安装", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
// 启动后台安装服务 `SilentInstallService`
Intent intent = new Intent(UpdateActivity.this, SilentInstallService.class);
intent.putExtra("apkPath", apk);
context.startService(intent);
}
});
updateMsgDialog.setCanceledOnTouchOutside(false);
updateMsgDialog.show();
}
后台安装服务
SilentInstallService.java
public class SilentInstallService extends IntentService {
static final String TAG = SilentInstallService.class.getSimpleName();
public SilentInstallService() {
super(TAG);
}
@Override
protected void onHandleIntent(Intent intent) {
PackageManager pm = getPackageManager();
String apkPath = intent.getStringExtra("apkPath");
PackageInfo info = pm.getPackageArchiveInfo(apkPath,PackageManager.GET_ACTIVITIES);
if(install(apkPath) && info!=null){
startActivity(getPackageManager().getLaunchIntentForPackage(info.packageName));
}
}
public boolean install(String apkPath){
Process process = null;
BufferedReader errorStream = null;
try {
process = Runtime.getRuntime().exec("pm install -r "+apkPath+"\n");
process.waitFor();
errorStream = new BufferedReader(new InputStreamReader(process.getErrorStream()));
String msg = "";
String line;
while((line=errorStream.readLine())!=null){
msg += line;
}
Log.i(TAG, "silent install msg : "+msg);
if(!msg.contains("Failure")){
return true;
}
} catch (Exception e) {
e.printStackTrace();
}finally {
if(errorStream!=null){
try {
errorStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if(process!=null){
process.destroy();
}
}
return false;
}
}
AndroidManifest.xml
注册 StorageMountListener
和SilentInstallService
<receiver android:name=".StorageMountListener">
<intent-filter>
<action android:name="android.intent.action.MEDIA_MOUNTED" />
<data android:scheme="file"/>
</intent-filter>
</receiver>
<service android:name="cn.rayland.update.SilentInstallService">
</service>
权限配置
到这个一切看起来尽善尽美了? but it does‘t work.
我们需要系统权限
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
... ...
android:sharedUserId="android.uid.system">
但是我们会发现安装失败
error:Failure [INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES]
网上说这是因为你安装了debug
权限签名,再安装系统sign
签名就会有这个问题。需要卸载以前的app
再安装。
然后我们又遇到了它
error:Failure [INSTALL_FAILED_SHARED_USER_INCOMPATIBLE]
这是因为我们在 AndroidManifest.xml
申明了系统签名,然而并没有。
我们需要一顿操作
找到编译目标系统时的签名证书platform.pk8和platform.x509.pem,在android源码目录build\target\product\security下
将签名工具(signapk.jar)、签名证书(platform.pk8和platform.x509.pem)及编译出来的apk文件都放到同一目录
然后命令行执行:
java -jar signapk.jar platform.x509.pem platform.pk8 input.apk output.apk
就能把路径下的 input.apk
变成签名的output.apk
当然你也可以使用现成的signapk,运行signApk.bat
就可以开心的更新了
其他方法
你也可以将更新APK服务SilentInstallService
编译成一个app
烧在img
中。每次调用有系统签名的SilentInstallService
app
即可。