android 安装应用

安装应用之前写过,最近需要给尝试多种安装方式,给了一下方法。
一般来说,有几种方式:
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厂商改过。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值