Commit b1ef79bd by pye52

Merge branch 'new_master' into new_for_phone

parents 1ed83019 9a36e6e2
......@@ -245,8 +245,8 @@ public class MainActivity extends AppCompatActivity implements View.OnClickListe
settingLayout.animate().setDuration(300).alpha(0f);
break;
case CommandState.WAIT:
settingLayout.animate().setDuration(300).alpha(1f);
settingText.setText(event.getMessage());
settingLayout.animate().setDuration(300).alpha(1f);
break;
case CommandState.SUCCESS:
case CommandState.FAILED:
......
......@@ -15,12 +15,12 @@ import retrofit2.converter.scalars.ScalarsConverterFactory;
public class SCRetrofit {
private static final long TIMEOUT = 10;
public static SCApi createApi() {
Retrofit retrofit = createRetrofit();
public static SCApi createApi(OkHttpClient client) {
Retrofit retrofit = createRetrofit(client);
return retrofit.create(SCApi.class);
}
public static OkHttpClient createOkHttpClient() {
public static OkHttpClient.Builder createOkHttpClient() {
OkHttpClient.Builder builder = new OkHttpClient.Builder()
.connectTimeout(TIMEOUT, TimeUnit.SECONDS)
.readTimeout(TIMEOUT, TimeUnit.SECONDS)
......@@ -31,11 +31,10 @@ public class SCRetrofit {
interceptor.setLevel(HttpLoggingInterceptor.Level.BASIC);
builder.addInterceptor(interceptor);
}
return builder.build();
return builder;
}
private static Retrofit createRetrofit() {
OkHttpClient client = createOkHttpClient();
private static Retrofit createRetrofit(OkHttpClient client) {
return new Retrofit.Builder()
.client(client)
.baseUrl(BuildConfig.MainHttpServerHost)
......
......@@ -7,25 +7,37 @@ import com.google.gson.Gson;
public abstract class CommandHandler {
protected Command command;
protected Gson gson;
private CommandProgressCallback callback;
protected CommandProgressCallback commandProgressCallback;
public CommandHandler(Command command, Gson gson, CommandProgressCallback callback) {
public CommandHandler(Command command, Gson gson, CommandProgressCallback commandProgressCallback) {
this.command = command;
this.gson = gson;
this.callback = callback;
this.commandProgressCallback = commandProgressCallback;
}
public abstract CommandResponse run() throws Exception;
void progress(String message, int progress) {
callback.progress(message, progress);
void idle(String message, int progress) {
commandProgressCallback.idle(message, progress);
}
CommandResponse failed(String reason) {
void wait(String message, int progress) {
commandProgressCallback.wait(message, progress);
}
void success(String message, int progress) {
commandProgressCallback.success(message, progress);
}
void failed(String message, int progress) {
commandProgressCallback.failed(message, progress);
}
CommandResponse failedResult(String reason) {
return CommandResponse.failed(reason);
}
CommandResponse success(String reason) {
CommandResponse successResult(String reason) {
return CommandResponse.success(reason);
}
}
package com.bgycc.smartcanteen.command;
public interface CommandProgressCallback {
void progress(String message, int progress);
void idle(String message, int progress);
void wait(String message, int progress);
void success(String message, int progress);
void failed(String message, int progress);
}
......@@ -25,6 +25,7 @@ import java.util.Locale;
import okhttp3.MediaType;
import okhttp3.MultipartBody;
import okhttp3.OkHttpClient;
import okhttp3.RequestBody;
import retrofit2.Response;
......@@ -81,7 +82,7 @@ public class LogCommandHandler extends CommandHandler {
File logDir = getLogDirByType(data.getLogType());
progress("建立临时目录", 0);
wait("建立临时目录", 0);
uploadDir = tempDirInit();
Thread.sleep(DEFAULT_DELAY);
if (uploadDir == null) return null;
......@@ -94,7 +95,7 @@ public class LogCommandHandler extends CommandHandler {
Thread.sleep(DEFAULT_DELAY);
File zip = new File(Utils.getApp().getCacheDir() + File.separator + ZIP_FILE);
try {
progress("开始压缩", 40);
wait("开始压缩", 40);
ZipUtils.zipFile(uploadDir, zip);
} catch (IOException e) {
LogUtils.e(TAG, "压缩日志文件失败: " + e.getMessage(), e);
......@@ -130,22 +131,25 @@ public class LogCommandHandler extends CommandHandler {
}
}
private boolean copyTargetFiles(File src, File desc) {
private boolean copyTargetFiles(File src, File descDir) {
FileFilter filter = file -> {
Date date = new Date(file.lastModified());
return date.after(startTime) && date.before(endTime);
};
List<File> logFiles = FileUtils.listFilesInDirWithFilter(src, filter, false, null);
boolean copyResult = true;
progress("筛选目标日志文件", 10);
wait("筛选目标日志文件", 10);
File descFile;
for (File file : logFiles) {
copyResult = copyResult && FileUtils.copy(file, desc);
descFile = new File(descDir, file.getName());
copyResult = copyResult && FileUtils.copy(file, descFile);
}
return !logFiles.isEmpty() && copyResult;
}
private void upload(File zip) {
SCApi api = SCRetrofit.createApi();
OkHttpClient client = SCRetrofit.createOkHttpClient().build();
SCApi api = SCRetrofit.createApi(client);
CommandLog.CommandLogData data = commandLog.getData();
String fileNameForServer = data.getLogType() +
"_" + format.format(startTime) +
......@@ -168,14 +172,14 @@ public class LogCommandHandler extends CommandHandler {
@Override
public CommandResponse run() throws InterruptedException {
if (!checkLogCommand()) {
return failed("日志上传指令不符合规范");
return failedResult("日志上传指令不符合规范");
}
File logFile = getZipLogs();
if (logFile != null) {
progress("上传压缩文件至服务器", 60);
wait("上传压缩文件至服务器", 60);
upload(logFile);
return success("");
return successResult("");
}
return failed("");
return failedResult("");
}
}
......@@ -5,18 +5,26 @@ 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.DangerousUtils;
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 java.io.BufferedInputStream;
import org.jetbrains.annotations.NotNull;
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;
......@@ -25,57 +33,149 @@ import okhttp3.ResponseBody;
import static com.bgycc.smartcanteen.utils.SmartCanteenUtils.TAG;
/**
* 设备指令: 更新
* 设备指令: 更新 <br/>
* 更新任务处理为单例,避免多个更新任务同时进行浪费资源
*/
public class UpdateCommandHandler extends CommandHandler {
private static final String UPDATE_APK = "SmartCanteen-update.apk";
private static final long INSTALL_DELAY = 3000;
// 下载超时为60s
private static final long TIMEOUT = 60 * 1000;
private static final long DEFAULT_DELAY = 5 * 1000;
private volatile boolean start = false;
private OkHttpClient httpClient;
private CommandUpdate commandUpdate;
private File updateApk;
public UpdateCommandHandler(Command command, Gson gson, CommandProgressCallback callback) {
// 避免下载异常,这里创建自己的线程池(毕竟收到更新通知时一般都会进入安装流程,无需担心开销)
private ScheduledExecutorService executor;
private ScheduledFuture<?> timeoutFuture;
private static UpdateCommandHandler instance;
public static UpdateCommandHandler getInstance(Command command, Gson gson, CommandProgressCallback callback) {
if (instance == null) {
synchronized (UpdateCommandHandler.class) {
if (instance == null) {
instance = new UpdateCommandHandler(command, gson, callback);
}
}
}
// 当callback发生变化时,重新设置
if (!callback.equals(instance.commandProgressCallback)) {
instance.commandProgressCallback = callback;
}
return instance;
}
private UpdateCommandHandler(Command command, Gson gson, CommandProgressCallback callback) {
super(command, gson, callback);
this.httpClient = SCRetrofit.createOkHttpClient();
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(PathUtils.getExternalStoragePath(), UPDATE_APK);
this.updateApk = new File(Utils.getApp().getCacheDir(), UPDATE_APK);
FileUtils.delete(updateApk);
}
@Override
public CommandResponse run() throws InterruptedException {
public synchronized CommandResponse run() {
if (start) {
LogUtils.w(TAG, "更新任务已启动");
return null;
}
start = true;
if (executor == null) {
executor = Executors.newScheduledThreadPool(1);
}
if (commandUpdate.getData() == null || commandUpdate.getData().getUrl() == null) {
return failed("更新包地址异常");
LogUtils.d(TAG, "更新包地址异常: " + commandUpdate.toString());
return failedResult("更新包地址异常");
}
String url = commandUpdate.getData().getUrl();
Request request = new Request.Builder()
.url(url)
.build();
try {
Response response = httpClient.newCall(request).execute();
LogUtils.d(TAG, "更新包开始下载: " + url);
wait("开始下载", 0);
timeoutFuture = executor.schedule(timeoutRunnable, TIMEOUT, TimeUnit.MILLISECONDS);
httpClient.newCall(request).enqueue(callback);
return successResult("开始下载");
}
private Runnable timeoutRunnable = () -> {
LogUtils.w(TAG, "安装包下载超时");
start = false;
};
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());
failed("下载失败", 0);
try {
Thread.sleep(DEFAULT_DELAY);
} catch (Exception ignored) {
} finally {
idle("", 0);
start = false;
}
}
@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) {
return failed("请求异常");
LogUtils.d(TAG, "更新包为空");
idle("", 0);
start = false;
return;
}
progress("开始下载", 0);
boolean success = FileIOUtils.writeFileFromIS(updateApk, new BufferedInputStream(body.byteStream()),
p -> progress("下载进度: " + ((int) p), (int) p));
if (success) {
progress("下载完毕,开始安装更新包", 100);
Thread.sleep(INSTALL_DELAY);
AppUtils.AppInfo info = AppUtils.getApkInfo(updateApk);
if (info == null ||
(info.getPackageName().equals(BuildConfig.APPLICATION_ID) && info.getVersionCode() < BuildConfig.VERSION_CODE)) {
return failed("不允许安装低版本");
} else {
DangerousUtils.installAppSilent(updateApk);
return success("安装更新包");
}
FileIOUtils.writeFileFromIS(updateApk, body.byteStream());
LogUtils.d(TAG, "更新包下载成功,开始安装");
AppUtils.AppInfo info = AppUtils.getApkInfo(updateApk);
if (info == null || !info.getPackageName().equals(BuildConfig.APPLICATION_ID)) {
FileUtils.delete(updateApk);
LogUtils.w(TAG, "更新包包名非法");
idle("", 0);
start = false;
return;
}
if (info.getVersionCode() == BuildConfig.VERSION_CODE) {
FileUtils.delete(updateApk);
LogUtils.w(TAG, "更新包已安装");
} else if (info.getVersionCode() < BuildConfig.VERSION_CODE) {
FileUtils.delete(updateApk);
LogUtils.d(TAG, "不允许安装低版本");
failed("不允许安装低版本", 0);
} else {
DangerousUtils.installAppSilent(updateApk);
LogUtils.d(TAG, "开始安装");
success("开始安装", 100);
}
try {
Thread.sleep(DEFAULT_DELAY);
} catch (Exception ignored) {
} finally {
idle("", 0);
start = false;
}
} catch (IOException e) {
LogUtils.e(TAG, "下载更新包失败: " + e.getMessage());
}
return failed("更新包安装失败");
}
};
}
......@@ -24,12 +24,12 @@ public class WifiConfigCommandHandler extends CommandHandler {
if (data == null) return null;
if (!NetworkUtils.isWifiEnabled()) {
progress("正在启动Wifi", 5);
wait("正在启动Wifi", 5);
if (!NetworkUtils.setEnable(true)) {
String failedMessage = "无法启动Wifi";
progress(failedMessage, 5);
wait(failedMessage, 5);
Thread.sleep(DEFAULT_DELAY);
return failed(failedMessage);
return failedResult(failedMessage);
}
}
......@@ -38,7 +38,7 @@ public class WifiConfigCommandHandler extends CommandHandler {
String pwd = data.getPwd();
String type = data.getType();
progress("正在配置Wifi", 10);
wait("正在配置Wifi", 10);
try {
NetworkUtils.connect(ssid, identity, pwd, type);
// 轮训检查wifi是否链接成功
......@@ -49,14 +49,14 @@ public class WifiConfigCommandHandler extends CommandHandler {
continue;
}
String message = "Wifi配置成功";
progress(message, 50);
wait(message, 50);
Thread.sleep(DEFAULT_DELAY);
return success(message);
return successResult(message);
}
} catch (Exception e) {
Thread.sleep(DEFAULT_DELAY);
return failed(e.getMessage());
return failedResult(e.getMessage());
}
return failed("无法连接Wifi");
return failedResult("无法连接Wifi");
}
}
package com.bgycc.smartcanteen.entity;
import com.bgycc.smartcanteen.listener.DownloadProgressListener;
import org.jetbrains.annotations.NotNull;
import java.io.IOException;
import okhttp3.MediaType;
import okhttp3.ResponseBody;
import okio.Buffer;
import okio.BufferedSource;
import okio.ForwardingSource;
import okio.Okio;
import okio.Source;
public class ProgressResponseBody extends ResponseBody {
private final ResponseBody responseBody;
private final DownloadProgressListener listener;
private BufferedSource bufferedSource;
public ProgressResponseBody(ResponseBody responseBody, DownloadProgressListener listener){
this.responseBody = responseBody;
this.listener = listener;
}
@Override
public MediaType contentType() {
return responseBody.contentType();
}
@Override
public long contentLength() {
return responseBody.contentLength();
}
@NotNull
@Override
public BufferedSource source() {
if (null == bufferedSource){
bufferedSource = Okio.buffer(source(responseBody.source()));
}
return bufferedSource;
}
private Source source(Source source) {
return new ForwardingSource(source) {
long totalBytesRead = 0L;
@Override
public long read(@NotNull Buffer sink, long byteCount) throws IOException {
long bytesRead = super.read(sink, byteCount);
totalBytesRead += bytesRead != -1 ? bytesRead : 0;
listener.onProgress(totalBytesRead, responseBody.contentLength(), bytesRead == -1);
return bytesRead;
}
};
}
}
package com.bgycc.smartcanteen.listener;
public interface DownloadProgressListener {
/**
* @param progress 已经下载或上传字节数
* @param total 总字节数
* @param done 是否完成
*/
void onProgress(long progress, long total, boolean done);
}
......@@ -110,7 +110,7 @@ public class DangerousUtils {
final boolean isRooted) {
if (!isFileExists(file)) return false;
String filePath = '"' + file.getAbsolutePath() + '"';
String command = "LD_LIBRARY_PATH=/vendor/lib*:/system/lib* pm install " +
String command = "LD_LIBRARY_PATH=/vendor/lib*:/system/lib* pm install -r " +
(params == null ? "" : params + " ")
+ filePath;
ShellUtils.CommandResult commandResult = ShellUtils.execCmd(command, isRooted);
......
......@@ -80,10 +80,25 @@ public class CommandViewModel extends ViewModel implements CommandProgressCallba
};
@Override
public void progress(String message, int progress) {
public void idle(String message, int progress) {
commandState.postValue(new CommandState(CommandState.IDLE, message, progress));
}
@Override
public void wait(String message, int progress) {
commandState.postValue(new CommandState(CommandState.WAIT, message, progress));
}
@Override
public void success(String message, int progress) {
commandState.postValue(new CommandState(CommandState.SUCCESS, message, progress));
}
@Override
public void failed(String message, int progress) {
commandState.postValue(new CommandState(CommandState.FAILED, message, progress));
}
private SCWebSocketListener listener = new SCWebSocketListenerAdapter() {
private static final String RESPONSE_PAY_RESULT = "PAY_RESULT";
......@@ -116,7 +131,7 @@ public class CommandViewModel extends ViewModel implements CommandProgressCallba
handler = new LogCommandHandler(command, gson, deviceSN,CommandViewModel.this);
break;
case Command.APP_UPDATE:
handler = new UpdateCommandHandler(command, gson, CommandViewModel.this);
handler = UpdateCommandHandler.getInstance(command, gson, CommandViewModel.this);
break;
case Command.CONFIG_WIFI:
handler = new WifiConfigCommandHandler(command, gson, CommandViewModel.this);
......@@ -139,6 +154,9 @@ public class CommandViewModel extends ViewModel implements CommandProgressCallba
try {
commandState.postValue(new CommandState(CommandState.WAIT));
CommandResponse response = handler.run();
if (response == null) {
return;
}
if (response.success()) {
commandState.postValue(new CommandState(CommandState.SUCCESS, response.getMessage()));
} else {
......
......@@ -37,7 +37,9 @@ import static com.bgycc.smartcanteen.utils.SmartCanteenUtils.TAG;
* 支付状态(空闲、发送订单信息、支付中、支付成功、支付失败)都会通过{@link PayOnlineState}发出通知 <br/><br/>
*/
public class PayOnlineViewModel extends ViewModel {
private static final long TIMEOUT = 5;
private static final long TIMEOUT = 5 * 1000;
// 在线支付延迟100ms执行,留出时间给扫码反馈
private static final long REQUEST_DELAY = 100;
private static final long DEFAULT_DELAY = 3 * 1000;
private Gson gson;
private String deviceSN;
......@@ -71,9 +73,9 @@ public class PayOnlineViewModel extends ViewModel {
payRequest = new PayRequest(deviceSN, payData);
cancelTimeout();
TimeoutRunnable timeoutRunnable = new TimeoutRunnable();
timeoutFuture = SCTaskExecutor.getInstance().schedule(timeoutRunnable, TIMEOUT, TimeUnit.SECONDS);
timeoutFuture = SCTaskExecutor.getInstance().schedule(timeoutRunnable, TIMEOUT, TimeUnit.MILLISECONDS);
RequestRunnable requestRunnable = new RequestRunnable();
SCTaskExecutor.getInstance().executeOnDiskIO(requestRunnable);
SCTaskExecutor.getInstance().schedule(requestRunnable, REQUEST_DELAY, TimeUnit.MILLISECONDS);
}
private void cancelTimeout() {
......
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