Commit 518ec14a by pye52

Merge branch 'new_master' into new_for_phone

parents 08f83b07 3adb7c35
......@@ -13,6 +13,7 @@ android {
ndk {
abiFilters "armeabi", "armeabi-v7a", "x86", "mips"
}
buildConfigField "int", "DaemonVersion", String.valueOf(rootProject.ext.daemon_verson_code)
}
buildTypes {
debug {
......@@ -90,8 +91,6 @@ android {
}
lintOptions {
checkReleaseBuilds false
// Or, if you prefer, you can continue to check for errors in release builds,
// but continue the build even when errors are found:
abortOnError false
}
}
......@@ -119,4 +118,5 @@ dependencies {
def lifecycle_version = "2.2.0"
implementation "androidx.lifecycle:lifecycle-extensions:$lifecycle_version"
implementation "androidx.lifecycle:lifecycle-common-java8:$lifecycle_version"
implementation 'com.liulishuo.filedownloader:library:1.7.7'
}
......@@ -4,22 +4,25 @@ import android.app.Application;
import com.blankj.utilcode.util.CrashUtils;
import com.blankj.utilcode.util.LogUtils;
import com.blankj.utilcode.util.PathUtils;
import com.blankj.utilcode.util.Utils;
import java.io.File;
public class RootApp extends Application {
private static final String LOG_PREFIX = "app";
private static final String LOG_DIR = "log";
@Override
public void onCreate() {
super.onCreate();
Utils.init(getApplicationContext());
String logDir = getApplicationContext().getFilesDir().getAbsolutePath() + File.separator + LOG_DIR;
String logDir = PathUtils.getExternalStoragePath() + File.separator + LOG_DIR;
CrashUtils.init(logDir);
LogUtils.getConfig()
.setDir(logDir)
.setLog2FileSwitch(true)
.setBorderSwitch(false);
.setBorderSwitch(false)
.setFilePrefix(LOG_PREFIX);
}
}
......@@ -20,7 +20,7 @@ public class SCRetrofit {
return retrofit.create(SCApi.class);
}
public static OkHttpClient.Builder createOkHttpClient() {
public static OkHttpClient.Builder createOkHttpClientBuilder() {
OkHttpClient.Builder builder = new OkHttpClient.Builder()
.connectTimeout(TIMEOUT, TimeUnit.SECONDS)
.readTimeout(TIMEOUT, TimeUnit.SECONDS)
......
......@@ -179,7 +179,7 @@ public class LogCommandHandler extends CommandHandler {
}
private void upload(File zip) {
OkHttpClient client = SCRetrofit.createOkHttpClient().build();
OkHttpClient client = SCRetrofit.createOkHttpClientBuilder().build();
SCApi api = SCRetrofit.createApi(client);
CommandLog.CommandLogData data = commandLog.getData();
String fileNameForServer = data.getLogType()
......
package com.bgycc.smartcanteen.command;
import com.bgycc.smartcanteen.BuildConfig;
import com.bgycc.smartcanteen.api.SCRetrofit;
import com.bgycc.smartcanteen.entity.Command;
import com.bgycc.smartcanteen.entity.CommandResponse;
import com.bgycc.smartcanteen.entity.CommandUpdate;
import com.bgycc.smartcanteen.entity.ProgressResponseBody;
import com.bgycc.smartcanteen.utils.DeviceProxy;
import com.blankj.utilcode.util.AppUtils;
import com.blankj.utilcode.util.FileIOUtils;
import com.blankj.utilcode.util.FileUtils;
import com.blankj.utilcode.util.LogUtils;
import com.blankj.utilcode.util.PathUtils;
import com.blankj.utilcode.util.Utils;
import com.google.gson.Gson;
import org.jetbrains.annotations.NotNull;
import com.liulishuo.filedownloader.BaseDownloadTask;
import com.liulishuo.filedownloader.FileDownloadListener;
import com.liulishuo.filedownloader.FileDownloadSampleListener;
import com.liulishuo.filedownloader.FileDownloader;
import java.io.File;
import java.io.IOException;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import okhttp3.Call;
import okhttp3.Callback;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import okhttp3.ResponseBody;
import static com.bgycc.smartcanteen.utils.SmartCanteenUtils.TAG;
/**
......@@ -38,18 +25,12 @@ import static com.bgycc.smartcanteen.utils.SmartCanteenUtils.TAG;
*/
public class UpdateCommandHandler extends CommandHandler {
private static final String UPDATE_APK = "SmartCanteen-update.apk";
// 下载超时为60s
private static final long TIMEOUT = 60 * 1000;
private static final String UPDATE_APK_PATH = PathUtils.getExternalStoragePath() + File.separator + UPDATE_APK;
private static final long DEFAULT_DELAY = 5 * 1000;
// 保证更新任务不会在同一时间内重复执行
private volatile boolean start = false;
private OkHttpClient httpClient;
private CommandUpdate commandUpdate;
private File updateApk;
// 避免下载异常,这里创建自己的线程池(毕竟收到更新通知时一般都会进入安装流程,无需担心开销)
private ScheduledExecutorService executor;
private ScheduledFuture<?> timeoutFuture;
private static UpdateCommandHandler instance;
......@@ -66,19 +47,7 @@ public class UpdateCommandHandler extends CommandHandler {
private UpdateCommandHandler(Command command, Gson gson, CommandProgressCallback callback) {
super(command, gson, callback);
this.httpClient = SCRetrofit.createOkHttpClient()
.addNetworkInterceptor(chain -> {
Response originalResponse = chain.proceed(chain.request());
return originalResponse.newBuilder()
.body(new ProgressResponseBody(originalResponse.body(), (progress, total, done) -> {
int per = (int) (progress * 1f / total * 100);
wait("下载进度: " + per + "%", per);
}))
.build();
})
.build();
this.commandUpdate = gson.fromJson(command.getData(), CommandUpdate.class);
this.updateApk = new File(Utils.getApp().getCacheDir(), UPDATE_APK);
}
private void init(Command command, Gson gson, CommandProgressCallback callback) {
......@@ -86,7 +55,6 @@ public class UpdateCommandHandler extends CommandHandler {
this.gson = gson;
this.commandProgressCallback = callback;
this.commandUpdate = gson.fromJson(command.getData(), CommandUpdate.class);
this.updateApk = new File(Utils.getApp().getCacheDir(), UPDATE_APK);
}
@Override
......@@ -96,47 +64,39 @@ public class UpdateCommandHandler extends CommandHandler {
return null;
}
start = true;
FileUtils.delete(updateApk);
boolean createApk = false;
try {
createApk = updateApk.createNewFile();
} catch (IOException e) {
LogUtils.e(TAG, "更新包文件创建失败: " + e.getMessage(), e);
}
if (!createApk) {
return failedResult("更新包文件创建失败");
}
if (executor == null) {
executor = Executors.newScheduledThreadPool(1);
}
if (commandUpdate.getData() == null || commandUpdate.getData().getUrl() == null) {
LogUtils.d(TAG, "更新包地址异常: " + commandUpdate.toString());
return failedResult("更新包地址异常");
}
String url = commandUpdate.getData().getUrl();
Request request = new Request.Builder()
.url(url)
.build();
LogUtils.d(TAG, "更新包开始下载: " + url);
wait("开始下载", 0);
timeoutFuture = executor.schedule(timeoutRunnable, TIMEOUT, TimeUnit.MILLISECONDS);
httpClient.newCall(request).enqueue(callback);
FileDownloader.setup(Utils.getApp());
FileDownloader.getImpl()
.create(url)
.setPath(UPDATE_APK_PATH)
.setAutoRetryTimes(3)
.setSyncCallback(true)
.setForceReDownload(true)
.setListener(listener)
.start();
return successResult("开始下载");
}
private Runnable timeoutRunnable = () -> {
LogUtils.w(TAG, "安装包下载超时");
start = false;
};
private FileDownloadListener listener = new FileDownloadSampleListener() {
@Override
protected void started(BaseDownloadTask task) {
LogUtils.d(TAG, "更新包开始下载: " + task.getUrl());
UpdateCommandHandler.this.wait("开始下载", 0);
}
private Callback callback = new Callback() {
@Override
public void onFailure(@NotNull Call call, @NotNull IOException e) {
if (timeoutFuture != null) {
timeoutFuture.cancel(true);
timeoutFuture = null;
}
LogUtils.e(TAG, "下载失败: " + e.getMessage());
protected void progress(BaseDownloadTask task, int soFarBytes, int totalBytes) {
int per = (int) (soFarBytes * 1f / totalBytes * 100);
UpdateCommandHandler.this.wait("下载进度: " + per + "%", per);
}
@Override
protected void error(BaseDownloadTask task, Throwable e) {
LogUtils.e(TAG, "下载失败: " + e.getMessage(), e);
failed("下载失败", 0);
try {
Thread.sleep(DEFAULT_DELAY);
......@@ -148,21 +108,10 @@ public class UpdateCommandHandler extends CommandHandler {
}
@Override
public void onResponse(@NotNull Call call, @NotNull Response response) {
if (timeoutFuture != null) {
timeoutFuture.cancel(true);
timeoutFuture = null;
}
ResponseBody body = response.body();
if (body == null) {
LogUtils.d(TAG, "更新包为空");
idle("", 0);
start = false;
return;
}
boolean result = FileIOUtils.writeFileFromIS(updateApk, body.byteStream());
protected void completed(BaseDownloadTask task) {
File updateApk = new File(UPDATE_APK_PATH);
AppUtils.AppInfo info = AppUtils.getApkInfo(updateApk);
LogUtils.d(TAG, "更新包下载: " + (result ? "成功" : "失败") + ",开始安装: " + (info == null ? "null" : info.getPackageName()));
LogUtils.d(TAG, "更新包下载成功,开始安装: " + (info == null ? "null" : info.getPackageName()));
if (info == null || !info.getPackageName().equals(BuildConfig.APPLICATION_ID)) {
FileUtils.delete(updateApk);
idle("", 0);
......
package com.bgycc.smartcanteen.utils;
import android.content.ComponentName;
import android.content.Intent;
import android.os.Build;
import com.bgycc.smartcanteen.BuildConfig;
import com.bgycc.smartcanteen.MainActivity;
import com.blankj.utilcode.util.AppUtils;
import com.blankj.utilcode.util.FileUtils;
import com.blankj.utilcode.util.LogUtils;
import com.blankj.utilcode.util.PathUtils;
import com.blankj.utilcode.util.Utils;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import static com.bgycc.smartcanteen.utils.SmartCanteenUtils.TAG;
public class InstallManager {
private static final String ARG_PATH = "arg_path";
private static final String ARG_COMPONENT = "arg_package";
// assets文件夹中守护进程apk名称
private static final String DAEMON_APK_NAME = "Daemon.apk";
// 守护package name
private static final String DAEMON_PACKAGE_NAME = "com.bgycc.smartcanteen.daemon";
// 守护服务类名称
private static final String DAEMON_SERVICE_NAME = "com.bgycc.smartcanteen.daemon.DaemonService";
public static boolean install(File updateApk) {
String model = Build.MODEL;
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.M) {
......@@ -17,14 +36,34 @@ public class InstallManager {
try {
Process p = Runtime.getRuntime().exec("chmod 755 " + updateApk);
p.waitFor();
LogUtils.d(TAG, "开始安装");
LogUtils.d(TAG, "安装文件权限修改成功");
} catch (Exception e) {
LogUtils.e(TAG, "安装文件权限修改失败");
return false;
}
}
if (model.contains(DeviceProxy.DEVICE_MODEL_TPS)) {
return DangerousUtils.installAppSilent(updateApk);
// 检查守护进程的安装情况,及其安装版本
if (!AppUtils.isAppInstalled(DAEMON_PACKAGE_NAME)) {
LogUtils.d(TAG, "守护app未安装,从assets复制并进行安装");
// 安装守护进程
if (installDaemonFromAssets("守护app安装失败,启动静默安装",
"assets文件夹中未找到守护apk,或守护app复制失败,启动静默安装")) {
return DangerousUtils.installAppSilent(updateApk);
}
} else {
// 若已安装,则检查是否需要更新
AppUtils.AppInfo daemonAppInfo = AppUtils.getAppInfo(DAEMON_PACKAGE_NAME);
if (daemonAppInfo.getVersionCode() < BuildConfig.DaemonVersion) {
LogUtils.d(TAG, "当前守护app版本: " + daemonAppInfo.getVersionCode() + ", 最新版本: " + BuildConfig.DaemonVersion);
if (installDaemonFromAssets("守护app更新失败,启动静默安装",
"新版app复制失败,启动静默安装")) {
return DangerousUtils.installAppSilent(updateApk);
}
}
}
startDaemonForInstall(updateApk);
return true;
} else if (model.contains(DeviceProxy.DEVICE_MODEL_QUAD)) {
AppUtils.installApp(updateApk);
return true;
......@@ -32,4 +71,47 @@ public class InstallManager {
throw new RuntimeException("不明设备型号: " + model);
}
}
private static void copyDaemonApkToTarget(File daemonApk) throws IOException {
InputStream is = Utils.getApp().getAssets().open(DAEMON_APK_NAME);
FileOutputStream fos = new FileOutputStream(daemonApk);
byte[] buffer = new byte[1024];
int byteCount;
while ((byteCount = is.read(buffer)) != -1) {
fos.write(buffer, 0, byteCount);
}
fos.flush();
is.close();
fos.close();
}
private static boolean installDaemonFromAssets(String installFailedLog, String copyErrorLog) {
File daemonApk = new File(PathUtils.getExternalStoragePath(), DAEMON_APK_NAME);
try {
copyDaemonApkToTarget(daemonApk);
boolean result = DangerousUtils.installAppSilent(daemonApk);
LogUtils.d(TAG, "守护app安装结果: " + result);
if (!result) {
LogUtils.d(TAG, installFailedLog);
return true;
}
} catch (IOException e) {
LogUtils.d(TAG, "复制安装过程出错: " + copyErrorLog, e);
return true;
} finally {
FileUtils.delete(daemonApk);
}
return false;
}
private static void startDaemonForInstall(File updateApk) {
LogUtils.d(TAG, "启动守护app进行更新操作");
ComponentName componentName = new ComponentName(DAEMON_PACKAGE_NAME, DAEMON_SERVICE_NAME);
Intent intent = new Intent();
intent.putExtra(ARG_PATH, updateApk.getAbsolutePath());
intent.putExtra(ARG_COMPONENT, BuildConfig.APPLICATION_ID + File.separator + MainActivity.class.getName());
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.setComponent(componentName);
Utils.getApp().startService(intent);
}
}
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
ext {
daemon_verson_code = 10
daemon_verson_name = "1.0"
}
repositories {
google()
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:3.6.1'
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
}
}
......@@ -17,9 +17,32 @@ allprojects {
repositories {
google()
jcenter()
maven { url "https://oss.sonatype.org/content/repositories/snapshots/" }
}
}
// 保证daemon会先打包到app的assets文件夹内
task clean(type: Delete) {
delete rootProject.buildDir
Task appPreBuildTask;
Task daemonBuildTask;
subprojects.forEach { project ->
if (project.name == "app") {
Set<Task> appPreBuildTasks = project.getTasksByName("preBuild", false)
if (!appPreBuildTasks.isEmpty()) {
appPreBuildTask = appPreBuildTasks[0]
}
} else if (project.name == "daemon") {
Set<Task> daemonAssembleReleaseTasks = project.getTasksByName("assembleRelease", false)
if (!daemonAssembleReleaseTasks.isEmpty()) {
daemonBuildTask = daemonAssembleReleaseTasks[0]
}
}
}
if (appPreBuildTask != null && daemonBuildTask != null) {
appPreBuildTask.dependsOn(daemonBuildTask)
}
}
apply plugin: 'com.android.application'
android {
compileSdkVersion 29
buildToolsVersion "29.0.3"
defaultConfig {
applicationId "com.bgycc.smartcanteen.daemon"
minSdkVersion 22
targetSdkVersion 29
versionCode rootProject.ext.daemon_verson_code
versionName rootProject.ext.daemon_verson_name
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
buildTypes {
debug {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
release {
postprocessing {
removeUnusedCode true
removeUnusedResources true
obfuscate false
optimizeCode true
proguardFiles 'proguard-rules.pro'
}
zipAlignEnabled true
}
}
compileOptions {
sourceCompatibility = 1.8
targetCompatibility = 1.8
}
// daemon输出到app模块的assets目录中
applicationVariants.all { variant ->
if (variant.buildType.name != "debug") {
String targetDir = findAppAssetsDir()
if (!targetDir.isEmpty()) {
variant.getPackageApplicationProvider()
.get()
.outputDirectory = new File(targetDir)
}
variant.getPackageApplicationProvider()
.get()
.outputScope
.apkDatas
.forEach { apkData -> apkData.outputFileName = "Daemon.apk" }
}
}
}
// 在打包完成后删除生成的output.json文件
tasks.whenTaskAdded { task ->
if (task.name == 'assembleRelease') {
task.doLast {
String targetDir = findAppAssetsDir()
if (targetDir.isEmpty()) {
return
}
File outputJsonFile = new File(targetDir, "output.json")
if (outputJsonFile.exists()) {
outputJsonFile.delete()
}
}
}
}
private String findAppAssetsDir() {
for (Project p : rootProject.subprojects) {
if (p.name == "app") {
return "${p.projectDir}${File.separator}src${File.separator}main${File.separator}assets${File.separator}"
}
}
return ""
}
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation 'androidx.appcompat:appcompat:1.1.0'
testImplementation 'junit:junit:4.13'
androidTestImplementation 'androidx.test.ext:junit:1.1.1'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
implementation 'com.blankj:utilcodex:1.26.0'
}
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable
# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile
# -----------------------------基本 -----------------------------
#
# 指定代码的压缩级别 0 - 7(指定代码进行迭代优化的次数,在Android里面默认是5,这条指令也只有在可以优化时起作用。)
-optimizationpasses 5
# 混淆时不会产生形形色色的类名(混淆时不使用大小写混合类名)
-dontusemixedcaseclassnames
# 指定不去忽略非公共的库类(不跳过library中的非public的类)
-dontskipnonpubliclibraryclasses
# 指定不去忽略包可见的库类的成员
-dontskipnonpubliclibraryclassmembers
#不进行优化,建议使用此选项,
-dontoptimize
# 不进行预校验,Android不需要,可加快混淆速度。
-dontpreverify
# 屏蔽警告
-ignorewarnings
# 指定混淆是采用的算法,后面的参数是一个过滤器
# 这个过滤器是谷歌推荐的算法,一般不做更改
-optimizations !code/simplification/arithmetic,!field/*,!class/merging/*
# 保护代码中的Annotation不被混淆
-keepattributes *Annotation*
# 避免混淆泛型, 这在JSON实体映射时非常重要
-keepattributes Signature
# 抛出异常时保留代码行号
-keepattributes SourceFile,LineNumberTable
#优化时允许访问并修改有修饰符的类和类的成员,这可以提高优化步骤的结果。
# 比如,当内联一个公共的getter方法时,这也可能需要外地公共访问。
# 虽然java二进制规范不需要这个,要不然有的虚拟机处理这些代码会有问题。当有优化和使用-repackageclasses时才适用。
#指示语:不能用这个指令处理库中的代码,因为有的类和类成员没有设计成public ,而在api中可能变成public
-allowaccessmodification
#当有优化和使用-repackageclasses时才适用。
-repackageclasses
# 混淆时记录日志(打印混淆的详细信息)
# 这句话能够使我们的项目混淆后产生映射文件
# 包含有类名->混淆后类名的映射关系
-verbose
#
# ----------------------------- 默认保留 -----------------------------
#
#----------------------------------------------------
# 保持哪些类不被混淆
#继承activity,application,service,broadcastReceiver,contentprovider....不进行混淆
-keep public class * extends android.app.Activity
-keep public class * extends android.app.Application
-keep public class * extends android.support.multidex.MultiDexApplication
-keep public class * extends android.app.Service
-keep public class * extends android.content.BroadcastReceiver
-keep public class * extends android.content.ContentProvider
-keep public class * extends android.app.backup.BackupAgentHelper
-keep public class * extends android.preference.Preference
-keep public class * extends android.view.View
-keep class android.support.** {*;}## 保留support下的所有类及其内部类
-keep public class com.google.vending.licensing.ILicensingService
-keep public class com.android.vending.licensing.ILicensingService
#表示不混淆上面声明的类,最后这两个类我们基本也用不上,是接入Google原生的一些服务时使用的。
#----------------------------------------------------
# 保留继承的
-keep public class * extends android.support.v4.**
-keep public class * extends android.support.v7.**
-keep public class * extends android.support.annotation.**
#表示不混淆任何包含native方法的类的类名以及native方法名,这个和我们刚才验证的结果是一致
-keepclasseswithmembernames class * {
native <methods>;
}
#这个主要是在layout 中写的onclick方法android:onclick="onClick",不进行混淆
#表示不混淆Activity中参数是View的方法,因为有这样一种用法,在XML中配置android:onClick=”buttonClick”属性,
#当用户点击该按钮时就会调用Activity中的buttonClick(View view)方法,如果这个方法被混淆的话就找不到了
-keepclassmembers class * extends android.app.Activity{
public void *(android.view.View);
}
#表示不混淆枚举中的values()和valueOf()方法,枚举我用的非常少,这个就不评论了
-keepclassmembers enum * {
public static **[] values();
public static ** valueOf(java.lang.String);
}
#表示不混淆任何一个View中的setXxx()和getXxx()方法,
#因为属性动画需要有相应的setter和getter的方法实现,混淆了就无法工作了。
-keep public class * extends android.view.View{
*** get*();
void set*(***);
public <init>(android.content.Context);
public <init>(android.content.Context, android.util.AttributeSet);
public <init>(android.content.Context, android.util.AttributeSet, int);
}
-keepclasseswithmembers class * {
public <init>(android.content.Context, android.util.AttributeSet);
public <init>(android.content.Context, android.util.AttributeSet, int);
}
#表示不混淆Parcelable实现类中的CREATOR字段,
#毫无疑问,CREATOR字段是绝对不能改变的,包括大小写都不能变,不然整个Parcelable工作机制都会失败。
-keep class * implements android.os.Parcelable {
public static final android.os.Parcelable$Creator *;
}
# 这指定了继承Serizalizable的类的如下成员不被移除混淆
-keepclassmembers class * implements java.io.Serializable {
static final long serialVersionUID;
private static final java.io.ObjectStreamField[] serialPersistentFields;
private void writeObject(java.io.ObjectOutputStream);
private void readObject(java.io.ObjectInputStream);
java.lang.Object writeReplace();
java.lang.Object readResolve();
}
# 保留R下面的资源
#-keep class **.R$* {
# *;
#}
#不混淆资源类下static的
-keepclassmembers class **.R$* {
public static <fields>;
}
# 对于带有回调函数的onXXEvent、**On*Listener的,不能被混淆
-keepclassmembers class * {
void *(**On*Event);
void *(**On*Listener);
}
# 保留我们自定义控件(继承自View)不被混淆
-keep public class * extends android.view.View{
*** get*();
void set*(***);
public <init>(android.content.Context);
public <init>(android.content.Context, android.util.AttributeSet);
public <init>(android.content.Context, android.util.AttributeSet, int);
}
#
# ----------------------------- 第三方 -----------------------------
#
#不混淆smartcanteen下的所有类
-keep class com.bgycc.smartcanteen.** { *; }
package com.bgycc.smartcanteen.daemon;
import android.content.Context;
import androidx.test.platform.app.InstrumentationRegistry;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
import static org.junit.Assert.*;
/**
* Instrumented test, which will execute on an Android device.
*
* @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
*/
@RunWith(AndroidJUnit4.class)
public class ExampleInstrumentedTest {
@Test
public void useAppContext() {
// Context of the app under test.
Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
assertEquals("com.bgycc.smartcanteen.daemon", appContext.getPackageName());
}
}
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.bgycc.smartcanteen.daemon">
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:name=".ServiceApplication"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme" >
<service android:name=".DaemonService"
android:exported="true" />
</application>
</manifest>
package com.bgycc.smartcanteen.daemon;
import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.text.TextUtils;
import androidx.annotation.Nullable;
import com.blankj.utilcode.util.FileUtils;
import com.blankj.utilcode.util.LogUtils;
import com.blankj.utilcode.util.PathUtils;
import com.blankj.utilcode.util.ShellUtils;
import java.io.File;
public class DaemonService extends Service {
private static final String TAG = "DaemonService";
private static final String UPDATE_APK = "SmartCanteen-update.apk";
private static final String ARG_PATH = "arg_path";
private static final String ARG_COMPONENT = "arg_package";
// 主应用package name
private static final String MAIN_PACKAGE_NAME = "com.bgycc.smartcanteen";
// 主应用类名称
private static final String MAIN_ACTIVITY_NAME = "com.bgycc.smartcanteen.MainActivity";
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
String path = intent.getStringExtra(ARG_PATH);
if (TextUtils.isEmpty(path)) {
path = PathUtils.getExternalStoragePath() + File.separator + UPDATE_APK;
LogUtils.d(TAG, "安装apk地址为空,使用默认地址: " + path);
}
String component = intent.getStringExtra(ARG_COMPONENT);
if (TextUtils.isEmpty(component)) {
component = MAIN_PACKAGE_NAME + File.separator + MAIN_ACTIVITY_NAME;
LogUtils.d(TAG, "安装完毕启动包名为空,使用默认包名: " + component);
}
String command = "LD_LIBRARY_PATH=/vendor/lib*:/system/lib* pm install -r " + '"' + path + '"'
+ ";am start -W " + component;
LogUtils.d(TAG, "开始静默安装, path: " + path + ", component: " + command);
ShellUtils.CommandResult commandResult = ShellUtils.execCmd(command, isDeviceRooted());
LogUtils.d(TAG, "安装完毕, success: " + commandResult.successMsg +
", error: " + commandResult.errorMsg);
FileUtils.delete(path);
stopSelf(START_NOT_STICKY);
return START_NOT_STICKY ;
}
@Nullable
@Override
public IBinder onBind(Intent intent) {
return null;
}
private static boolean isDeviceRooted() {
String su = "su";
String[] locations = {"/system/bin/", "/system/xbin/", "/sbin/", "/system/sd/xbin/",
"/system/bin/failsafe/", "/data/local/xbin/", "/data/local/bin/", "/data/local/",
"/system/sbin/", "/usr/bin/", "/vendor/bin/"};
for (String location : locations) {
if (new File(location + su).exists()) {
return true;
}
}
return false;
}
}
package com.bgycc.smartcanteen.daemon;
import android.app.Application;
import com.blankj.utilcode.util.CrashUtils;
import com.blankj.utilcode.util.LogUtils;
import com.blankj.utilcode.util.PathUtils;
import com.blankj.utilcode.util.Utils;
import java.io.File;
public class ServiceApplication extends Application {
private static final String LOG_PREFIX = "daemon";
private static final String LOG_DIR = "log";
@Override
public void onCreate() {
super.onCreate();
Utils.init(getApplicationContext());
CrashUtils.init();
String logDir = PathUtils.getExternalStoragePath() + File.separator + LOG_DIR;
CrashUtils.init(logDir);
LogUtils.getConfig()
.setDir(logDir)
.setLog2FileSwitch(true)
.setBorderSwitch(false)
.setFilePrefix(LOG_PREFIX);
}
}
<vector xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:aapt="http://schemas.android.com/aapt"
android:width="108dp"
android:height="108dp"
android:viewportWidth="108"
android:viewportHeight="108">
<path android:pathData="M31,63.928c0,0 6.4,-11 12.1,-13.1c7.2,-2.6 26,-1.4 26,-1.4l38.1,38.1L107,108.928l-32,-1L31,63.928z">
<aapt:attr name="android:fillColor">
<gradient
android:endX="85.84757"
android:endY="92.4963"
android:startX="42.9492"
android:startY="49.59793"
android:type="linear">
<item
android:color="#44000000"
android:offset="0.0" />
<item
android:color="#00000000"
android:offset="1.0" />
</gradient>
</aapt:attr>
</path>
<path
android:fillColor="#FFFFFF"
android:fillType="nonZero"
android:pathData="M65.3,45.828l3.8,-6.6c0.2,-0.4 0.1,-0.9 -0.3,-1.1c-0.4,-0.2 -0.9,-0.1 -1.1,0.3l-3.9,6.7c-6.3,-2.8 -13.4,-2.8 -19.7,0l-3.9,-6.7c-0.2,-0.4 -0.7,-0.5 -1.1,-0.3C38.8,38.328 38.7,38.828 38.9,39.228l3.8,6.6C36.2,49.428 31.7,56.028 31,63.928h46C76.3,56.028 71.8,49.428 65.3,45.828zM43.4,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2c-0.3,-0.7 -0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C45.3,56.528 44.5,57.328 43.4,57.328L43.4,57.328zM64.6,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2s-0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C66.5,56.528 65.6,57.328 64.6,57.328L64.6,57.328z"
android:strokeWidth="1"
android:strokeColor="#00000000" />
</vector>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="108dp"
android:height="108dp"
android:viewportWidth="108"
android:viewportHeight="108">
<path
android:fillColor="#3DDC84"
android:pathData="M0,0h108v108h-108z" />
<path
android:fillColor="#00000000"
android:pathData="M9,0L9,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,0L19,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M29,0L29,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M39,0L39,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M49,0L49,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M59,0L59,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M69,0L69,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M79,0L79,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M89,0L89,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M99,0L99,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,9L108,9"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,19L108,19"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,29L108,29"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,39L108,39"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,49L108,49"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,59L108,59"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,69L108,69"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,79L108,79"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,89L108,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,99L108,99"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,29L89,29"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,39L89,39"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,49L89,49"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,59L89,59"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,69L89,69"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,79L89,79"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M29,19L29,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M39,19L39,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M49,19L49,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M59,19L59,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M69,19L69,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M79,19L79,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
</vector>
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@drawable/ic_launcher_background" />
<foreground android:drawable="@drawable/ic_launcher_foreground" />
</adaptive-icon>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@drawable/ic_launcher_background" />
<foreground android:drawable="@drawable/ic_launcher_foreground" />
</adaptive-icon>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="colorPrimary">#6200EE</color>
<color name="colorPrimaryDark">#3700B3</color>
<color name="colorAccent">#03DAC5</color>
</resources>
<resources>
<string name="app_name">Daemon</string>
</resources>
<resources>
<!-- Base application theme. -->
<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
<!-- Customize your theme here. -->
<item name="colorPrimary">@color/colorPrimary</item>
<item name="colorPrimaryDark">@color/colorPrimaryDark</item>
<item name="colorAccent">@color/colorAccent</item>
</style>
</resources>
package com.bgycc.smartcanteen.daemon;
import org.junit.Test;
import static org.junit.Assert.*;
/**
* Example local unit test, which will execute on the development machine (host).
*
* @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
*/
public class ExampleUnitTest {
@Test
public void addition_isCorrect() {
assertEquals(4, 2 + 2);
}
}
\ No newline at end of file
include ':app'
include ':daemon'
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment