安装应用之前写过,最近需要给尝试多种安装方式,给了一下方法。
一般来说,有几种方式:
1,标准的Intent
2,把apk地址托管给浏览器,浏览器下载安装
3,pm install(需要su权限)
4,使用PackageManager进行安装(需要是系统级别的应用,或系统签名)
5,把apk地址 托管给 DownloadManager 下载处理(类似2)
下面实现了前三种方式。
当然,完整的流程应该是
1,从后台获得安装包的升级信息
2,还有校验版本号
3,选择升级界面等UI
4,校验下载下来的安装包
5,适配android7的 FileProvider
这里就不展开了。
SuperShell
见我的 之前的博客
build.gradle
dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
compile 'com.android.support:appcompat-v7:25.3.1'
compile 'com.android.support:design:25.3.1'
compile 'com.loopj.android:android-async-http:1.4.9'
}
package org.yeshen.upgrade;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.os.Environment;
import android.support.annotation.NonNull;
import android.util.Log;
import android.widget.Toast;
import com.loopj.android.http.AsyncHttpClient;
import com.loopj.android.http.FileAsyncHttpResponseHandler;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
import cz.msebera.android.httpclient.Header;
import org.yeshen.until.SuperShell;
public class Upgrade {
private static Upgrade ins = new Upgrade();
public static Upgrade getInstance() {
return ins;
}
interface DownloadListener {
void done(String path);
}
public void installByPm(final Context context, @NonNull String url) {
download(context, url, new DownloadListener() {
@Override
public void done(String path) {
String cmd = "pm install -r " + path;
SuperShell sh = new SuperShell();
try {
sh.open();
sh.write(cmd);
} catch (Exception e) {
e.printStackTrace();
}
sh.close();
Log.e("path done", cmd);
Toast.makeText(context, "done:" + cmd, Toast.LENGTH_SHORT).show();
}
});
}
public void installByIntent(final Context context, @NonNull String url) {
download(context, url, new DownloadListener() {
@Override
public void done(String path) {
final Uri apk = Uri.fromFile(new File(path));
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setDataAndType(apk, "application/vnd.android.package-archive");
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
context.startActivity(intent);
}
});
}
private void download(final Context context, String url, final DownloadListener listener) {
AsyncHttpClient client = new AsyncHttpClient();
client.get(url, new FileAsyncHttpResponseHandler(context) {
@Override
public void onFailure(int statusCode, Header[] headers, Throwable throwable, File file) {
Toast.makeText(context, "fail to download", Toast.LENGTH_SHORT).show();
}
@Override
public void onSuccess(int statusCode, Header[] headers, File source) {
File target = new File(destFile(context));
try {
InputStream in = new FileInputStream(source);
OutputStream out = new FileOutputStream(target);
byte[] buf = new byte[1024];
int len;
while ((len = in.read(buf)) > 0) {
out.write(buf, 0, len);
}
in.close();
out.close();
} catch (Exception e) {
e.printStackTrace();
}
modifyApkFileVisibility(target);
if (listener != null) {
listener.done(target.getAbsolutePath());
}
}
});
}
private String destFile(Context context) {
String cachePath = "";
if (Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState())
|| !Environment.isExternalStorageRemovable()) {
File cacheDir = context.getExternalCacheDir();
if (cacheDir != null) cachePath = cacheDir.getPath();
} else {
cachePath = context.getCacheDir().getPath();
}
return cachePath + "/target.apk";
}
@SuppressLint({"SetWorldReadable", "SetWorldWritable"})
private boolean modifyApkFileVisibility(File file) {
return file.setExecutable(true, false)
&& file.setReadable(true, false)
&& file.setWritable(true, false);
}
public void installByBrowser(Context context, @NonNull String url) {
final Uri uri = Uri.parse(url);
final Intent intent = new Intent(Intent.ACTION_VIEW, uri);
if (context instanceof Activity) {
context.startActivity(intent);
} else if (context instanceof Service) {
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
context.startActivity(intent);
}
}
}
最近看这部分的系统源码,这个周末有时间再补源码分析吧 :)
然后用浏览器打开的这种方式在我们的系统上出了问题,查了一下,用浏览器打开url之后,浏览器调用了 DownloadManager
源码路径在
cd ./packages/providers/DownloadProvider
发现最后在OpenHelper
中出错了
vim ./packages/providers/DownloadProvider/src/com/android/providers/downloads/OpenHelper.java
private static Intent buildViewIntent(Context context, long id) {
final DownloadManager downManager = (DownloadManager) context.getSystemService(
Context.DOWNLOAD_SERVICE);
downManager.setAccessAllDownloads(true);
final Cursor cursor = downManager.query(new DownloadManager.Query().setFilterById(id));
try {
if (!cursor.moveToFirst()) {
return null;
}
final Uri localUri = getCursorUri(cursor, COLUMN_LOCAL_URI);
final File file = getCursorFile(cursor, COLUMN_LOCAL_FILENAME);
String mimeType = getCursorString(cursor, COLUMN_MEDIA_TYPE);
mimeType = DownloadDrmHelper.getOriginalMimeType(context, file, mimeType);
final Intent intent = new Intent(Intent.ACTION_VIEW);
if ("application/vnd.android.package-archive".equals(mimeType)) {
// PackageInstaller doesn't like content URIs, so open file
intent.setDataAndType(localUri, mimeType);
// Also splice in details about where it came from
final Uri remoteUri = getCursorUri(cursor, COLUMN_URI);
intent.putExtra(Intent.EXTRA_ORIGINATING_URI, remoteUri);
intent.putExtra(Intent.EXTRA_REFERRER, getRefererUri(context, id));
intent.putExtra(Intent.EXTRA_ORIGINATING_UID, getOriginatingUid(context, id));
} else if ("file".equals(localUri.getScheme())) {
intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION
| Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
intent.setDataAndType(
ContentUris.withAppendedId(ALL_DOWNLOADS_CONTENT_URI, id), mimeType);
} else {
intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
intent.setDataAndType(localUri, mimeType);
}
return intent;
} finally {
cursor.close();
}
}
mimeType 识别错了….
09-21 08:27:52.008 1765 1838 E yeshentest: application/octet-stream/storage/emulated/0/Download/yy.apk
发现是我nginx的配置有问题
sudo vim /etc/nginx/mime.types
加入
application/vnd.android.package-archive apk;
application/iphone pxl ipa;
sudo nginx -s reload
识别出来的信息就是
09-21 08:41:09.125 1764 1842 E yeshentest: application/vnd.android.package-archive | /storage/emulated/0/Download/yy.apk | file
然后就对了,可以安装了。
不过我在魅族,oppo上试了,即使mimeType是application/octet-stream
也是安装的,应该是这个东西被这些rom厂商改过。