android 版本更新适配8.0,解决8.0手机无法更新自动安装apk
随着android 7.0的普及android 8.0的也逐渐流行起来,那么google对权限方面又有了新的修改。而且我发现在android8.0中除了一些bug,比如说:在小米6(Android 8.0系统)中无法自动安装的问题,那么之前做的更新系统就要稍微调整下。
那根据android 8.0我们重新理一下更新的思路:
1、写一个接口调用是否有更新,可以读取版本号后台判断是否有更新,也可拉去最新信息,app内部判断:
2、创建网络监听,监听网络变化,在application中初始化,
3、创建更新service,根据service的机制,我们可以创建一个service不销毁,调用startService方法,service无论你创建多少次onCreate方法只会执行一次(当你没有stop的时候),而onStartCommand可以多次调用
4、调用更新接口,在回调中判断是否需要更新,如果需要更新就创建更新BroadcastReceiver广播,用来通知更新的开始,进度更新,结束,和各种失败的情况,然后创建更新提示dialog(这个不用我说了吧),为了防止更新按钮多次点击和重复下载的问题,我使用了三个全局静态变量来控制
public static boolean hasNewAppVersion = false;//是否有新版本
public static boolean isDownLoadApk = false;//apk是否正在下载
public static boolean isSuccessRequestUpdate = false;//更新请求是否成功了
5.点击更新弹框的,更新按钮,创建更新下载的service,注意下载需要在子线程中做。同时根据下载的开始,下载进度,或者结束,失败等情况利用广播器进行通知。
6、下载完成调用安装,这里一定要注意,android O更新了未知来源的权限,需要在配置文件中添加配置
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
那么接下来我们一步一步讲解
一、接口调用是否有更新,返回数据如下大致如下:
{
"status":true,
"code":200,
"data":{
"VersionName":"1.1.8",
"VersionCode":19,
"AppUrl":"https://********/appfile/*****.apk",//更新地址
"Content":"部分BUG修复",//提示内容
"Status":1,
"isMustUpdate":true//是否强制更新
},
"msg":"返回成功"
}
二、网络状态监听
创建网络状态变化广播器
/**
* Created by CherishTang on 2016/12/29.
* 自定义广播接收器,监听网络状态是否发生改变
*/
public class NetworkStateReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
if (NetworkStateUtil.isNetWorkConnected(context) && NetworkStateUtil.isWifiConnected(context)) {
WifiInfo wifiInfo = NetworkStateUtil.getWifiInfo(context);
// MThoast.showShort(context, "已连接到"+(wifiInfo.getSSID()==null?"wifi":wifiInfo.getSSID())+"网络");
processCustomMessage(context, 1);
} else if (NetworkStateUtil.isNetWorkConnected(context) && NetworkStateUtil.isMobileConnected(context)) {
// MThoast.showShort(context, "已连接到数据流量网络");
processCustomMessage(context, 2);
} else {
StaticUtil.isDownLoadApk = false;
processCustomMessage(context, 3);
// MThoast.showShort(context, "已进入无网络次元,请检查网络设置!");
}
}
//send msg to MainActivity
private void processCustomMessage(Context context, Integer message) {
// Intent mIntent = new Intent(BaseActivity.ACTION_INTENT_RECEIVER_MESSAGE);
// mIntent.putExtra("message", message);
// context.sendBroadcast(mIntent);
if (networkChangeListener != null) {
networkChangeListener.networkChanger(message);
}
}
private NetworkChangeListener networkChangeListener;
public void setNetworkChangeListener(NetworkChangeListener networkChangeListener) {
this.networkChangeListener = networkChangeListener;
}
}
添加网络状态变化监听权限
<uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" />
为了防止重复注册我们再application中注册广播
public final static String ACTION_INTENT_RECEIVER ="android.net.conn.CONNECTIVITY_CHANGE";
public final static String ACTION_INTENT_RECEIVER_8 = "android.net.wifi.SCAN_RESULTS";
/**
* 注册广播,在onCreate中调用
*/
private void initNetWorkReceiver() {
intentFilter = new IntentFilter();
intentFilter.addAction(ACTION_INTENT_RECEIVER);
intentFilter.addAction(ACTION_INTENT_RECEIVER_8);
networkChangeReceiver = new NetworkStateReceiver();
registerReceiver(networkChangeReceiver, intentFilter);
}
/**
* 回收广播
*/
@Override
public void onTerminate() {
super.onTerminate();
if (networkChangeReceiver != null)
unregisterReceiver(networkChangeReceiver);
}
/**
* 获取NetworkStateReceiver 实例
*/
public NetworkStateReceiver getNetworkChangeReceiver(){
return networkChangeReceiver;
}
三、创建更新service
在你的MainActivity中开启更新service,同时监听网络变化状态,在onStartConmmand中检查给更新
四、调用接口检查更新
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
checkVersionUpdate();//检查更新网络请求
//获取网络状态变化广播器
NetworkStateReceiver networkStateReceiver =
MyApplication.getInstance().getNetworkChangeReceiver();
//如果广播器不为空,在添加网络变化监听回调
if (networkStateReceiver != null)
networkStateReceiver.setNetworkChangeListener(this);
return super.onStartCommand(intent, flags, startId);
}
/**
* 注册更新BroadcastReceiver
*
* @param versionMessage 更新数据
*/
public void updateCheck(VersionMessage versionMessage) {
StaticUtil.hasNewAppVersion = true;
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction(DownloadApkService.ACTION_START);
intentFilter.addAction(DownloadApkService.ACTION_UPDATE);
intentFilter.addAction(DownloadApkService.ACTION_FINISHED);
intentFilter.addAction(DownloadApkService.ACTION_CANCEL);
intentFilter.addAction(DownloadApkService.ACTION_ERROR);
intentFilter.addAction(DownloadApkService.ACTION_REDIRECT_ERROR);
downloadApkReceiver = new DownloadApkReceiver();//创建更新下载广播器
registerReceiver(downloadApkReceiver, intentFilter);
//获取当前activity
Activity mContext = AppManager.getAppManager().currentActivity();
//如果程序在前台,并且当前activity没有销毁的话,创建更新弹框,
//我封装在UpdateManger方法中了
if (AppManager.isForeground(this) && mContext != null) {
UpdateManger.getInstance().checkUpdateInfo(mContext,
versionMessage.getAppUrl(), versionMessage.getContent(),
versionMessage.getVersionName(), versionMessage.getMustUpdate());
}
}
五、点击更新按钮下载新版app
首先判断任务是否正在下载,避免重复下载
if (StaticUtil.isDownLoadApk) {
MThoast.showShort(mContext, "已存在下载任务,请勿重复下载");
return;
}
检查并动态获取文件的读取权限
if (ContextCompat.checkSelfPermission(mContext, Manifest.permission.WRITE_EXTERNAL_STORAGE)
!= PackageManager.PERMISSION_GRANTED) {
//申请WRITE_EXTERNAL_STORAGE权限
ActivityCompat.requestPermissions(mContext, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE},
0x01);
return;
}
创建更新service并关闭更新提示框
if (customDialog != null && customDialog.isShowing())
customDialog.dismiss();
Intent intent = new Intent(mContext, DownloadApkService.class);
intent.setAction(DownloadApkService.ACTION_START);
intent.putExtra("id", 0);
intent.putExtra("url", apkUrl);
intent.putExtra("name", "保存到本地的app名称");
mContext.startService(intent);
更新下载service代码:
/**
* Created by CherishTang on 2017/10/17.
* 更新app服务
*/
public class DownloadApkService extends Service {
public static final String ACTION_START = "ACTION_START";
public static final String ACTION_UPDATE = "ACTION_UPDATE";
public static final String ACTION_FINISHED = "ACTION_FINISHED";
public static final String ACTION_CANCEL = "ACTION_CANCEL";
public static final String ACTION_ERROR = "ACTION_ERROR";
public static final String ACTION_REDIRECT_ERROR = "ACTION_REDIRECT_ERROR";
public static final String HIDE_DIALOG = "HIDE_DIALOG";
// 文件的保存路径
public static final String path = Environment.getExternalStorageDirectory().getAbsolutePath() +
File.separator + StaticUtil.ROOTFILEPATH + File.separator + "download" + File.separator;
@Nullable
@Override
public IBinder onBind(Intent intent) {
return null;
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
if (intent != null) {
if (ACTION_START.equals(intent.getAction())) {
new DownLoadApkThread(intent.getIntExtra("id", 0)
, intent.getStringExtra("url"), intent.getStringExtra("name")).start();
}
}
return super.onStartCommand(intent, flags, startId);
}
public class DownLoadApkThread extends Thread {
private int id;
private String downloadUrl;
private String fileName;
public DownLoadApkThread(int id, String downloadUrl, String fileName) {
this.id = id;
this.downloadUrl = downloadUrl;
this.fileName = fileName;
}
@Override
public void run() {
HttpURLConnection connLength = null;
HttpURLConnection connFile = null;
RandomAccessFile randomAccessFile = null;
InputStream inputStream = null;
URL url = null;
try {
url = new URL(downloadUrl);
//获取apk文件长度
connLength = (HttpURLConnection) url.openConnection();
connLength.setConnectTimeout(5000);
connLength.setRequestMethod("GET");
int code = connLength.getResponseCode();
int length = 0;
if (code == HttpURLConnection.HTTP_OK) {
length = connLength.getContentLength();
} else if (code == 301) {
sendBroadcast(new Intent().setAction(ACTION_REDIRECT_ERROR));
return;
} else {
sendBroadcast(new Intent().setAction(ACTION_ERROR));
return;
}
//判断文件是否存在,不存在则创建
File dir = new File(path);
if (!dir.exists()) {
dir.mkdirs();
}
File file = new File(dir, fileName);
randomAccessFile = new RandomAccessFile(file, "rwd");
randomAccessFile.setLength(length);
//下载文件
connFile = (HttpURLConnection) url.openConnection();
connFile.setConnectTimeout(5000);
connFile.setRequestMethod("GET");
connFile.setRequestProperty("Range", "bytes=" + 0 + "-" + length);
code = connFile.getResponseCode();
//显示通知栏进度条
Intent intent = new Intent();
intent.setAction(ACTION_START);
intent.putExtra("id", id);
sendBroadcast(intent);
if (code == HttpURLConnection.HTTP_PARTIAL) {
inputStream = connFile.getInputStream();
int finished = 0;
byte[] bytes = new byte[1024 * 1024];
int len = -1;
long time = System.currentTimeMillis();
while ((len = inputStream.read(bytes)) != -1) {
//文件写入
randomAccessFile.write(bytes, 0, len);
//更新通知栏进度条
finished += len;
if (System.currentTimeMillis() - time > 1000) {
time = System.currentTimeMillis();
intent.setAction(ACTION_UPDATE);
int pro = (int) (((float) finished / length) * 100);
intent.putExtra("finished", pro);
sendBroadcast(intent);
}
}
}
//关闭通知栏
intent.setAction(ACTION_FINISHED);
sendBroadcast(intent);
} catch (MalformedURLException e) {
sendBroadcast(new Intent().setAction(ACTION_ERROR));
e.printStackTrace();
} catch (IOException e) {
sendBroadcast(new Intent().setAction(ACTION_ERROR));
e.printStackTrace();
} finally {
if (connLength != null) {
connLength.disconnect();
}
if (connFile != null) {
connFile.disconnect();
}
try {
if (inputStream != null) {
inputStream.close();
}
if (randomAccessFile != null) {
randomAccessFile.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
super.run();
}
}
}
更新下载通知广播:
/**
* Created by 方舟 on 2017/10/17.
* 更新下载广播接收器
*/
public class DownloadApkReceiver extends BroadcastReceiver {
private UpdateNotificationUtil mNotificationUtil;
@Override
public void onReceive(Context context, Intent intent) {
mNotificationUtil = new UpdateNotificationUtil(context);
if (DownloadApkService.ACTION_START.equals(intent.getAction())) {
StaticUtil.isDownLoadApk = true;//单例下载,防止多任务进行
// 下载开始的时候启动通知栏
mNotificationUtil.showNotification(intent.getIntExtra("id", 0));
} else if (DownloadApkService.ACTION_UPDATE.equals(intent.getAction())) {
// 更新进度条
mNotificationUtil.updateNotification(intent.getIntExtra("id",0), intent.getIntExtra("finished", 0));
} else if (DownloadApkService.ACTION_FINISHED.equals(intent.getAction())) {
StaticUtil.isDownLoadApk = false;//变更未任务未下载
mNotificationUtil.cancelNotification(intent.getIntExtra("id", 0));// 下载结束后取消通知
UpdateManger.installApk(context, new File(DownloadApkService.path + StaticUtil.apkName));
} else if (DownloadApkService.ACTION_CANCEL.equals(intent.getAction())) {
StaticUtil.isDownLoadApk = false;//变更未任务未下载
mNotificationUtil.cancelNotification(intent.getIntExtra("id", 0));// 下载结束后取消通知
} else if (DownloadApkService.ACTION_ERROR.equals(intent.getAction())) {
MThoast.showShort(context, "读取文件失败,请前往官方网站扫码下载最新版本!");
}else if (DownloadApkService.ACTION_REDIRECT_ERROR.equals(intent.getAction())) {
MThoast.showShort(context, "下载地址重定向出现错误,请稍后再试!");
}
}
}
由于android 8.0添加了NotificationChannel的概念因此这个UpdateNotificationUtil工具类可以根据新的方式自己写,我贴一个仅供参考其中
StaticUtil.updateVerisonChannelId,StaticUtil.updateVerisonChannelName,两个静态变量最好不要重复
public static final String updateVerisonChannelId = "包名.updateVersionApp";
public static final String updateVerisonChannelName = "版本更新";
/**
* Created by CherishTang on 2017/10/13.
* 更新通知
*/
public class UpdateNotificationUtil {
private Context mContext;
private NotificationManager mManager;
private NotificationCompat.Builder mBuilder;
public UpdateNotificationUtil(Context context) {
this.mContext = context;
mManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
NotificationUtils.createNotificationChannel(
false,false,
StaticUtil.updateVerisonChannelId,
StaticUtil.updateVerisonChannelName,
NotificationManager.IMPORTANCE_DEFAULT);
}
mBuilder = new NotificationCompat.Builder(context, StaticUtil.updateVerisonChannelId);
}
/**
* 显示通知栏
*
* @param id
*/
public void showNotification(final int id) {
mBuilder.setTicker("正在下载");//Ticker是状态栏显示的提示
mBuilder.setOngoing(true);
mBuilder.setContentTitle("正在下载最新版本");
mBuilder.setProgress(100, 0, false);
mBuilder.setContentText(0 + "%");
mBuilder.setSmallIcon(R.mipmap.icon_launcher);
mBuilder.setLargeIcon(BitmapFactory.decodeResource(mContext.getResources(), R.mipmap.icon_launcher));//通知栏的大图标
Intent msgIntent = new Intent();
msgIntent.setClass(mContext, MainActivity.class);
msgIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);
PendingIntent pendingIntent = PendingIntent.getActivity(mContext, 100, msgIntent, PendingIntent.FLAG_UPDATE_CURRENT);
mBuilder.setContentIntent(pendingIntent);//点击跳转
mManager.notify(id, mBuilder.build());
}
/**
* 取消通知栏通知
*/
public void cancelNotification(int id) {
mManager.cancel(id);
}
/**
* 更新通知栏进度条
*
* @param id 获取Notification的id
* @param progress 获取的进度
*/
public void updateNotification(int id, int progress) {
if (mBuilder != null) {
mBuilder.setTicker("开始下载");//Ticker是状态栏显示的提示
mBuilder.setOngoing(true);
mBuilder.setContentTitle("app名称");
mBuilder.setSmallIcon(R.mipmap.icon_launcher);
mBuilder.setLargeIcon(BitmapFactory.decodeResource(mContext.getResources(), R.mipmap.icon_launcher));//通知栏的大图标
mBuilder.setProgress(100, progress, false);
mBuilder.setContentText(progress + "%");
mManager.notify(id, mBuilder.build());
}
}
}
/**
* Created by CherishTang on 2018/7/19.
* 通知栏工具类
*/
public class NotificationUtils {
private static NotificationManager mManager;
private static NotificationCompat.Builder notification;
private String contentText, contentTitle, channelId;
private boolean autoCancel = true;
private static Application application;
public static void init(Application application) {
NotificationUtils.application = application;
mManager = (NotificationManager) application.getSystemService(NOTIFICATION_SERVICE);
}
public NotificationUtils Builder(String channelId) {
if (notification == null) {
notification = new NotificationCompat.Builder(application, channelId == null ? "chat" : channelId);
} else {
notification.setChannelId(channelId);
}
this.channelId = channelId;
return this;
}
public static NotificationManager getManager() {
return mManager;
}
public static NotificationCompat.Builder getNotification() {
return notification;
}
@TargetApi(Build.VERSION_CODES.O)
public static void createNotificationChannel(boolean isVibrate,
boolean hasSound,
String channelId,
String channelName,
int importance) {
NotificationChannel channel = new NotificationChannel(channelId, channelName, importance);
NotificationManager notificationManager = (NotificationManager) application.getSystemService(
NOTIFICATION_SERVICE);
channel.enableVibration(isVibrate);
channel.enableLights(true);
if (!hasSound)
channel.setSound(null, null);
if (notificationManager != null)
notificationManager.createNotificationChannel(channel);
}
public NotificationUtils setChannelId(String channelId) {
notification.setChannelId(channelId);
this.channelId = channelId;
return this;
}
public NotificationUtils setContentText(String contentText) {
notification.setContentText(contentText);
this.contentText = contentText;
return this;
}
public NotificationUtils setContentTitle(String contentTitle) {
notification.setContentTitle(contentTitle);
this.contentTitle = contentTitle;
return this;
}
public NotificationUtils setAutoCancel(boolean autoCancel) {
notification.setAutoCancel(autoCancel);
this.autoCancel = autoCancel;
return this;
}
public NotificationUtils notifyMessage(int id) {
if (mManager != null) {
notification.setContentTitle(contentTitle)
.setContentText(contentText)
.setWhen(System.currentTimeMillis())
.setSmallIcon(R.mipmap.icon_launcher)
.setLargeIcon(BitmapFactory.decodeResource(application.getResources(), R.mipmap.icon_launcher))
.setAutoCancel(autoCancel)
.build();
mManager.notify(id, notification.build());
}
return this;
}
}
六、下载完成后启动自动安装
首先注意在Android O新增权限,必须添加,最后调用安装方法
<!--适配android 8.0-->
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
public static void installApk(Context mContext, File apkFile) {
try{
if (!apkFile.exists()) {
return;
}
Intent i = new Intent(Intent.ACTION_VIEW);
if (Build.VERSION.SDK_INT >= 24) { //适配安卓7.0
i.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_ACTIVITY_NEW_TASK); //添加这一句表示对目标应用临时授权该Uri所代表的文件
Uri apkFileUri = FileProvider.getUriForFile(mContext.getApplicationContext(),
mContext.getPackageName() + ".FileProvider", apkFile);
i.setDataAndType(apkFileUri, "application/vnd.android.package-archive");
} else {
i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
i.setDataAndType(Uri.parse("file://" + apkFile.toString()),
"application/vnd.android.package-archive");// File.toString()会返
回路径信息
}
mContext.startActivity(i);
}catch (Exception e){
e.printStackTrace();
MThoast.showShort(mContext,"自动安装失败,请尝试手动安装!");
}
}
到这里版本更新就好了,大家有问题可以留言。
demo地址:https://download.csdn.net/download/fzkf9225/10679015
我做了两个下载进度条,通知栏和dialog,我觉得一般用通知栏就可以了,dialog最好不用如果关闭下载进度的dialog弹框的话,注销相关DownloadProgressDialog类的相关代码即可
---------------------
作者:青穗CherishTang
来源:CSDN
原文:https://blog.csdn.net/fzkf9225/article/details/80969439
版权声明:本文为博主原创文章,转载请附上博文链接!
android 版本更新适配8.0,解决8.0手机无法更新自动安装apk的更多相关文章
- 下载apk安装包后,调用安装器自动安装apk(适配7.0)
在更新操作时,下载新apk之后,往往需要自动安装新apk,用以下代码即可安装下载在本地的apk文件(apkFile) Intent intent = new Intent(); intent.setA ...
- 命令行从Android手机中导出已安装APK的方法调研
一.背景 二.步骤 一.背景 很多时候,APK文件只存在于应用市场,在PC上无法直接下载.用手机下载下来后就直接安装了,也不能保存原始的APK文件. APK安装到手机后,Android系统会保存一份和 ...
- Android实现应用下载并自动安装apk包
安装: ? 1 2 3 4 5 String str = "/CanavaCancel.apk"; String fileName = Environment.getExterna ...
- 【我的Android进阶之旅】 解决bug: Expected file scheme in URI: content://downloads/my_downloads/12
一.错误描述 今天测试MM用HTC手机测试某个模块的时候crash了,抓log后发现是使用DownloadManager下载apk安装包然后自动安装的时候,抛了异常:java.lang.Illegal ...
- android6.0、7.0、8.0新特性总结之开发应用时加以考虑的一些主要变更。
android6.0 参考一:简书Android 6.0 新特性详解 参考二:关于Android6.0以上系统的权限问题 参考三:值得你关注的Android6.0上的重要变化(一) 参考四:值得你关注 ...
- Android 自己实现更新下载自动安装
1.一些公司开发完一款App之后可能并不会去上架App商店,但事后期也需要定时进行维护更新,所以会选择把打包好的apk 发布到自己的服务器,然后在数据库建一个版本号的表,然后剩下的就交给你androi ...
- android 开发 实现自动安装
场景:实现自动安装apk程序 注意:不能使用 intent.setDataAndType(Uri.parse(apkPath), "application/vnd.android.pack ...
- Android权限管理之RxPermission解决Android 6.0 适配问题
前言: 上篇重点学习了Android 6.0的运行时权限,今天还是围绕着Android 6.0权限适配来总结学习,这里主要介绍一下我们公司解决Android 6.0权限适配的方案:RxJava+RxP ...
- Android app 在线更新那点事儿(适配Android6.0、7.0、8.0)
一.前言 app在线更新是一个比较常见需求,新版本发布时,用户进入我们的app,就会弹出更新提示框,第一时间更新新版本app.在线更新分为以下几个步骤: 1, 通过接口获取线上版本号,versionC ...
随机推荐
- MapServer Tutorial——MapServer7.2.1教程学习——第一节用例实践:Example1.5 Adding a raster layer
MapServer Tutorial——MapServer7.2.1教程学习——第一节用例实践:Example1.5 Adding a raster layer 一.前言 MapServer不仅支持 ...
- 浮点型数据转整型的丢失精度问题(C++)
如下代码:http://ideone.com/xcgHgw #include <iostream> using namespace std; int main() { // your co ...
- phthon--------异常处理
一 什么是异常 异常就是程序运行时发生错误的信号(在程序出现错误时,则会产生一个异常,若程序没有处理它,则会抛出该异常,程序的运行也随之终止),在python中,错误触发的异常如下 而错误分成两种 # ...
- java多线程面试中常见知识点
1.进程和线程 (1)进程是资源分配的最小单位,线程是程序执行的最小单位. (2)进程有自己的独立地址空间,每启动一个进程,系统就会为它分配地址空间,建立数据表来维护代码段.堆栈段和数据段,这种操作非 ...
- linux使用npm成功安装命令后,执行时却报找不到命令的问题
# 使用npm安装serve命令 ~$ npm install serve --global 安装成功 # 可是执行命令会报错 ~$ serve -v bash: serve: command not ...
- python enumerate用法总结
enumerate()说明enumerate()是python的内置函数enumerate在字典上是枚举.列举的意思对于一个可迭代的(iterable)/可遍历的对象(如列表.字符串),enumera ...
- egret 添加帧动画
private showEffect(): void { //加载本地的帧动画资源 RES.getResByUrlNoCache("resource/assets/shenqi_eff.js ...
- 关于Linux时间设置的总结
系统环境:centos Linux的时间有两种,一种是系统时间,一种是硬件时间. 系统时间的查看:#date 系统时间的设置:#date -set 时间同步工具准备 Linux进行时间同步要使用一个工 ...
- Timer 的学习
Timer 实例化多个对象就会启动多个线程 TimerTask 中 捕获异常为基类Exception,那么出现异常后就继续执行.及时报错 TimerTask中未捕获异常或者捕获异常与程序抛出异常不一致 ...
- tensorFlow可以运行的代码
折腾了很久,终于运行成功. 才云科技的书不错,就是需要微调一二. 心得:1,记得activate tensorflow,然后再python 2,Python的代码格式很重要,不要错误. 3,还不清楚如 ...