转载请注明出处: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. 用Latex写学术论文:作者(Author)&摘要(Abstract)

    标题&作者 1.标题 \title{} "Line breaks (\\) may be used to equalize the length of the title lines ...

  2. 爬虫--Scrapy

    Scrapy Scrapy是一个为了爬取网站数据,提取结构性数据而编写的应用框架. 其可以应用在数据挖掘,信息处理或存储历史数据等一系列的程序中.其最初是为了页面抓取 (更确切来说, 网络抓取 )所设 ...

  3. Zip 压缩、解压技术在 HTML5 浏览器中的应用

    JSZip 是一款可以创建.读取.修改 .zip 文件的 javaScript 工具.在 web 应用中,免不了需要从 web 服务器中获取资源,如果可以将所有的资源都合并到一个 .zip 文件中,这 ...

  4. es6继承 vs js原生继承(es5)

    最近在看es2015的一些语法,最实用的应该就是继承这个新特性了.比如下面的代码: $(function(){ class Father{ constructor(name, age){ this.n ...

  5. WinForm中DataGridView显示更新数据--人性版

    using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; usin ...

  6. C#删除程序自身【总结】

    偶然看到一个可以自删除的程序,于是了解下如何实现.然后整理如下: 思路: 在.NET程序中,因为运行中的程序是受系统保护的,不能自己删除自身的,所以自删除的思路:  在关闭本程序之前启动新的进程打开另 ...

  7. Thinkphp 用PHPExcel 导入Excel

    搞了个简单的Excel导入, 用的是PHPExcel(百科:用来操作Office Excel文档的一个PHP类库, 基于微软的OpenXML标准和PHP语言) 好, 不说了, 开始吧... 首先得有P ...

  8. IO复用_select函数

    select函数: #include <sys/select.h> #include <time.h> #include <sys/types.h> #includ ...

  9. [ASP.NET MVC] 使用CLK.AspNet.Identity提供依权限显示选单项目的功能

    [ASP.NET MVC] 使用CLK.AspNet.Identity提供依权限显示选单项目的功能 CLK.AspNet.Identity CLK.AspNet.Identity是一个基于ASP.NE ...

  10. [转]精通JS正则表达式

    原文路径:http://www.jb51.net/article/25313.htm 正则表达式可以: •测试字符串的某个模式.例如,可以对一个输入字符串进行测试,看在该字符串是否存在一个电话号码模式 ...