什么是热更新,然后怎么实现热更新呢?这里我向大家谈谈我的理解。热更新就是动态下发代码,就是已经上线的作品,在不发布新版本的情况下,只更新作品的一部分,实现修复BUG或者发布新功能。最开始是让开发者绕开苹果的审核机制,避免长时间的审核等待以及多次被拒造成的成本。这里给大家介绍下Egret官方文档的Android热更新方案。首先要说的是热更新的功能是要开发者自己去实现的,例如如果项目是Android项目,那么热更新就要在Android项目里用java实现,因为Android是用java开发的,所以你改Egret引擎里的东西其实是起不了多大作用的,换句话说就是热更新跟白鹭引擎没有什么很大的关系。 首先要做的就是修改config.preloadPath来指定预加载目录,开发者需要自行维护这个目录下的内容。 Android:
//MainActivity.java
nativeAndroid.config.preloadPath=”指定目录“;
可以简单地理解为,将游戏部署到手机上地目录下,然后打开这个目录下的游戏。需要更新某个资源时,只需要更新这个目录上对应的资源即可。
MainActivity.java
package org.egret.testUpdate;
import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.util.Log;
import android.view.KeyEvent;
import android.widget.Toast;
import org.egret.runtime.launcherInterface.INativePlayer;
import org.egret.egretnativeandroid.EgretNativeAndroid;
public class MainActivity extends Activity {
private final String TAG = "MainActivity";
private EgretNativeAndroid nativeAndroid;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
nativeAndroid = new EgretNativeAndroid(this);
if (!nativeAndroid.checkGlEsVersion()) {
Toast.makeText(this, "This device does not support OpenGL ES 2.0.",
Toast.LENGTH_LONG).show();
return;
}
nativeAndroid.config.showFPS = true;
nativeAndroid.config.fpsLogTime = 30;
nativeAndroid.config.disableNativeRender = false;
nativeAndroid.config.clearCache = false;
nativeAndroid.config.loadingTimeout = 0;
Intent intent = getIntent();
nativeAndroid.config.preloadPath = intent.getStringExtra("preloadPath");
setExternalInterfaces();
if (!nativeAndroid.initialize("http://game.com/game/index.html")) {
Toast.makeText(this, "Initialize native failed.",
Toast.LENGTH_LONG).show();
return;
}
setContentView(nativeAndroid.getRootFrameLayout());
}
@Override
protected void onPause() {
super.onPause();
nativeAndroid.pause();
}
@Override
protected void onResume() {
super.onResume();
nativeAndroid.resume();
}
@Override
public boolean onKeyDown(final int keyCode, final KeyEvent keyEvent) {
if (keyCode == KeyEvent.KEYCODE_BACK) {
nativeAndroid.exitGame();
}
return super.onKeyDown(keyCode, keyEvent);
}
private void setExternalInterfaces() {
nativeAndroid.setExternalInterface("sendToNative", new INativePlayer.INativeInterface() {
@Override
public void callback(String message) {
String str = "Native get message: ";
str += message;
Log.d(TAG, str);
nativeAndroid.callExternalInterface("sendToJS", str);
}
});
}
@Override
protected void onDestroy() {
super.onDestroy();
}
热更新的实现代码:
LaunchActivity.java
package org.egret.testUpdate;
import android.Manifest;
import android.app.Activity;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.os.Build;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
public class LaunchActivity extends Activity {
private Button btn_load;
private Button btn_game;
private final String gameUrl = "http://game.com/game/index.html";
private final String zipUrl = "http://tool.egret-labs.org/Weiduan/game/game2.zip";
private final String preloadPath = "/sdcard/egretGame/";
private static String[] permissions = {Manifest.permission.WRITE_EXTERNAL_STORAGE};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_launch);
btn_load = (Button)findViewById(R.id.btn_load);
btn_game = (Button)findViewById(R.id.btn_game);
btn_load.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
btn_load.setEnabled(false);
btn_game.setEnabled(false);
preloadGame();
}
});
btn_game.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent(LaunchActivity.this, MainActivity.class);
intent.putExtra("preloadPath", preloadPath);
startActivity(intent);
}
});
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
int check = checkSelfPermission(permissions[0]);
if (check != PackageManager.PERMISSION_GRANTED) {
requestPermissions(permissions, 111);
}
}
}
private void preloadGame() {
String dir = preloadPath + getFileDirByUrl(gameUrl);
File dirFile = new File(dir);
if (!dirFile.exists()) {
dirFile.mkdirs();
}
downloadGameRes(zipUrl, dir);
}
private void downloadGameRes(final String zipUrl, String targetDir) {
String tempZipFileName = targetDir + "game.zip";
final File file = new File(tempZipFileName);
Runnable runnable = new Runnable() {
@Override
public void run() {
InputStream inputStream = null;
FileOutputStream outputStream = null;
HttpURLConnection connection = null;
boolean finish = false;
try {
URL url = new URL(zipUrl);
connection = (HttpURLConnection)url.openConnection();
int code = connection.getResponseCode();
if (code == 200) {
inputStream = connection.getInputStream();
outputStream = new FileOutputStream(file, true);
byte[] buffer = new byte[4096];
int length;
while ((length = inputStream.read(buffer)) != -1) {
outputStream.write(buffer, 0, length);
}
}
finish = true;
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (outputStream != null) {
outputStream.close();
}
if (inputStream != null) {
inputStream.close();
}
if (connection != null) {
connection.disconnect();
}
} catch (Exception e) {
e.printStackTrace();
return;
}
}
if (finish) {
unzip(file);
}
}
};
new Thread(runnable).start();
}
private void unzip(File file) {
int BUFFER = 4096;
String strEntry;
String targetDir = file.getParent() + "/";
try {
BufferedOutputStream dest = null;
FileInputStream fis = new FileInputStream(file.getAbsolutePath());
ZipInputStream zis = new ZipInputStream(new BufferedInputStream(fis));
ZipEntry entry;
while ((entry = zis.getNextEntry()) != null) {
try {
int count;
byte data[] = new byte[BUFFER];
strEntry = entry.getName();
File entryFile = new File(targetDir + strEntry);
if (strEntry.endsWith("/")) {
entryFile.mkdirs();
continue;
}
File entryDir = new File(entryFile.getParent());
if (!entryDir.exists()) {
entryDir.mkdirs();
}
FileOutputStream fos = new FileOutputStream(entryFile);
dest = new BufferedOutputStream(fos, BUFFER);
while ((count = zis.read(data, 0, BUFFER)) != -1) {
dest.write(data, 0, count);
}
dest.flush();
dest.close();
} catch (Exception ex) {
ex.printStackTrace();
return;
}
}
zis.close();
} catch (Exception e) {
e.printStackTrace();
return;
}
file.delete();
runOnUiThread(new Runnable() {
@Override
public void run() {
btn_game.setEnabled(true);
}
});
}
private static String getFileDirByUrl(String urlString) {
int lastSlash = urlString.lastIndexOf('/');
String server = urlString.substring(0, lastSlash + 1);
return server.replaceFirst("://", "/").replace(":", "#0A");
}
}
什么是热更新,然后怎么实现热更新呢?这里我向大家谈谈我的理解。热更新就是动态下发代码,就是已经上线的作品,在不发布新版本的情况下,只更新作品的一部分,实现修复BUG或者发布新功能。最开始是让开发者绕开苹果的审核机制,避免长时间的审核等待以及多次被拒造成的成本。这里给大家介绍下Egret官方文档的Android热更新方案。首先要说的是热更新的功能是要开发者自己去实现的,例如如果项目是Android项目,那么热更新就要在Android项目里用java实现,因为Android是用java开发的,所以你改Egret引擎里的东西其实是起不了多大作用的,换句话说就是热更新跟白鹭引擎没有什么很大的关系。 首先要做的就是修改config.preloadPath来指定预加载目录,开发者需要自行维护这个目录下的内容。 Android: //MainActivity.java nativeAndroid.config.preloadPath=”指定目录“; 可以简单地理解为,将游戏部署到手机上地目录下,然后打开这个目录下的游戏。需要更新某个资源时,只需要更新这个目录上对应的资源即可。 MainActivity.java package org.egret.testUpdate;
import android.app.Activity; import android.content.Intent; import android.os.Bundle; import android.util.Log; import android.view.KeyEvent; import android.widget.Toast;
import org.egret.runtime.launcherInterface.INativePlayer; import org.egret.egretnativeandroid.EgretNativeAndroid;
public class MainActivity extends Activity { private final String TAG = "MainActivity"; private EgretNativeAndroid nativeAndroid;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
nativeAndroid = new EgretNativeAndroid(this);
if (!nativeAndroid.checkGlEsVersion()) {
Toast.makeText(this, "This device does not support OpenGL ES 2.0.",
Toast.LENGTH_LONG).show();
return;
}
nativeAndroid.config.showFPS = true;
nativeAndroid.config.fpsLogTime = 30;
nativeAndroid.config.disableNativeRender = false;
nativeAndroid.config.clearCache = false;
nativeAndroid.config.loadingTimeout = 0;
Intent intent = getIntent();
nativeAndroid.config.preloadPath = intent.getStringExtra("preloadPath");
setExternalInterfaces();
if (!nativeAndroid.initialize("http://game.com/game/index.html")) {
Toast.makeText(this, "Initialize native failed.",
Toast.LENGTH_LONG).show();
return;
}
setContentView(nativeAndroid.getRootFrameLayout());
}
@Override
protected void onPause() {
super.onPause();
nativeAndroid.pause();
}
@Override
protected void onResume() {
super.onResume();
nativeAndroid.resume();
}
@Override
public boolean onKeyDown(final int keyCode, final KeyEvent keyEvent) {
if (keyCode == KeyEvent.KEYCODE_BACK) {
nativeAndroid.exitGame();
}
return super.onKeyDown(keyCode, keyEvent);
}
private void setExternalInterfaces() {
nativeAndroid.setExternalInterface("sendToNative", new INativePlayer.INativeInterface() {
@Override
public void callback(String message) {
String str = "Native get message: ";
str += message;
Log.d(TAG, str);
nativeAndroid.callExternalInterface("sendToJS", str);
}
});
}
@Override
protected void onDestroy() {
super.onDestroy();
}
热更新的实现代码: LaunchActivity.java package org.egret.testUpdate;
import android.Manifest; import android.app.Activity; import android.content.Intent; import android.content.pm.PackageManager; import android.os.Build; import android.os.Bundle; import android.util.Log; import android.view.View; import android.widget.Button;
import java.io.BufferedInputStream; import java.io.BufferedOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.InputStream; import java.net.HttpURLConnection; import java.net.URL; import java.util.zip.ZipEntry; import java.util.zip.ZipInputStream;
public class LaunchActivity extends Activity {
private Button btn_load;
private Button btn_game;
private final String gameUrl = "http://game.com/game/index.html";
private final String zipUrl = "http://tool.egret-labs.org/Weiduan/game/game2.zip";
private final String preloadPath = "/sdcard/egretGame/";
private static String[] permissions = {Manifest.permission.WRITE_EXTERNAL_STORAGE};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_launch);
btn_load = (Button)findViewById(R.id.btn_load);
btn_game = (Button)findViewById(R.id.btn_game);
btn_load.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
btn_load.setEnabled(false);
btn_game.setEnabled(false);
preloadGame();
}
});
btn_game.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent(LaunchActivity.this, MainActivity.class);
intent.putExtra("preloadPath", preloadPath);
startActivity(intent);
}
});
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
int check = checkSelfPermission(permissions[0]);
if (check != PackageManager.PERMISSION_GRANTED) {
requestPermissions(permissions, 111);
}
}
}
private void preloadGame() {
String dir = preloadPath + getFileDirByUrl(gameUrl);
File dirFile = new File(dir);
if (!dirFile.exists()) {
dirFile.mkdirs();
}
downloadGameRes(zipUrl, dir);
}
private void downloadGameRes(final String zipUrl, String targetDir) {
String tempZipFileName = targetDir + "game.zip";
final File file = new File(tempZipFileName);
Runnable runnable = new Runnable() {
@Override
public void run() {
InputStream inputStream = null;
FileOutputStream outputStream = null;
HttpURLConnection connection = null;
boolean finish = false;
try {
URL url = new URL(zipUrl);
connection = (HttpURLConnection)url.openConnection();
int code = connection.getResponseCode();
if (code == 200) {
inputStream = connection.getInputStream();
outputStream = new FileOutputStream(file, true);
byte[] buffer = new byte[4096];
int length;
while ((length = inputStream.read(buffer)) != -1) {
outputStream.write(buffer, 0, length);
}
}
finish = true;
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (outputStream != null) {
outputStream.close();
}
if (inputStream != null) {
inputStream.close();
}
if (connection != null) {
connection.disconnect();
}
} catch (Exception e) {
e.printStackTrace();
return;
}
}
if (finish) {
unzip(file);
}
}
};
new Thread(runnable).start();
}
private void unzip(File file) {
int BUFFER = 4096;
String strEntry;
String targetDir = file.getParent() + "/";
try {
BufferedOutputStream dest = null;
FileInputStream fis = new FileInputStream(file.getAbsolutePath());
ZipInputStream zis = new ZipInputStream(new BufferedInputStream(fis));
ZipEntry entry;
while ((entry = zis.getNextEntry()) != null) {
try {
int count;
byte data[] = new byte[BUFFER];
strEntry = entry.getName();
File entryFile = new File(targetDir + strEntry);
if (strEntry.endsWith("/")) {
entryFile.mkdirs();
continue;
}
File entryDir = new File(entryFile.getParent());
if (!entryDir.exists()) {
entryDir.mkdirs();
}
FileOutputStream fos = new FileOutputStream(entryFile);
dest = new BufferedOutputStream(fos, BUFFER);
while ((count = zis.read(data, 0, BUFFER)) != -1) {
dest.write(data, 0, count);
}
dest.flush();
dest.close();
} catch (Exception ex) {
ex.printStackTrace();
return;
}
}
zis.close();
} catch (Exception e) {
e.printStackTrace();
return;
}
file.delete();
runOnUiThread(new Runnable() {
@Override
public void run() {
btn_game.setEnabled(true);
}
});
}
private static String getFileDirByUrl(String urlString) {
int lastSlash = urlString.lastIndexOf('/');
String server = urlString.substring(0, lastSlash + 1);
return server.replaceFirst("://", "/").replace(":", "#0A");
}
} 这里给大家简单的理解下,这里热更新的实现是靠2个按钮触发的,一个是 btn_load按钮这个按钮就是热更新的触发按钮,一个是btn_game按钮这个按钮就是进入游戏的按钮。当第一次点击btn_game按钮时,就是进入初始的游戏界面,就是没有经过热更新的游戏界面。当点击了btn_load按钮后,再点击btn_game按钮就是进入了已经热更新后的游戏界面。点击btn_load按钮后,手机会自动下载解压压缩包,很多人说egret官方文档的示例是错的,其实不是,在手机里的文件管理里没有找到热更新的压缩包,其实照成这样的原因是下载的压缩包已经被解压了,当然也就不存在了,再怎么找也找不到。 我这里测试的地址是一个空的不存在的地址,game.com/index.html,…
就如预想的那样显示这个地址找不到。(ps:特别要注意的一点就是进入游戏首先不能先点击btn_load按钮,这个热更新按钮。要先点击btn_game按钮,因为我们要得到的是没有热更新的游戏界面。) 然后再点击btn_load按钮,进行下载新的压缩包,更新游戏界面。点击后就会出现如图画面。
这是就是已经热更新了,这时就是已经下载并解压了压缩包。然后我们再点击btn_game按钮,进入游戏界面,显示如图:
这就是热更新后的游戏界面。看来是成功了。 这里要说的就是如果你退出这个程序,再点击btn_game按钮就会显示已经热更新后的游戏画面,而不是404的画面。你再点击btn_load按钮,再进入游戏,游戏画面依然不会变。有很多人就以为是热更新没有实现,其实并不是,而是你第一次运行这个程序,已经实现了热更新的功能,你再次进入游戏的时候,这个游戏已经热更新了,你再次点击热更新,更新相同的资源,显示的画面也是一样的。所以说官网的示例是没毛病的。热更新的现象,只有第一次运行这个游戏,进行热更新才会显示出来。当你再次运行这个游戏的是时候,这个游戏都是已经进行了热更新了,所以没有热更新的现象显示。这个问题一直困惑了我好久,我一直以为是示例的热更新代码出问题,可是看了琢磨了好久,发现热更新的代码的逻辑是行得通得,没有什么毛病,一直想不到问题会出在哪,后来玩王者荣耀时,刚好也要更新,更新后进入游戏的界面就变了,然后退了,再次进入游戏的时候,显示就是更新后的游戏界面,而不会显示更新前的游戏界面,突然间就想同了这个问题,实在的困惑了好久,一直在测试,一直在看代码,就是不知道哪里出了问题。现在解决了,真的是一身轻松。 如果有什么疑问,欢迎在帖子下留言。