记android精简壳以访问网页方式打开App开机自启并静默安装(需root)

前言:
应公司发展需求,实现android app项目一键打包(即多个项目只需修改部分配置文件,实现一个android工程文件打包多个app项目)。首先需解决的问题就是项目资源替换问题,最终我们采用的是访问网页的形式避免压缩资源替换的复杂度(将app项目发布成网页,通过android壳进行访问,不同app只需替换访问地址即可);其次就是针对客户需求实现静默安装自启和开机自启功能。

一、环境配置
注:以下环境配置只针jdk1.8,若java环境不是该版本,其他环境需对应调整。
java:jdk1.8;
android studio工具的gradle:4.1.3;
打包时的gradle:6.5;
本地gradle(命令行打包需要):6.5;

二、使用android studio新建空的android工程
新建android项目请自行百度,此处不再过多描述,工程建好后做以下简单那配置:
这里先预览一下目录结构:
在这里插入图片描述
新手注意,android studio打开工程时可以选择模式,通常使用Project和Android两种,两种打开的目录结构有差异,我常用Project模式。

首先要使用res目录下的activity_main.xml文件
activity_main.xml:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

  <WebView
    android:id="@+id/activity_main_webview"
    android:layout_width="match_parent"
    android:layout_height="match_parent" />

</RelativeLayout>

我们将通过WebView调用android设备的默认浏览器来访问网页(WebView也可指定浏览器,但不推荐,防止浏览器卸载)。

然后就需要在启动类的onCreate钩子中指定要打开的网页
在这里插入图片描述

MainActivity.java:

package com.ntc.app;

import ...

public class MainActivity extends Activity {
  @Override
  @SuppressLint("SetJavaScriptEnabled")
  protected void onCreate(Bundle savedInstanceState) {
    System.out.println("开始测试!!!!!!!!!!!!!!!!!!!");
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    // Android 4.0 之后不能在主线程中请求HTTP请求
    if (android.os.Build.VERSION.SDK_INT > 9) {
      StrictMode.ThreadPolicy policy = new StrictMode.ThreadPolicy.Builder().permitAll().build();
      StrictMode.setThreadPolicy(policy);
    }
    uniContext = MainActivity.this;
    mWebView = findViewById(R.id.activity_main_webview);
    WebSettings webSettings = mWebView.getSettings();
    webSettings.setJavaScriptEnabled(true);
    mWebView.setWebViewClient(new MyWebViewClient());

    // REMOTE RESOURCE,设置指定网页地址
    //mWebView.loadUrl("你要打开的网址");
    mWebView.loadUrl("http://xxx.xxx/");
  }

  // 首页返回退出
  @Override
  public void onBackPressed() {
    if(mWebView.canGoBack()) {
      mWebView.goBack();
    } else {
      super.onBackPressed();
    }
  }


}

WebViewClient.java:


```cpp
package com.ntc.app;

import android.content.Intent;
import android.net.Uri;
import android.webkit.WebView;
import android.webkit.WebViewClient;

class MyWebViewClient extends WebViewClient {

    @Override
    public boolean shouldOverrideUrlLoading(WebView view, String url) {
        String hostname;

        // YOUR HOSTNAME
        hostname = "example.com";

        Uri uri = Uri.parse(url);
        if (url.startsWith("file:") || uri.getHost() != null && uri.getHost().endsWith(hostname)) {
            return false;
        }
        Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url));
        view.getContext().startActivity(intent);
        return true;
    }
}

到这里app启动就可以打开你指定的网页了。

三、配置开机自启

1、配置AndroidManifest.xml,添加权限(application同级节点)

<!--  接收广播主要用于开机自启  -->
    <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />

2、引入开机广播类(application节点下)

//com.ntc.app为你项目代码的包名,不是打包apk的包名
<receiver
        android:name="com.ntc.app.PlayerReceiver"
        android:enabled="true"
        android:exported="true">
        <intent-filter>
          <action android:name="android.intent.action.MEDIA_MOUNTED" />
          <action android:name="android.intent.action.MEDIA_UNMOUNTED" />
          <action android:name="android.intent.action.MEDIA_EJECT" />
          <action android:name="android.intent.action.MEDIA_REMOVED" />

          <data android:scheme="file" />
        </intent-filter>
        <intent-filter>
          <action android:name="android.intent.action.BOOT_COMPLETED" />
          <category android:name="android.intent.category.DEFAULT"/>
        </intent-filter>
      </receiver>

3、创建开机广播类
在这里插入图片描述
PlayerReceiver.java:

package com.ntc.app;

import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;

public class PlayerReceiver extends BroadcastReceiver {
  private final String ACTION_BOOT = "android.intent.action.BOOT_COMPLETED";

  /**
   * 开机自启广播
   * 接收广播消息后都会进入 onReceie 方法,然后要做的就是对相应的消息做出相应的处理
   *
   * @param context 表示广播接收器所运行的上下文
   * @param intent  表示广播接收器收到的Intent
   */
  @Override
  public void onReceive(Context context, Intent intent) {

    /**
     * 如果 系统 启动的消息,则启动 APP 主页活动
     */
    if (Intent.ACTION_BOOT_COMPLETED.equals(intent.getAction())) {
      //MainActivity.class为app的启动类
      Intent intentMainActivity = new Intent(context, MainActivity.class);
      intentMainActivity.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
      intentMainActivity.setAction("android.intent.action.MAIN");
      intentMainActivity.addCategory("android.intent.category.LAUNCHER");
      context.startActivity(intentMainActivity);
    }
  }
}

四、检测当前版本与网络版本并对比更新
1、创建APKVersionCodeUtils类检测当前版本信息
APKVersionCodeUtils.java:

package com.ntc.app;

import android.content.Context;
import android.content.pm.PackageManager;

public class APKVersionCodeUtils {
  public static int getVersionCode(Context uniContext) {
    int versionCode = 0;
    try {
      //获取软件版本号,对应AndroidManifest.xml下android:versionCode
      versionCode = uniContext.getPackageManager().
        getPackageInfo(uniContext.getPackageName(), 0).versionCode;
    } catch (PackageManager.NameNotFoundException e) {
      e.printStackTrace();
    }
    return versionCode;
  }

  /**
   * 获取版本号名称
   *
   * @param context 上下文
   * @return
   */
  public static String getVerName(Context context) {
    String verName = "";
    try {
      verName = context.getPackageManager().
        getPackageInfo(context.getPackageName(), 0).versionName;
    } catch (PackageManager.NameNotFoundException e) {
      e.printStackTrace();
    }
    return verName;
  }

}

2、创建CheckUpdates类检测网络版本信息
注:在检测网络版本信息时,我是在服务器上准备了一个json文件供测试项目请求并将高版本的安装包放在服务器上供下载

update.json:
在这里插入图片描述

CheckUpdates.java:

package com.ntc.app;


import com.alibaba.fastjson.JSONObject;

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.HashMap;
import java.util.Map;


public class CheckUpdates {

  Map<String, String> check(String URL) {
//    System.out.println("开始检查版本!!!!!!!!!!!!");
    Map<String, String> map = new HashMap<String,String>();
    String ApkUrl = "";
    String Version = "";
    String apkName = "";

    try {
      URL url = new URL(URL);
      HttpURLConnection conn = (HttpURLConnection)url.openConnection();
      InputStreamReader is = new InputStreamReader(conn.getInputStream());
      BufferedReader bufferedReader = new BufferedReader(is);
      StringBuffer strBuffer = new StringBuffer();
      String line = null;
      while (null!=(line = bufferedReader.readLine()))  {
        strBuffer.append(line);
      }

      String objectStr = strBuffer.toString();

      //构建JSON数组对象
      JSONObject jsonObject = JSONObject.parseObject(objectStr);

      ApkUrl = (String) jsonObject.get("url");
      Version = jsonObject.get("version").toString();
//      Integer versionNum = Integer.parseInt(jsonObject.get("version").toString());
      apkName = (String) jsonObject.get("apkName");
      map.put("ApkUrl", ApkUrl);
      map.put("version", Version);
      map.put("apkName", apkName);
      // 获取packagemanager的实例
//      if (Version .equals("1.0.0")) {
//
//      }else {
//
//      }
//      System.out.println(Version+"----"+ApkUrl);
      is.close();
      conn.disconnect();
    } catch (Exception e) {
      e.printStackTrace();
    }
    return map;
  }

}

3、配置AndroidManifest.xml

 <!--  sd卡内创建和删除文件权限  -->
    <uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS" tools:ignore="ProtectedPermissions" />
    <!-- 允许程序向外部存储设备写数据 -->
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
	<application
 		 ...>
 		 ...
 		 <!--   获取文件读取权限   -->
 		 <provider
        android:name="android.support.v4.content.FileProvider"
        android:authorities="com.ntc.app.fileprovider"
        android:exported="false"
        android:grantUriPermissions="true">
        <meta-data
          android:name="android.support.FILE_PROVIDER_PATHS"
          android:resource="@xml/filepaths"
          />
      </provider>
  </application>

4、在res目录下创建xml/filepaths.xml文件
在这里插入图片描述
filepaths.xml:

<paths xmlns:android="http://schemas.android.com/apk/res/android">
  <external-path name="external" path="."/>
</paths>

6、在MainActivity 主类中定时循环检测
MainActivity.java完整代码:

package com.ntc.app;

import android.annotation.SuppressLint;
import android.app.Activity;
import android.content.Context;
import android.os.Bundle;
import android.os.Environment;
import android.os.StrictMode;
import android.webkit.WebSettings;
import android.webkit.WebView;

import java.io.File;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.Map;
import java.util.Timer;
import java.util.TimerTask;

import uni.fvv.packageManager.util.PackageUtils;

public class MainActivity extends Activity {

  private WebView mWebView;
  //  上下文
  private Context uniContext;
  // 定时任务
  private Timer timer;
  private TimerTask timerTask;

  //  判断是否停止
  private boolean mIsCancel = false;
  //  进度
  private int mProgress;
  //  文件保存路径
  private String mSavePath;
  //  版本名称
  private String newApk_name="";
  //  apk下载链接
  private String ApkUrl ="";



  @Override
  @SuppressLint("SetJavaScriptEnabled")
  protected void onCreate(Bundle savedInstanceState) {
  	setStatusBar(); //实现沉浸式状态栏
    System.out.println("开始测试!!!!!!!!!!!!!!!!!!!");
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    // Android 4.0 之后不能在主线程中请求HTTP请求
    if (android.os.Build.VERSION.SDK_INT > 9) {
      StrictMode.ThreadPolicy policy = new StrictMode.ThreadPolicy.Builder().permitAll().build();
      StrictMode.setThreadPolicy(policy);
    }
    uniContext = MainActivity.this;
    mWebView = findViewById(R.id.activity_main_webview);
    WebSettings webSettings = mWebView.getSettings();
    webSettings.setJavaScriptEnabled(true);
    mWebView.setWebViewClient(new MyWebViewClient());

    // REMOTE RESOURCE,设置指定网页地址
    //mWebView.loadUrl("你要初始加载的网页");
    mWebView.loadUrl("https://www.baidu.com/");

    // 获取当前版本信息
    final String versionName = APKVersionCodeUtils.getVerName(this);
    final int versionCode = APKVersionCodeUtils.getVersionCode(this);
//    System.out.println(versionName+"!!1111111 !!"+versionCode);

    //启动后,先检查本地目录是否有安装包,有则删除安装包目录,保证只有一个安装包
    String apk = getExternalFilesDir(null) + "/";
    File apkDir = new File(apk+"apk");
    if (apkDir.exists()){
      apkDir.delete();
    }

    //  定时循环检查版本更新
    timer = new Timer();
    timerTask = new TimerTask() {
      @Override
      public void run() {
        System.out.println("!!!!!!!!!!!!!!!!!!!!定时器执行,请让陆!!!!!!!!!!!!!!!!!!!!!!!!!!!!!");

        // 获取新版本(网络)
        CheckUpdates CheckUpdates = new CheckUpdates();
        Map newVersionInfo = CheckUpdates.check("http://xx.xxx.xx.x:2021/update.json");
        Integer newVersionCode = Integer.parseInt(newVersionInfo.get("version").toString());
        newApk_name = (String) newVersionInfo.get("apkName");
        ApkUrl = (String) newVersionInfo.get("ApkUrl");
        System.out.println(versionName+"!!222222222222222!!"+newVersionCode);

        if(newVersionCode>versionCode){
          // 检测到新版本,执行下载安装
          downloadAPK();

        }else {

        }

      }
    };
    /**
     * 第一个参数:任务
     * 第二个参数:初始启动等待时间
     * 第三个参数:时间间隔
     */
    timer.schedule(timerTask, 1000, 600000);


  }


  // 首页返回退出
  @Override
  public void onBackPressed() {
    if(mWebView.canGoBack()) {
      mWebView.goBack();
    } else {
      super.onBackPressed();
    }
  }



  private void downloadAPK() {
    new Thread(new Runnable() {
      @Override
      public void run() {
        try{
          if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)){
            String sdPath = getExternalFilesDir(null) + "/";
            System.out.println(sdPath+"!!!!!!!!");
//                      文件保存路径
            mSavePath = sdPath + "apk";

            File dir = new File(mSavePath);
            if (!dir.exists()){
              dir.mkdir();
            }
            // 下载文件
            HttpURLConnection conn = (HttpURLConnection) new URL(ApkUrl).openConnection();
            conn.connect();
            InputStream is = conn.getInputStream();
            int length = conn.getContentLength();

            File apkFile = new File(mSavePath, newApk_name);
            FileOutputStream fos = new FileOutputStream(apkFile);

            int count = 0;
            byte[] buffer = new byte[1024];
            while (!mIsCancel){
              int numread = is.read(buffer);
              count += numread;
              // 计算进度条的当前位置
              mProgress = (int) (((float)count/length) * 100);
              // 更新进度条
//              mUpdateProgressHandler.sendEmptyMessage(1);

              // 下载完成
              if (numread < 0){
//                mUpdateProgressHandler.sendEmptyMessage(2);
                // 执行安装(必须root)
                System.out.println("!!!! 下载完成 !!!!");
                int as =  PackageUtils.install(MainActivity.this, mSavePath+"/"+newApk_name);

                break;
              }
              fos.write(buffer, 0, numread);
            }
            fos.close();
            is.close();
          }
        }catch(Exception e){
          e.printStackTrace();
        }
      }
    }).start();
  }
//是否使用特殊的标题栏背景颜色,android5.0以上可以设置状态栏背景色,如果不使用则使用透明色值
  protected boolean useThemestatusBarColor = false;
  //是否使用状态栏文字和图标为暗色,如果状态栏采用了白色系,则需要使状态栏和图标为暗色,android6.0以上可以设置
  protected boolean useStatusBarColor = true;
  //设置沉浸式状态栏
  protected void setStatusBar() {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {//5.0及以上
      View decorView = getWindow().getDecorView();
      int option = View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
        | View.SYSTEM_UI_FLAG_LAYOUT_STABLE;
      decorView.setSystemUiVisibility(option);
      //根据上面设置是否对状态栏单独设置颜色
      if (useThemestatusBarColor) {
        //getWindow().setStatusBarColor(getResources().getColor(R.color.colorTheme));//设置状态栏背景色
      } else {
        getWindow().setStatusBarColor(Color.TRANSPARENT);//透明
      }
    } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {//4.4到5.0
      WindowManager.LayoutParams localLayoutParams = getWindow().getAttributes();
      localLayoutParams.flags = (WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS | localLayoutParams.flags);
    } else {
      Toast.makeText(this, "低于4.4的android系统版本不存在沉浸式状态栏", Toast.LENGTH_SHORT).show();
    }

    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && useStatusBarColor) {//android6.0以后可以对状态栏文字颜色和图标进行修改
      getWindow().getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR);
    }
  }


}

5、新建安装方法包PackageUtils.install(install方法可网上百度)并添加更新重启依赖模块
packagemanager-release.aar(可网上下载),uni.fvv.packageManager.util在packagemanager-release.aar包内。
uni插件下载地址uni.fvv.packageManager.util

将packagemanager-release.aar包放入libs文件下
在这里插入图片描述

PackageUtils.java:

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//

package uni.fvv.packageManager.util;

import android.annotation.SuppressLint;
import android.app.ActivityManager;
import android.app.ActivityManager.RunningAppProcessInfo;
import android.app.ActivityManager.RunningTaskInfo;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.graphics.Bitmap;
import android.graphics.Bitmap.CompressFormat;
import android.net.Uri;
import android.os.Build.VERSION;
import android.util.Base64;
import android.util.Log;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.util.Iterator;
import java.util.List;
import uni.fvv.packageManager.util.ShellUtils.CommandResult;

public class PackageUtils {
  public static final String TAG = "PackageUtils";
  public static final int APP_INSTALL_AUTO = 0;
  public static final int APP_INSTALL_INTERNAL = 1;
  public static final int APP_INSTALL_EXTERNAL = 2;
  public static final int INSTALL_SUCCEEDED = 1;
  public static final int INSTALL_FAILED_ALREADY_EXISTS = -1;
  public static final int INSTALL_FAILED_INVALID_APK = -2;
  public static final int INSTALL_FAILED_INVALID_URI = -3;
  public static final int INSTALL_FAILED_INSUFFICIENT_STORAGE = -4;
  public static final int INSTALL_FAILED_DUPLICATE_PACKAGE = -5;
  public static final int INSTALL_FAILED_NO_SHARED_USER = -6;
  public static final int INSTALL_FAILED_UPDATE_INCOMPATIBLE = -7;
  public static final int INSTALL_FAILED_SHARED_USER_INCOMPATIBLE = -8;
  public static final int INSTALL_FAILED_MISSING_SHARED_LIBRARY = -9;
  public static final int INSTALL_FAILED_REPLACE_COULDNT_DELETE = -10;
  public static final int INSTALL_FAILED_DEXOPT = -11;
  public static final int INSTALL_FAILED_OLDER_SDK = -12;
  public static final int INSTALL_FAILED_CONFLICTING_PROVIDER = -13;
  public static final int INSTALL_FAILED_NEWER_SDK = -14;
  public static final int INSTALL_FAILED_TEST_ONLY = -15;
  public static final int INSTALL_FAILED_CPU_ABI_INCOMPATIBLE = -16;
  public static final int INSTALL_FAILED_MISSING_FEATURE = -17;
  public static final int INSTALL_FAILED_CONTAINER_ERROR = -18;
  public static final int INSTALL_FAILED_INVALID_INSTALL_LOCATION = -19;
  public static final int INSTALL_FAILED_MEDIA_UNAVAILABLE = -20;
  public static final int INSTALL_FAILED_VERIFICATION_TIMEOUT = -21;
  public static final int INSTALL_FAILED_VERIFICATION_FAILURE = -22;
  public static final int INSTALL_FAILED_PACKAGE_CHANGED = -23;
  public static final int INSTALL_FAILED_UID_CHANGED = -24;
  public static final int INSTALL_PARSE_FAILED_NOT_APK = -100;
  public static final int INSTALL_PARSE_FAILED_BAD_MANIFEST = -101;
  public static final int INSTALL_PARSE_FAILED_UNEXPECTED_EXCEPTION = -102;
  public static final int INSTALL_PARSE_FAILED_NO_CERTIFICATES = -103;
  public static final int INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES = -104;
  public static final int INSTALL_PARSE_FAILED_CERTIFICATE_ENCODING = -105;
  public static final int INSTALL_PARSE_FAILED_BAD_PACKAGE_NAME = -106;
  public static final int INSTALL_PARSE_FAILED_BAD_SHARED_USER_ID = -107;
  public static final int INSTALL_PARSE_FAILED_MANIFEST_MALFORMED = -108;
  public static final int INSTALL_PARSE_FAILED_MANIFEST_EMPTY = -109;
  public static final int INSTALL_FAILED_INTERNAL_ERROR = -110;
  public static final int INSTALL_FAILED_OTHER = -1000000;
  public static final int DELETE_SUCCEEDED = 1;
  public static final int DELETE_FAILED_INTERNAL_ERROR = -1;
  public static final int DELETE_FAILED_DEVICE_POLICY_MANAGER = -2;
  public static final int DELETE_FAILED_INVALID_PACKAGE = -3;
  public static final int DELETE_FAILED_PERMISSION_DENIED = -4;

  private PackageUtils() {
    throw new AssertionError();
  }

  public static final int install(Context context, String filePath) {
    if (!isSystemApplication(context) && !uni.fvv.packageManager.util.ShellUtils.checkRootPermission()) {
      return installNormal(context, filePath) ? 1 : -3;
    } else {
      return installSilent(context, filePath);
    }
  }

  @SuppressLint("WrongConstant")
  public static boolean installNormal(Context context, String filePath) {
    Intent i = new Intent("android.intent.action.VIEW");
    File file = new File(filePath);
    if (file != null && file.exists() && file.isFile() && file.length() > 0L) {
      i.setDataAndType(Uri.parse("file://" + filePath), "application/vnd.android.package-archive");
      i.addFlags(268435456);
      context.startActivity(i);
      return true;
    } else {
      return false;
    }
  }

  public static int installSilent(Context context, String filePath) {
    return installSilent(context, filePath, " -r " + getInstallLocationParams());
  }

  public static int installSilent(Context context, String filePath, String pmParams) {
    if (filePath != null && filePath.length() != 0) {
      File file = new File(filePath);
      if (file != null && file.length() > 0L && file.exists() && file.isFile()) {
        StringBuilder command = (new StringBuilder()).append("LD_LIBRARY_PATH=/vendor/lib*:/system/lib* pm install ").append(pmParams == null ? "" : pmParams).append(" ").append(filePath.replace(" ", "\\ "));
        CommandResult commandResult = uni.fvv.packageManager.util.ShellUtils.execCommand(command.toString(), !isSystemApplication(context), true);
        if (commandResult.successMsg == null || !commandResult.successMsg.contains("Success") && !commandResult.successMsg.contains("success")) {
          Log.e("PackageUtils", "installSilent successMsg:" + commandResult.successMsg + ", ErrorMsg:" + commandResult.errorMsg);
          if (commandResult.errorMsg == null) {
            return -1000000;
          } else if (commandResult.errorMsg.contains("INSTALL_FAILED_ALREADY_EXISTS")) {
            return -1;
          } else if (commandResult.errorMsg.contains("INSTALL_FAILED_INVALID_APK")) {
            return -2;
          } else if (commandResult.errorMsg.contains("INSTALL_FAILED_INVALID_URI")) {
            return -3;
          } else if (commandResult.errorMsg.contains("INSTALL_FAILED_INSUFFICIENT_STORAGE")) {
            return -4;
          } else if (commandResult.errorMsg.contains("INSTALL_FAILED_DUPLICATE_PACKAGE")) {
            return -5;
          } else if (commandResult.errorMsg.contains("INSTALL_FAILED_NO_SHARED_USER")) {
            return -6;
          } else if (commandResult.errorMsg.contains("INSTALL_FAILED_UPDATE_INCOMPATIBLE")) {
            return -7;
          } else if (commandResult.errorMsg.contains("INSTALL_FAILED_SHARED_USER_INCOMPATIBLE")) {
            return -8;
          } else if (commandResult.errorMsg.contains("INSTALL_FAILED_MISSING_SHARED_LIBRARY")) {
            return -9;
          } else if (commandResult.errorMsg.contains("INSTALL_FAILED_REPLACE_COULDNT_DELETE")) {
            return -10;
          } else if (commandResult.errorMsg.contains("INSTALL_FAILED_DEXOPT")) {
            return -11;
          } else if (commandResult.errorMsg.contains("INSTALL_FAILED_OLDER_SDK")) {
            return -12;
          } else if (commandResult.errorMsg.contains("INSTALL_FAILED_CONFLICTING_PROVIDER")) {
            return -13;
          } else if (commandResult.errorMsg.contains("INSTALL_FAILED_NEWER_SDK")) {
            return -14;
          } else if (commandResult.errorMsg.contains("INSTALL_FAILED_TEST_ONLY")) {
            return -15;
          } else if (commandResult.errorMsg.contains("INSTALL_FAILED_CPU_ABI_INCOMPATIBLE")) {
            return -16;
          } else if (commandResult.errorMsg.contains("INSTALL_FAILED_MISSING_FEATURE")) {
            return -17;
          } else if (commandResult.errorMsg.contains("INSTALL_FAILED_CONTAINER_ERROR")) {
            return -18;
          } else if (commandResult.errorMsg.contains("INSTALL_FAILED_INVALID_INSTALL_LOCATION")) {
            return -19;
          } else if (commandResult.errorMsg.contains("INSTALL_FAILED_MEDIA_UNAVAILABLE")) {
            return -20;
          } else if (commandResult.errorMsg.contains("INSTALL_FAILED_VERIFICATION_TIMEOUT")) {
            return -21;
          } else if (commandResult.errorMsg.contains("INSTALL_FAILED_VERIFICATION_FAILURE")) {
            return -22;
          } else if (commandResult.errorMsg.contains("INSTALL_FAILED_PACKAGE_CHANGED")) {
            return -23;
          } else if (commandResult.errorMsg.contains("INSTALL_FAILED_UID_CHANGED")) {
            return -24;
          } else if (commandResult.errorMsg.contains("INSTALL_PARSE_FAILED_NOT_APK")) {
            return -100;
          } else if (commandResult.errorMsg.contains("INSTALL_PARSE_FAILED_BAD_MANIFEST")) {
            return -101;
          } else if (commandResult.errorMsg.contains("INSTALL_PARSE_FAILED_UNEXPECTED_EXCEPTION")) {
            return -102;
          } else if (commandResult.errorMsg.contains("INSTALL_PARSE_FAILED_NO_CERTIFICATES")) {
            return -103;
          } else if (commandResult.errorMsg.contains("INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES")) {
            return -104;
          } else if (commandResult.errorMsg.contains("INSTALL_PARSE_FAILED_CERTIFICATE_ENCODING")) {
            return -105;
          } else if (commandResult.errorMsg.contains("INSTALL_PARSE_FAILED_BAD_PACKAGE_NAME")) {
            return -106;
          } else if (commandResult.errorMsg.contains("INSTALL_PARSE_FAILED_BAD_SHARED_USER_ID")) {
            return -107;
          } else if (commandResult.errorMsg.contains("INSTALL_PARSE_FAILED_MANIFEST_MALFORMED")) {
            return -108;
          } else if (commandResult.errorMsg.contains("INSTALL_PARSE_FAILED_MANIFEST_EMPTY")) {
            return -109;
          } else {
            return commandResult.errorMsg.contains("INSTALL_FAILED_INTERNAL_ERROR") ? -110 : -1000000;
          }
        } else {
          return 1;
        }
      } else {
        return -3;
      }
    } else {
      return -3;
    }
  }

  public static final int uninstall(Context context, String packageName) {
    if (!isSystemApplication(context) && !uni.fvv.packageManager.util.ShellUtils.checkRootPermission()) {
      return uninstallNormal(context, packageName) ? 1 : -3;
    } else {
      return uninstallSilent(context, packageName);
    }
  }

  @SuppressLint("WrongConstant")
  public static boolean uninstallNormal(Context context, String packageName) {
    if (packageName != null && packageName.length() != 0) {
      Intent i = new Intent("android.intent.action.DELETE", Uri.parse((new StringBuilder(32)).append("package:").append(packageName).toString()));
      i.addFlags(268435456);
      context.startActivity(i);
      return true;
    } else {
      return false;
    }
  }

  public static int uninstallSilent(Context context, String packageName) {
    return uninstallSilent(context, packageName, true);
  }

  public static int uninstallSilent(Context context, String packageName, boolean isKeepData) {
    if (packageName != null && packageName.length() != 0) {
      StringBuilder command = (new StringBuilder()).append("LD_LIBRARY_PATH=/vendor/lib*:/system/lib* pm uninstall").append(isKeepData ? " -k " : " ").append(packageName.replace(" ", "\\ "));
      CommandResult commandResult = uni.fvv.packageManager.util.ShellUtils.execCommand(command.toString(), !isSystemApplication(context), true);
      if (commandResult.successMsg != null && (commandResult.successMsg.contains("Success") || commandResult.successMsg.contains("success"))) {
        return 1;
      } else {
        Log.e("PackageUtils", "uninstallSilent successMsg:" + commandResult.successMsg + ", ErrorMsg:" + commandResult.errorMsg);
        if (commandResult.errorMsg == null) {
          return -1;
        } else {
          return commandResult.errorMsg.contains("Permission denied") ? -4 : -1;
        }
      }
    } else {
      return -3;
    }
  }

  public static boolean isSystemApplication(Context context) {
    return context == null ? false : isSystemApplication(context, context.getPackageName());
  }

  public static boolean isSystemApplication(Context context, String packageName) {
    return context == null ? false : isSystemApplication(context.getPackageManager(), packageName);
  }

  public static boolean isSystemApplication(PackageManager packageManager, String packageName) {
    if (packageManager != null && packageName != null && packageName.length() != 0) {
      try {
        ApplicationInfo app = packageManager.getApplicationInfo(packageName, 0);
        return app != null && (app.flags & 1) > 0;
      } catch (NameNotFoundException var3) {
        var3.printStackTrace();
        return false;
      }
    } else {
      return false;
    }
  }

  public static Boolean isTopActivity(Context context, String packageName) {
    if (context != null && !uni.fvv.packageManager.util.StringUtils.isEmpty(packageName)) {
      ActivityManager activityManager = (ActivityManager)context.getSystemService("activity");
      List<RunningTaskInfo> tasksInfo = activityManager.getRunningTasks(1);
      if (ListUtils.isEmpty(tasksInfo)) {
        return null;
      } else {
        try {
          return packageName.equals(((RunningTaskInfo)tasksInfo.get(0)).topActivity.getPackageName());
        } catch (Exception var5) {
          var5.printStackTrace();
          return false;
        }
      }
    } else {
      return null;
    }
  }

  public static String getTopPackageName(Context context) {
    if (context == null) {
      return null;
    } else {
      ActivityManager activityManager = (ActivityManager)context.getSystemService("activity");
      List<RunningTaskInfo> tasksInfo = activityManager.getRunningTasks(1);
      if (ListUtils.isEmpty(tasksInfo)) {
        return null;
      } else {
        try {
          return ((RunningTaskInfo)tasksInfo.get(0)).topActivity.getPackageName();
        } catch (Exception var4) {
          return null;
        }
      }
    }
  }

  public static boolean isRunning(Context context, String packageName) {
    ActivityManager am = (ActivityManager)context.getSystemService("activity");
    List<RunningAppProcessInfo> infos = am.getRunningAppProcesses();
    Iterator var4 = infos.iterator();

    RunningAppProcessInfo rapi;
    do {
      if (!var4.hasNext()) {
        return false;
      }

      rapi = (RunningAppProcessInfo)var4.next();
    } while(!rapi.processName.equals(packageName));

    return true;
  }

  public static boolean checkPackageInstall(Context context, String packageName) {
    PackageInfo packageInfo = null;

    try {
      packageInfo = context.getPackageManager().getPackageInfo(packageName, 0);
    } catch (NameNotFoundException var4) {
      var4.printStackTrace();
    }

    return packageInfo != null;
  }

  @SuppressLint("WrongConstant")
  public static void openApp(Context context, String packageName, JSONObject extra) {
    Intent intent = context.getPackageManager().getLaunchIntentForPackage(packageName);
    if (packageName.contains(":")) {
      intent = new Intent();
      intent.setData(Uri.parse(packageName));
    }

    if (intent != null) {
      if (extra != null) {
        Iterator var4 = extra.keySet().iterator();

        while(var4.hasNext()) {
          String item = (String)var4.next();
          Log.i(item, extra.getString(item));
          intent.putExtra(item, extra.getString(item));
        }
      }

      intent.setFlags(268435456);
      context.startActivity(intent);
    }

  }

  public static JSONArray getAppList(Context context) {
    PackageManager packageManager = context.getPackageManager();
    Intent mainIntent = new Intent("android.intent.action.MAIN", (Uri)null);
    mainIntent.addCategory("android.intent.category.LAUNCHER");
    JSONArray jsonArray = new JSONArray();
    List<PackageInfo> apps = packageManager.getInstalledPackages(0);

    for(int i = 0; i < apps.size(); ++i) {
      JSONObject jsonObject = new JSONObject();
      jsonObject.put("packageName", ((PackageInfo)apps.get(i)).packageName);
      jsonObject.put("appName", ((PackageInfo)apps.get(i)).applicationInfo.loadLabel(packageManager).toString());
      jsonObject.put("version", ((PackageInfo)apps.get(i)).versionName);
      boolean isSystem = false;
      if ((1 & ((PackageInfo)apps.get(i)).applicationInfo.flags) != 0) {
        isSystem = true;
      }

      jsonObject.put("isSystem", isSystem);
      jsonArray.add(jsonObject);
    }

    return jsonArray;
  }

  private static String bitmap2base64(Bitmap bitmap) {
    ByteArrayOutputStream baos = new ByteArrayOutputStream();
    bitmap.compress(CompressFormat.PNG, 100, baos);
    byte[] bytes = baos.toByteArray();
    return Base64.encodeToString(bytes, 0);
  }

  public static int getAppVersionCode(Context context) {
    if (context != null) {
      PackageManager pm = context.getPackageManager();
      if (pm != null) {
        try {
          PackageInfo pi = pm.getPackageInfo(context.getPackageName(), 0);
          if (pi != null) {
            return pi.versionCode;
          }
        } catch (NameNotFoundException var4) {
          var4.printStackTrace();
        }
      }
    }

    return -1;
  }

  public static int getInstallLocation() {
    CommandResult commandResult = uni.fvv.packageManager.util.ShellUtils.execCommand("LD_LIBRARY_PATH=/vendor/lib*:/system/lib* pm get-install-location", false, true);
    if (commandResult.result == 0 && commandResult.successMsg != null && commandResult.successMsg.length() > 0) {
      try {
        int location = Integer.parseInt(commandResult.successMsg.substring(0, 1));
        switch(location) {
          case 1:
            return 1;
          case 2:
            return 2;
        }
      } catch (NumberFormatException var2) {
        var2.printStackTrace();
        Log.e("PackageUtils", "pm get-install-location error");
      }
    }

    return 0;
  }

  private static String getInstallLocationParams() {
    int location = getInstallLocation();
    switch(location) {
      case 1:
        return "-f";
      case 2:
        return "-s";
      default:
        return "";
    }
  }

  @SuppressLint("WrongConstant")
  public static void startInstalledAppDetails(Context context, String packageName) {
    Intent intent = new Intent();
    int sdkVersion = VERSION.SDK_INT;
    if (VERSION.SDK_INT >= 9) {
      intent.setAction("android.settings.APPLICATION_DETAILS_SETTINGS");
      intent.setData(Uri.fromParts("package", packageName, (String)null));
    } else {
      intent.setAction("android.intent.action.VIEW");
      intent.setClassName("com.android.settings", "com.android.settings.InstalledAppDetails");
      intent.putExtra(sdkVersion == 8 ? "pkg" : "com.android.settings.ApplicationPkgName", packageName);
    }

    intent.addFlags(268435456);
    context.startActivity(intent);
  }
}

本着代码完整性,特在此放上AndroidManifest.xml、build.gradle的完整代码以作记录(防止我忘记了)
AndroidManifest.xml:

<?xml version="1.0" encoding="utf-8"?>
<manifest
    package="com.ntc.app"
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools">
    <uses-permission android:name="android.permission.INTERNET" />

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

    <!--  sd卡内创建和删除文件权限  -->
    <uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS" tools:ignore="ProtectedPermissions" />
    <!-- 允许程序向外部存储设备写数据 -->
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
    <!--  接收广播主要用于开机自启  -->
    <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
    <application

        android:allowBackup="true"
        android:fullBackupContent="true"
        android:icon="@mipmap/icon"
        android:label="@string/app_name"
        android:theme="@style/AppTheme"
        android:usesCleartextTraffic="true"
        tools:ignore="GoogleAppIndexingWarning">
        <activity
            android:configChanges="orientation|screenSize"
            android:name="com.ntc.app.MainActivity"
            android:label="@string/app_name"
            android:exported="true">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>

      <receiver
        android:name="com.ntc.app.PlayerReceiver"
        android:enabled="true"
        android:exported="true">
        <intent-filter>
          <action android:name="android.intent.action.MEDIA_MOUNTED" />
          <action android:name="android.intent.action.MEDIA_UNMOUNTED" />
          <action android:name="android.intent.action.MEDIA_EJECT" />
          <action android:name="android.intent.action.MEDIA_REMOVED" />

          <data android:scheme="file" />
        </intent-filter>
        <intent-filter>
          <action android:name="android.intent.action.BOOT_COMPLETED" />
          <category android:name="android.intent.category.DEFAULT"/>
        </intent-filter>
      </receiver>
      <!--   获取文件读取权限   -->
      <provider
        android:name="android.support.v4.content.FileProvider"
        android:authorities="com.ntc.app.fileprovider"
        android:exported="false"
        android:grantUriPermissions="true">
        <meta-data
          android:name="android.support.FILE_PROVIDER_PATHS"
          android:resource="@xml/filepaths"
          />
      </provider>

      <!--        <receiver android:name="uni.fvv.packageManager.ReplaceAddRemoveBroadcastReceiver" >-->
<!--          <intent-filter>-->
<!--            <action android:name="android.intent.action.PACKAGE_REPLACED" />-->

<!--            <data android:scheme="package" />-->
<!--          </intent-filter>-->
<!--        </receiver>-->

    </application>
</manifest>

build.gradle:

apply plugin: 'com.android.application'

android {
    compileSdkVersion 30

    defaultConfig {
        applicationId "com.ntc2.app"
        minSdkVersion 21
        targetSdkVersion 30
        versionCode 1
        versionName "1.0"
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }

    aaptOptions {
      additionalParameters '--auto-add-overlay'
      ignoreAssetsPattern "!.svn:!.git:.*:!CVS:!thumbs.db:!picasa.ini:!*.scc:*~"
    }

    android.applicationVariants.all {
      variant ->
        variant.outputs.all {
          //在这里修改apk文件名,引号内的字符串都可以随便定义
          outputFileName = "webtest-v${variant.versionName}.apk"
//          outputFileName = "webtest.apk"
        }
    }
}

//repositories {
//  flatDir {
//    dirs 'libs'
//  }
//}

dependencies {
//    implementation fileTree(dir: 'libs', include: ['*.jar'])
  implementation fileTree(dir: 'libs', include: ['*.aar', '*.jar'], exclude: [])
  implementation "com.android.support:support-v4:28.0.0"
  //noinspection GradleCompatible
  implementation "com.android.support:appcompat-v7:28.0.0"
  implementation 'com.android.support:recyclerview-v7:28.0.0'
  implementation 'com.facebook.fresco:fresco:1.13.0'
  implementation "com.facebook.fresco:animated-gif:1.13.0"
  implementation 'com.github.bumptech.glide:glide:4.9.0'
  implementation 'com.alibaba:fastjson:1.1.46.android'
  implementation 'org.apache.commons:commons-io:1.3.2'

//  compile(name: 'lib.5plus.base-release', ext: 'aar')
//  api(name: 'lib.5plus.base-release', ext: 'aar')
}

至此就可使用android studio打包了
在这里插入图片描述
但要实现一键打包就必须使用命令行打包:
cd至工程目录:
在这里插入图片描述
执行命令

//清空build打包目录防止不同项目代码冗余冲突
gradle clean
//进行debug打包
gradle assembleDebug

更多打包命令可自行百度

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值