介绍

  项目中需要引入腾讯手机管家做安全防护功能,因此需要检测用户手机是否安装了腾讯手机管家,以此来引导用户下载手机管家,这个功能其实也很简单。

首先需要拿到手机管家的包名,不用多说,包名是APP的唯一标识。

取得APP包名的方式有很多,但是个人感觉最方便的还是采用appt工具来对apk进行解析获得apk的详细信息。

aapt简介

aapt即Android Asset Packaging Tool,我们可以在SDK的platform-tools目录下找到该工具。aapt可以查看、 创建、 更新ZIP格式的文档附件(zip, jar, apk)。 也可将资源文件编译成二进制文件,尽管你可能没有直接使用过aapt工具,但是build scripts和IDE插件会使用这个工具打包apk文件构成一个Android 应用程序。

主要用法

下面的这个参数列表基本向我们展示了如何使用aapt以及aapt的基本功能了。

aapt l[ist]:列出资源压缩包里的内容。
aapt d[ump]:查看APK包内指定的内容。
aapt p[ackage]:打包生成资源压缩包。
aapt r[emove]:从压缩包中删除指定文件。
aapt a[dd]:向压缩包中添加指定文件。
aapt v[ersion]:打印aapt的版本。

我们关注aapt d[ump]命令参数的用法,这个命令用来查看APK指定的内容,values 参数指定了想要查看的内容

aapt d[ump] [--values] [--include-meta-data] WHAT file.{apk} [asset [asset ...]]
   strings          输出字符串资源
   badging          查看apk包的packageName、versionCode、applicationLabel、launcherActivity、permission等各种详细信息
   permissions      输出权限列表
    resources       输出资源列表
   configurations   输出configurations 配置项
   xmltree          以树形结构输出的xml信息。
   xmlstrings     输出xml文件中所有的字符串信息。

如下图所示,我们拿到了app的包名 com.tencent.qqpimsecure

判断APP是否安装

PackageManager类用来获取当前安装在设备上的所有应用的各种信息。
SDK 说明如下:

/* Class for retrieving various kinds of information related to
the application * packages that are currently installed on the
device. * * You can find this class through {@link
Context#getPackageManager}. */

其中getInstalledPackages方法可以返回设备所有已安装的所有packages信息,参数为过滤条件,此处我们不需要对返回信息做过滤,传入0即可。

  /**
     * Return a List of all packages that are installed
     * on the device.
     */
    public abstract List<PackageInfo> getInstalledPackages(int flags);

通过PackageManager可以拿到系统安装所有的APP 的 PackageInfo 列表,PackageInfo 封装了一个app所有的信息,其实就是在APP安装过程中,系统通过解析AndroidManifest.xml文件 拿到APP信息并封装保存在PackageInfo中的。

以下是PackageInfo的SDK 说明:

/* Overall information about the contents of a package. This
corresponds * to all of the information collected from
AndroidManifest.xml. */

其中成员变量packageName表示应用的包名。

 /**
     * The name of this package.  From the &lt;manifest&gt; tag's "name"
     * attribute.
     */
    public String packageName;

了解了这些,我们可以写代码了。
代码如下:


 public static boolean isAppInstalled(Context context, String packageName) {
        final PackageManager packageManager = context.getPackageManager();
        //取得所有的PackageInfo
        List<PackageInfo> pinfo = packageManager.getInstalledPackages(0);
        List<String> pName = new ArrayList<>();
        if (pinfo != null) {
            for (int i = 0; i < pinfo.size(); i++) {
                String pn = pinfo.get(i).packageName;
                pName.add(pn);
            }
        }
        //判断包名是否在系统包名列表中
         return pName.contains(packageName);

利用DownLoadManager下载

如果尚未安装指定的应用,则应该从网络下载并安装应用。一个完整的下载服务需要考虑如下一些问题:

  1. 网络通信模块编写
  2. 用户网络类型判断,是否允许移动网络下载
  3. 断点续传或进度控制,中间暂停之后,之后可以继续下载
  4. 下载进度条展示
  5. 文件保存I/O操作
  6. 下载完成进行安装

总之 一个下载模块需要考虑的问题还是很多的,有时候还要做很多适配工作,例如 你通过通知栏进度条展示下载进度,往往在不同的机器上得到形形色色的UI 展示,你还得费心费力去做适配,的确很坑,实际上,我们只需要一个下载服务就可以了。

  从Android 2.3(API level 9)开始,Android用系统服务(Service)的方式提供了Download Manager来优化处理长时间的下载操作。Download Manager处理HTTP连接并监控连接中的状态变化以及系统重启来确保每一个下载任务顺利完成。

在大多数涉及到下载的情况中使用Download Manager都是不错的选择,特别是当用户切换不同的应用以后下载需要在后台继续进行,以及当下载任务顺利完成非常重要的情况(DownloadManager对于断点续传功能支持很好)。要想使用Download Manager,使用getSystemService方法请求系统的DOWNLOAD_SERVICE服务。

使用步骤如下:

  1. 得到DownloadManager

    DownloadManager dm = (DownloadManager) getSystemService(Context.DOWNLOAD_SERVICE);
  2. 得到连接请求对象

    DownloadManager.Request   request=new DownloadManager.Request (Uri.parse(uri));//请求的uri
  3. 对请求设置参数(常用的)

    //设置允许使用的网络类型,移动网络与wifi都可以
    request.setAllowedNetworkTypes(DownloadManager.Request.NETWORK_MOBILE|DownloadManager.Request.NETWORK_WIFI);
    
    //显示在下载界面,即下载后的文件在下载管理里显示
    down.setVisibleInDownloadsUi(true);
    
    /**
    设置下载后文件存放的位置,不设置会存在data/data/com.Android.provider.downloads/cache/下面,设置后存在sd上的Android/data/<包名>/files/下面。第2个参数是files下再建目录的目录名,第3个参数是文件名,如果第3个参数带路径,要确保路径存在,第2个参数路径随便写,会自己创建 
    */
    down.setDestinationInExternalFilesDir(mContext, Environment.DIRECTORY_MUSIC, "我的歌声里.mp3"); 
    
    
    /**在通知栏显示下载详情,比如百分比。默认是true,改为false需要权限*android.permission.DOWNLOAD_WITHOUT_NOTIFICATION
     此方法被setNotificationVisibility(int visibility)(API11)取代.
    */     
     down.setShowRunningNotification(true)(API 9); 
    
    //以sd卡路径为根路径,与上方法只有一个有效。第一个参数创建文件夹用的是mkdir
    
                    down.setDestinationInExternalPublicDir(Environment.DIRECTORY_MUSIC, "我的歌声里.mp3");
  4. 开始下载

    //将下载请求放入队列
    manager.enqueue(down);

其他

  1. 可以通过广播接收下载完成信息,Action为DownloadManager.ACTION_DOWNLOAD_COMPLETE

  2. 点击正在下载的notification将会受到Action为DownloadManager.ACTION_NOTIFICATION_CLICKED的广播。

  3. 得到下载完成的文件存在数据库中的ID:

    long downId = intent.getLongExtra(DownloadManager.EXTRA_DOWNLOAD_ID, -1);
  4. 得到文件存储的URI
    getUriForDownloadedFile(long id)

  5. 通过Cursor查询数据库获取下载信息

    new一个DownloadManager.QuerysetFilterById(long... ids),接着用DownloadManagerquery(DownloadManager.Queryquery)来得到Cursor对象。下载百分比也是这么获取,数据库里有两个字段其中current_bytes是当前下载bit数,在等于total_bytes之前是更新的。

记得加权限

<uses-permission android:name="android.permission.INTERNET"/>  
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>  

代码实现

布局文件activity_main.xml


<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <LinearLayout
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerInParent="true"
        android:orientation="vertical">

        <TextView
            android:id="@+id/tvShow"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content" />


        <Button
            android:id="@+id/btn"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="下载手机管家" />

    </LinearLayout>


</RelativeLayout>

AppUtils.java

public class AppUtils {

    /**
     * 判断某个App是否安装
     *
     * @param context
     * @param packageName
     * @return
     */
    public static boolean isAppInstalled(Context context, String packageName) {
        final PackageManager packageManager = context.getPackageManager();
        List<PackageInfo> pinfo = packageManager.getInstalledPackages(0);
        List<String> pName = new ArrayList<>();
        if (pinfo != null) {
            for (int i = 0; i < pinfo.size(); i++) {
                String pn = pinfo.get(i).packageName;
                pName.add(pn);
            }
        }
        return pName.contains(packageName);
    }

    /**
     * 根据包名启动对应的APP
     *
     * @param context
     * @param packageName
     */

    public static void startApp(Context context, String packageName) {
        Intent LaunchIntent = context.getPackageManager().getLaunchIntentForPackage(packageName);
        context.startActivity(LaunchIntent);
    }

    /**
     * 安装apk
     *
     * @param context
     * @param uri
     */

    public static void installApp(Context context, Uri uri) {
        Intent intent = new Intent(Intent.ACTION_VIEW);
        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        intent.setDataAndType(uri,
                "application/vnd.android.package-archive");
        context.startActivity(intent);
    }


}

MainActivty.java

import android.app.DownloadManager;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.Uri;
import android.os.Bundle;
import android.os.Environment;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;

public class MainActivity extends AppCompatActivity {
    private TextView tvShow;

    private Button btn;


    private DownloadReceiver mReceiver;

    private DownloadManager mDownloadManager;

    private final String URL = "http://f3.market.xiaomi/download/AppStore/0f55a4edf525573555d6ee71b9f8b7b2a0e40d6d0/com.tencent.qqpimsecure.apk";

    private final String APP_PACKAGE = "com.tencent.qqpimsecure";

    private final String APP_NAME = "腾讯手机管家";
    //下载完成标识
    private boolean dFlag = false;
    //是否安装标识
    private boolean iFlag = false;

    private Uri downLoadUri;


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        initViews();
    }

    private void initViews() {
        tvShow = (TextView) findViewById(R.id.tvShow);
        btn = (Button) findViewById(R.id.btn);

        //注册下载接收器
        IntentFilter intentFilter = new IntentFilter();
        intentFilter.addAction(DownloadManager.ACTION_DOWNLOAD_COMPLETE);
        intentFilter.addAction(DownloadManager.ACTION_NOTIFICATION_CLICKED);
        mReceiver = new DownloadReceiver();
        registerReceiver(mReceiver, intentFilter);
        //启动应用

        btn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                AppUtils.startApp(MainActivity.this, APP_PACKAGE);
            }
        });

        //按钮
        btn.setOnClickListener(new View.OnClickListener() {
                                   @Override
                                   public void onClick(View v) {
                                       //如果已经安装,点击启动
                                       if (iFlag) {
                                           AppUtils.startApp(MainActivity.this, APP_PACKAGE);
                                       }
                                       //如果已经下载完毕,点击安装
                                       else if (dFlag) {
                                           AppUtils.installApp(MainActivity.this, downLoadUri);
                                       }
                                       //如果尚未下载,点击下载
                                       else {
                                           mDownloadManager = (DownloadManager) getSystemService(Context.DOWNLOAD_SERVICE);
                                           DownloadManager.Request request = new DownloadManager.Request(Uri.parse(URL));
                                           //设置允许下载的网络类型
                                           request.setAllowedNetworkTypes(DownloadManager.Request.NETWORK_WIFI);
                                           //通知栏进度可见
                                           request.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED);
                                           request.setTitle(APP_NAME);
                                           //设置下载的文件位置
                                           request.setDestinationInExternalFilesDir(MainActivity.this, Environment.DIRECTORY_DOWNLOADS, APP_NAME + ".apk");
                                           mDownloadManager.enqueue(request);
                                           btn.setText("下载中...");
                                       }

                                   }
                               }

        );


    }


    @Override

    protected void onResume() {
        super.onResume();
        if (AppUtils.isAppInstalled(this, APP_PACKAGE)) {
            tvShow.setText("已启用安全防护");
            btn.setText("启动手机管家");
            iFlag = true;

        } else {
            tvShow.setText("尚未安装手机管家");
            iFlag = false;
            if (dFlag) {
                btn.setText("点击安装");
            } else {
                btn.setText("点击下载");
            }

        }


    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        Log.d("app", "onDestroy");
        unregisterReceiver(mReceiver);
    }

    /**
     * 广播接收器,接受ACTION_DOWNLOAD_COMPLETE
     */
    class DownloadReceiver extends BroadcastReceiver {

        @Override
        public void onReceive(Context context, Intent intent) {

            if (intent.getAction().equals(DownloadManager.ACTION_DOWNLOAD_COMPLETE)) {

                long downId = intent.getLongExtra(DownloadManager.EXTRA_DOWNLOAD_ID, -1);
                downLoadUri = mDownloadManager.getUriForDownloadedFile(downId);
                String mimeType = mDownloadManager.getMimeTypeForDownloadedFile(downId);
                Log.d("app", "下载完成->" + downLoadUri.toString() + "mimeType->" + mimeType);
                AppUtils.installApp(context, downLoadUri);
                dFlag = true;
                btn.setText("点击安装");
            }

        }
    }
}

运行效果