转载请注明出处:http://blog.csdn.net/guolin_blog/article/details/47803149

之前有很多朋友都问过我,在Android系统中怎样才能实现静默安装呢?所谓的静默安装,就是不用弹出系统的安装界面,在不影响用户任何操作的情况下不知不觉地将程序装好。虽说这种方式看上去不打搅用户,但是却存在着一个问题,因为Android系统会在安装界面当中把程序所声明的权限展示给用户看,用户来评估一下这些权限然后决定是否要安装该程序,但如果使用了静默安装的方式,也就没有地方让用户看权限了,相当于用户被动接受了这些权限。在Android官方看来,这显示是一种非常危险的行为,因此静默安装这一行为系统是不会开放给开发者的。

但是总是弹出一个安装对话框确实是一种体验比较差的行为,这一点Google自己也意识到了,因此Android系统对自家的Google Play商店开放了静默安装权限,也就是说所有从Google Play上下载的应用都可以不用弹出安装对话框了。这一点充分说明了拥有权限的重要性,自家的系统想怎么改就怎么改。借鉴Google的做法,很多国内的手机厂商也采用了类似的处理方式,比如说小米手机在小米商店中下载应用也是不需要弹出安装对话框的,因为小米可以在MIUI中对Android系统进行各种定制。因此,如果我们只是做一个普通的应用,其实不太需要考虑静默安装这个功能,因为我们只需要将应用上架到相应的商店当中,就会自动拥有静默安装的功能。

但是如果我们想要做的也是一个类似于商店的平台呢?比如说像360手机助手,它广泛安装于各种各样的手机上,但都是作为一个普通的应用存在的,而没有Google或小米这样的特殊权限,那360手机助手应该怎样做到更好的安装体验呢?为此360手机助手提供了两种方案, 秒装(需ROOT权限)和智能安装,如下图示:

因此,今天我们就模仿一下360手机助手的实现方式,来给大家提供一套静默安装的解决方案。

一、秒装

所谓的秒装其实就是需要ROOT权限的静默安装,其实静默安装的原理很简单,就是调用Android系统的pm install命令就可以了,但关键的问题就在于,pm命令系统是不授予我们权限调用的,因此只能在拥有ROOT权限的手机上去申请权限才行。

下面我们开始动手,新建一个InstallTest项目,然后创建一个SilentInstall类作为静默安装功能的实现类,代码如下所示:

  1. /**
  2. * 静默安装的实现类,调用install()方法执行具体的静默安装逻辑。
  3. * 原文地址:http://blog.csdn.net/guolin_blog/article/details/47803149
  4. * @author guolin
  5. * @since 2015/12/7
  6. */
  7. public class SilentInstall {
  8. /**
  9. * 执行具体的静默安装逻辑,需要手机ROOT。
  10. * @param apkPath
  11. *          要安装的apk文件的路径
  12. * @return 安装成功返回true,安装失败返回false。
  13. */
  14. public boolean install(String apkPath) {
  15. boolean result = false;
  16. DataOutputStream dataOutputStream = null;
  17. BufferedReader errorStream = null;
  18. try {
  19. // 申请su权限
  20. Process process = Runtime.getRuntime().exec("su");
  21. dataOutputStream = new DataOutputStream(process.getOutputStream());
  22. // 执行pm install命令
  23. String command = "pm install -r " + apkPath + "\n";
  24. dataOutputStream.write(command.getBytes(Charset.forName("utf-8")));
  25. dataOutputStream.flush();
  26. dataOutputStream.writeBytes("exit\n");
  27. dataOutputStream.flush();
  28. process.waitFor();
  29. errorStream = new BufferedReader(new InputStreamReader(process.getErrorStream()));
  30. String msg = "";
  31. String line;
  32. // 读取命令的执行结果
  33. while ((line = errorStream.readLine()) != null) {
  34. msg += line;
  35. }
  36. Log.d("TAG", "install msg is " + msg);
  37. // 如果执行结果中包含Failure字样就认为是安装失败,否则就认为安装成功
  38. if (!msg.contains("Failure")) {
  39. result = true;
  40. }
  41. } catch (Exception e) {
  42. Log.e("TAG", e.getMessage(), e);
  43. } finally {
  44. try {
  45. if (dataOutputStream != null) {
  46. dataOutputStream.close();
  47. }
  48. if (errorStream != null) {
  49. errorStream.close();
  50. }
  51. } catch (IOException e) {
  52. Log.e("TAG", e.getMessage(), e);
  53. }
  54. }
  55. return result;
  56. }
  57. }

可以看到,SilentInstall类中只有一个install()方法,所有静默安装的逻辑都在这个方法中了,那么我们具体来看一下这个方法。首先在第21行调用了Runtime.getRuntime().exec("su")方法,在这里先申请ROOT权限,不然的话后面的操作都将失败。然后在第24行开始组装静默安装命令,命令的格式就是pm install -r <apk路径>,-r参数表示如果要安装的apk已经存在了就覆盖安装的意思,apk路径是作为方法参数传入的。接下来的几行就是执行上述命令的过程,注意安装这个过程是同步的,因此我们在下面调用了process.waitFor()方法,即安装要多久,我们就要在这里等多久。等待结束之后说明安装过程结束了,接下来我们要去读取安装的结果并进行解析,解析的逻辑也很简单,如果安装结果中包含Failure字样就说明安装失败,反之则说明安装成功。

整个方法还是非常简单易懂的,下面我们就来搭建调用这个方法的环境。修改activity_main.xml中的代码,如下所示:

  1. <?xml version="1.0" encoding="utf-8"?>
  2. <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
  3. xmlns:tools="http://schemas.android.com/tools"
  4. android:layout_width="match_parent"
  5. android:layout_height="match_parent"
  6. android:orientation="vertical"
  7. android:paddingBottom="@dimen/activity_vertical_margin"
  8. android:paddingLeft="@dimen/activity_horizontal_margin"
  9. android:paddingRight="@dimen/activity_horizontal_margin"
  10. android:paddingTop="@dimen/activity_vertical_margin"
  11. tools:context="com.example.installtest.MainActivity">
  12. <LinearLayout
  13. android:layout_width="match_parent"
  14. android:layout_height="wrap_content">
  15. <Button
  16. android:layout_width="wrap_content"
  17. android:layout_height="wrap_content"
  18. android:onClick="onChooseApkFile"
  19. android:text="选择安装包" />
  20. <TextView
  21. android:id="@+id/apkPathText"
  22. android:layout_width="0dp"
  23. android:layout_height="wrap_content"
  24. android:layout_weight="1"
  25. android:layout_gravity="center_vertical"
  26. />
  27. </LinearLayout>
  28. <View
  29. android:layout_width="match_parent"
  30. android:layout_height="1dp"
  31. android:background="@android:color/darker_gray" />
  32. <Button
  33. android:layout_width="wrap_content"
  34. android:layout_height="wrap_content"
  35. android:onClick="onSilentInstall"
  36. android:text="秒装" />
  37. <View
  38. android:layout_width="match_parent"
  39. android:layout_height="1dp"
  40. android:background="@android:color/darker_gray" />
  41. <Button
  42. android:layout_width="wrap_content"
  43. android:layout_height="wrap_content"
  44. android:onClick="onForwardToAccessibility"
  45. android:text="开启智能安装服务" />
  46. <Button
  47. android:layout_width="wrap_content"
  48. android:layout_height="wrap_content"
  49. android:onClick="onSmartInstall"
  50. android:text="智能安装" />
  51. </LinearLayout>

这里我们先将程序的主界面确定好,主界面上拥有四个按钮,第一个按钮用于选择apk文件的,第二个按钮用于开始秒装,第三个按钮用于开启智能安装服务,第四个按钮用于开始智能安装,这里我们暂时只能用到前两个按钮。那么调用SilentInstall的install()方法需要传入apk路径,因此我们需要先把文件选择器的功能实现好,新建activity_file_explorer.xml和list_item.xml作为文件选择器的布局文件,代码分别如下所示:

  1. <?xml version="1.0" encoding="utf-8"?>
  2. <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
  3. android:orientation="vertical"
  4. android:layout_width="match_parent"
  5. android:layout_height="match_parent">
  6. <ListView
  7. android:id="@+id/list_view"
  8. android:layout_width="match_parent"
  9. android:layout_height="match_parent"
  10. />
  11. </LinearLayout>
  1. <?xml version="1.0" encoding="utf-8"?>
  2. <LinearLayout
  3. xmlns:android="http://schemas.android.com/apk/res/android"
  4. android:layout_width="match_parent"
  5. android:layout_height="wrap_content"
  6. android:padding="4dp"
  7. android:orientation="horizontal">
  8. <ImageView android:id="@+id/img"
  9. android:layout_width="32dp"
  10. android:layout_margin="4dp"
  11. android:layout_gravity="center_vertical"
  12. android:layout_height="32dp"/>
  13. <TextView android:id="@+id/name"
  14. android:textSize="18sp"
  15. android:textStyle="bold"
  16. android:layout_width="match_parent"
  17. android:gravity="center_vertical"
  18. android:layout_height="50dp"/>
  19. </LinearLayout>

然后新建FileExplorerActivity作为文件选择器的Activity,代码如下:

  1. public class FileExplorerActivity extends AppCompatActivity implements AdapterView.OnItemClickListener {
  2. ListView listView;
  3. SimpleAdapter adapter;
  4. String rootPath = Environment.getExternalStorageDirectory().getPath();
  5. String currentPath = rootPath;
  6. List<Map<String, Object>> list = new ArrayList<>();
  7. @Override
  8. public void onCreate(Bundle savedInstanceState) {
  9. super.onCreate(savedInstanceState);
  10. setContentView(R.layout.activity_file_explorer);
  11. listView = (ListView) findViewById(R.id.list_view);
  12. adapter = new SimpleAdapter(this, list, R.layout.list_item,
  13. new String[]{"name", "img"}, new int[]{R.id.name, R.id.img});
  14. listView.setAdapter(adapter);
  15. listView.setOnItemClickListener(this);
  16. refreshListItems(currentPath);
  17. }
  18. private void refreshListItems(String path) {
  19. setTitle(path);
  20. File[] files = new File(path).listFiles();
  21. list.clear();
  22. if (files != null) {
  23. for (File file : files) {
  24. Map<String, Object> map = new HashMap<>();
  25. if (file.isDirectory()) {
  26. map.put("img", R.drawable.directory);
  27. } else {
  28. map.put("img", R.drawable.file_doc);
  29. }
  30. map.put("name", file.getName());
  31. map.put("currentPath", file.getPath());
  32. list.add(map);
  33. }
  34. }
  35. adapter.notifyDataSetChanged();
  36. }
  37. @Override
  38. public void onItemClick(AdapterView<?> parent, View v, int position, long id) {
  39. currentPath = (String) list.get(position).get("currentPath");
  40. File file = new File(currentPath);
  41. if (file.isDirectory())
  42. refreshListItems(currentPath);
  43. else {
  44. Intent intent = new Intent();
  45. intent.putExtra("apk_path", file.getPath());
  46. setResult(RESULT_OK, intent);
  47. finish();
  48. }
  49. }
  50. @Override
  51. public void onBackPressed() {
  52. if (rootPath.equals(currentPath)) {
  53. super.onBackPressed();
  54. } else {
  55. File file = new File(currentPath);
  56. currentPath = file.getParentFile().getPath();
  57. refreshListItems(currentPath);
  58. }
  59. }
  60. }

这部分代码由于和我们本篇文件的主旨没什么关系,主要是为了方便demo展示的,因此我就不进行讲解了。

接下来修改MainActivity中的代码,如下所示:

  1. /**
  2. * 仿360手机助手秒装和智能安装功能的主Activity。
  3. * 原文地址:http://blog.csdn.net/guolin_blog/article/details/47803149
  4. * @author guolin
  5. * @since 2015/12/7
  6. */
  7. public class MainActivity extends AppCompatActivity {
  8. TextView apkPathText;
  9. String apkPath;
  10. @Override
  11. protected void onCreate(Bundle savedInstanceState) {
  12. super.onCreate(savedInstanceState);
  13. setContentView(R.layout.activity_main);
  14. apkPathText = (TextView) findViewById(R.id.apkPathText);
  15. }
  16. @Override
  17. protected void onActivityResult(int requestCode, int resultCode, Intent data) {
  18. if (requestCode == 0 && resultCode == RESULT_OK) {
  19. apkPath = data.getStringExtra("apk_path");
  20. apkPathText.setText(apkPath);
  21. }
  22. }
  23. public void onChooseApkFile(View view) {
  24. Intent intent = new Intent(this, FileExplorerActivity.class);
  25. startActivityForResult(intent, 0);
  26. }
  27. public void onSilentInstall(View view) {
  28. if (!isRoot()) {
  29. Toast.makeText(this, "没有ROOT权限,不能使用秒装", Toast.LENGTH_SHORT).show();
  30. return;
  31. }
  32. if (TextUtils.isEmpty(apkPath)) {
  33. Toast.makeText(this, "请选择安装包!", Toast.LENGTH_SHORT).show();
  34. return;
  35. }
  36. final Button button = (Button) view;
  37. button.setText("安装中");
  38. new Thread(new Runnable() {
  39. @Override
  40. public void run() {
  41. SilentInstall installHelper = new SilentInstall();
  42. final boolean result = installHelper.install(apkPath);
  43. runOnUiThread(new Runnable() {
  44. @Override
  45. public void run() {
  46. if (result) {
  47. Toast.makeText(MainActivity.this, "安装成功!", Toast.LENGTH_SHORT).show();
  48. } else {
  49. Toast.makeText(MainActivity.this, "安装失败!", Toast.LENGTH_SHORT).show();
  50. }
  51. button.setText("秒装");
  52. }
  53. });
  54. }
  55. }).start();
  56. }
  57. public void onForwardToAccessibility(View view) {
  58. }
  59. public void onSmartInstall(View view) {
  60. }
  61. /**
  62. * 判断手机是否拥有Root权限。
  63. * @return 有root权限返回true,否则返回false。
  64. */
  65. public boolean isRoot() {
  66. boolean bool = false;
  67. try {
  68. bool = new File("/system/bin/su").exists() || new File("/system/xbin/su").exists();
  69. } catch (Exception e) {
  70. e.printStackTrace();
  71. }
  72. return bool;
  73. }
  74. }

可以看到,在MainActivity中,我们对四个按钮点击事件的回调方法都进行了定义,当点击选择安装包按钮时就会调用onChooseApkFile()方法,当点击秒装按钮时就会调用onSilentInstall()方法。在onChooseApkFile()方法方法中,我们通过Intent打开了FileExplorerActivity,然后在onActivityResult()方法当中读取选择的apk文件路径。在onSilentInstall()方法当中,先判断设备是否ROOT,如果没有ROOT就直接return,然后判断安装包是否已选择,如果没有也直接return。接下来我们开启了一个线程来调用SilentInstall.install()方法,因为安装过程会比较耗时,如果不开线程的话主线程就会被卡住,不管安装成功还是失败,最后都会使用Toast来进行提示。

代码就这么多,最后我们来配置一下AndroidManifest.xml文件:

  1. <?xml version="1.0" encoding="utf-8"?>
  2. <manifest xmlns:android="http://schemas.android.com/apk/res/android"
  3. package="com.example.installtest">
  4. <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
  5. <application
  6. android:allowBackup="true"
  7. android:icon="@mipmap/ic_launcher"
  8. android:label="@string/app_name"
  9. android:supportsRtl="true"
  10. android:theme="@style/AppTheme">
  11. <activity android:name=".MainActivity">
  12. <intent-filter>
  13. <action android:name="android.intent.action.MAIN" />
  14. <category android:name="android.intent.category.LAUNCHER" />
  15. </intent-filter>
  16. </activity>
  17. <activity android:name=".FileExplorerActivity"/>
  18. </application>
  19. </manifest>

并没有什么特殊的地方,由于选择apk文件需要读取SD卡,因此在AndroidManifest.xml文件中要记得声明读SD卡权限。

另外还有一点需要注意,在Android 6.0系统中,读写SD卡权限被列为了危险权限,因此如果将程序的targetSdkVersion指定成了23则需要做专门的6.0适配,这里简单起见,我把targetSdkVersion指定成了22,因为6.0的适配工作也不在文章的讲解范围之内。

现在运行程序,就可以来试一试秒装功能了,切记手机一定要ROOT,效果如下图所示:

可以看到,这里我们选择的网易新闻安装包已成功安装到手机上了,并且没有弹出系统的安装界面,由此证明秒装功能已经成功实现了。

android apk静默安装的更多相关文章

  1. Android实现静默安装与卸载

    一般情况下,Android系统安装apk会出现一个安装界面,用户可以点击确定或者取消来进行apk的安装. 但在实际的项目需求中,有一种需求,就是希望apk在后台安装(不出现安装界面的提示),这种安装方 ...

  2. Android对于静默安装和卸载

    在一般情况下,Android系统安装apk会有一个安装界面,用户可以单击确定或取消apk设备. 但在实际的项目需求中,有一种需求.就是希望apk在后台安装(不出现安装界面的提示),这样的安装方式称为静 ...

  3. Android的静默安装

    原文 Android的静默安装似乎是一个很有趣很诱人的东西,但是,用普通做法,如果手机没有root权限的话,似乎很难实现静默安装,因为Android并不提供显示的Intent调用,一般是通过以下方式安 ...

  4. Android apk的安装、卸载、更新升级(通过Eclipse实现静默安装)

    一.通过Intent消息机制发送消息,调用系统应用进行,实现apk的安装/卸载 . (1) 调用系统的安装应用,让系统自动进行apk的安装 String fileName = "/data/ ...

  5. Android 免Root实现Apk静默安装,覆盖兼容市场主流的98%的机型

    地址:http://blog.csdn.net/sk719887916/article/details/46746991 作者: skay 最近在做apk自我静默更新,在获取内置情况下,或者已root ...

  6. Android为TV端助力 apk静默安装

    转载请注明出处:http://blog.csdn.net/guolin_blog/article/details/47803149 之前有很多朋友都问过我,在Android系统中怎样才能实现静默安装呢 ...

  7. apk 静默安装

    老大要我弄个自动更新,要用到静默安装,网上找到了些大拿的代码,我拿去改吧改吧,先贴出来: /** * 软件静默安装 * @param apkAbsolutePath apk文件所在路径 * @retu ...

  8. APP流氓大法之apk 静默安装

    老大要我弄个自动更新,要用到静默安装,网上找到了些大拿的代码,我拿去改吧改吧,先贴出来: /** * 软件静默安装 * @param apkAbsolutePath apk文件所在路径 * @retu ...

  9. android APK应用安装过程以及默认安装路径[转]

    一:安装过程 APK是类似Symbian Sis或Sisx的文件格式.通过将APK文件直接传到Android模拟器或Android手机中执行即可安装. Android应用安装有如下四种方式 1.   ...

随机推荐

  1. C#串口通信—向串口发送数据,同步接收返回数据

    最近写C#串口通信程序,系统是B/S架构.SerialPort类有一个DataReceived事件,用来接收串口返回的数据,但这种方式在C/S架构下很好用,但B/S就不好处理了.所以写了一个同步模式接 ...

  2. Razor语法的一些特殊需求输出

    开发ASP.NET MVC,常会使用Razor来呈现内容.下面有几个特殊需求的输出,Insus.NET列出来让大家参考. 双@@输出只有一个. 在Razor的语法中,如果想输出html,它会有两种语法 ...

  3. css background 背景图设置

  4. PHP中用GD绘制饼图

    PHP中用GD绘制饼图,绘制的类见代码: Class Chart{ private $image; // 定义图像 private $title; // 定义标题 private $ydata; // ...

  5. hhvm的正确安装姿势 http://dl.hhvm.com 镜像

    hhvm是php的第三方运行环境,由facebook出品,基于该运行环境,它还提供了一种编程语言hack - PHP的静态类型版. 折腾了一天后,包括各种编译.配置.FQ,后面终于忍不住搜了一下 ht ...

  6. FreeBSD应该装gnome3做桌面

    目前freebsd pkg包管理体系的repo源多了一些,速度快了很多. 仓库中目前的版本为3.14,安装gnome3很简单. pkg install xorg gnome3 echo "e ...

  7. Oracle 数据库 基础学习 (一) SQL基本知识

    Oracle 从零开始,不知所措.要掌握一种技能,最好的方式是先学会怎么使用它,然后再深入学习,先有样子,再有技术.   一,什么是数据库? 为什么需要数据库? 数据库实质上是一个信息的列表,或者是一 ...

  8. InfluxDB学习之InfluxDB的安装和简介

    最近用到了 InfluxDB,在此记录下学习过程,同时也希望能够帮助到其他学习的同学. 本文主要介绍InfluxDB的功能特点以及influxDB的安装过程.更多InfluxDB详细教程请看:Infl ...

  9. 利用Canvas编辑图片

    使用<canvas>对象在浏览器中把一幅彩色图片变成灰度图片. grayscale.html <!DOCTYPE html> <html lang="en&qu ...

  10. [转载]OSI七层模型详解

    OSI 七层模型通过七个层次化的结构模型使不同的系统不同的网络之间实现可靠的通讯,因此其最主要的功能就是帮助不同类型的主机实现数据传输 . 完成中继功能的节点通常称为中继系统.在OSI七层模型中,处于 ...