最近研究了一下游戏内apk包更新的方法。

ios对于应用的管理比较严格,除非热更新脚本,不太可能做到端内大版本包的更新。然而安卓端则没有此限制。因此可以做到不跳到网页或应用商店,就覆盖更新apk包。

Unity最常用的脚本语言就是C#,不做断点续传的情况下,采用C#的网络库,还是比较简单的。重点就是做好相应的异常处理。

C#用于网络访问的方法主要有两种:WebRequest和封装好的WebClient。为了将来能做更多的扩展,我采用更灵活的HttpWebRequest进行请求。为了不阻塞主线程,使用异步接口。

基本做法可参考官方文档https://msdn.microsoft.com/zh-cn/library/system.net.httpwebrequest.begingetresponse(v=vs.110).aspx

然而我们知道,Unity4.X对于多线程的支持是很弱的,不推荐使用。因此,无法在下载线程中回调相应的事件。我将回调写在主线程中,用Coroutine去轮询当前的下载状态和进度,并做相应的处理。

首先需要定义下载的状态和传入下载线程的请求状态,然后是下载的路径(可能还需要文件MD5码)以及安装路径等必要的变量,最后为了显示当前的下载进度、下载速度等,需要开启一个Coroutine或者在Update中不断查询当前下载状态,是否有异常,以及是否已经下载完毕。如果下载完毕,则校验文件,并开始安装。

 using UnityEngine;
using System;
using System.Collections;
using System.Threading;
using System.IO;
using System.Net;
using System.Security.Cryptography;
using System.Text;
using System; public class VersionUpdater : MonoBehaviour
{
public class RequestState
{
public const int BUFFER_SIZE = ;
public byte[] BufferRead;
public HttpWebRequest request;
public HttpWebResponse response;
public Stream responseStream;
} public enum DownloadState
{
DOWNLOADING,
FINISHED,
FAILED
} public delegate void ProgressCallback(long curr, long length, float rate, DownloadState state);
public ProgressCallback progressCallback; string url = "";
string installPath = "";
string apkName = "";
string errorMsg = ""; private FileStream fileStream = null;
private long length = ;
private long curr = ;
private long last = ;
private const float UpdateTime = 0.5f;
private float rate = ;
private DownloadState downState = DownloadState.DOWNLOADING; public void DownloadApkAsync(string url, string md5, string path, string name)
{
this.url = url;
this.installPath = path;
this.apkName = name;
this.errorMsg = "";
downState = DownloadState.DOWNLOADING; DownloadApkAsync();
} private void DownloadApkAsync()
{
if (string.IsNullOrEmpty(url)) return;
if (string.IsNullOrEmpty(installPath)) return;
if (string.IsNullOrEmpty(apkName)) return; string fullpath = installPath + "/" + apkName; IAsyncResult result = null;
try
{
fileStream = new FileStream(fullpath, FileMode.Create, FileAccess.Write); Uri uri = new Uri(url);
HttpWebRequest request = (HttpWebRequest)WebRequest.Create(uri); request.Method = "GET"; RequestState requestState = new RequestState();
requestState.BufferRead = new byte[RequestState.BUFFER_SIZE];
requestState.request = request; curr = ;
length = ;
rate = 0.0f;
downState = DownloadState.DOWNLOADING;
result = (IAsyncResult)request.BeginGetResponse(new AsyncCallback(ResponeCallback), requestState);
}
catch (Exception e)
{
errorMsg = "Begin Create Exception!";
errorMsg += string.Format("Message:{0}", e.Message);
StopDownload(result);
downState = DownloadState.FAILED;
} StartCoroutine(updateProgress());
} IEnumerator updateProgress()
{
while (curr <= length)
{
yield return new WaitForSeconds(UpdateTime); rate = (curr - last) / UpdateTime;
last = curr; if (downState == DownloadState.FAILED)
{
Debug.LogError(errorMsg);
if (fileStream != null)
fileStream.Close();
if (progressCallback != null)
progressCallback( curr, length, rate, DownloadState.FAILED);
break;
} if (progressCallback != null)
progressCallback( curr, length, rate, DownloadState.DOWNLOADING); if (downState == DownloadState.FINISHED)
{
if (progressCallback != null)
progressCallback( curr, length, rate, DownloadState.FINISHED);
break;
}
}
} void StopDownload(IAsyncResult result)
{
if (result == null) return;
RequestState requestState = (RequestState)result.AsyncState;
requestState.request.Abort();
} void ResponeCallback(IAsyncResult result)
{
try
{
if (downState != DownloadState.FAILED)
{
RequestState requestState = (RequestState)result.AsyncState;
HttpWebRequest request = requestState.request;
requestState.response = (HttpWebResponse)request.EndGetResponse(result); Stream responseStream = requestState.response.GetResponseStream();
requestState.responseStream = responseStream; length = requestState.response.ContentLength; IAsyncResult readResult = responseStream.BeginRead(requestState.BufferRead, , RequestState.BUFFER_SIZE, new AsyncCallback(ReadCallback), requestState);
return;
}
}
catch (Exception e)
{
string msg = "ResponseCallback exception!\n";
msg += string.Format("Message:{0}", e.Message);
StopDownload(result);
errorMsg = msg;
downState = DownloadState.FAILED;
}
} void ReadCallback(IAsyncResult result)
{
try
{
if (downState != DownloadState.FAILED)
{
RequestState requestState = (RequestState)result.AsyncState;
Stream responseStream = requestState.responseStream;
int read = responseStream.EndRead(result);
if (read > )
{
fileStream.Write(requestState.BufferRead, , read);
fileStream.Flush();
curr += read; IAsyncResult readResult = responseStream.BeginRead(requestState.BufferRead, , RequestState.BUFFER_SIZE, new AsyncCallback(ReadCallback), requestState);
return;
}
else
{
Debug.Log("download end");
responseStream.Close();
fileStream.Close(); downState = DownloadState.FINISHED;
}
}
}
catch (Exception e)
{
string msg = "ReadCallBack exception!";
msg += string.Format("Message:{0}", e.Message);
StopDownload(result);
errorMsg = msg;
downState = DownloadState.FAILED;
}
} public void InstallApk()
{
#if UNITY_ANDROID && !UNITY_EDITOR
Debug.Log("begin install");
using (AndroidJavaObject jo = new AndroidJavaObject("com.kevonyang.androidhelper.AndroidHelper"))
{
if (jo == null)
{
WMDebug.Debug.LogError("VersionUpdater: Failed to get com.kevonyang.androidhelper.AndroidHelper");
return;
}
using (AndroidJavaClass jc = new AndroidJavaClass("com.unity3d.player.UnityPlayer"))
{
if (jc == null)
{
WMDebug.Debug.LogError("VersionUpdater: Failed to get com.unity3d.player.UnityPlayer");
return;
}
AndroidJavaObject m_jo = jc.GetStatic<AndroidJavaObject>("currentActivity");
if (m_jo == null)
{
WMDebug.Debug.LogError("VersionUpdater: Failed to get currentActivity");
return;
} jo.CallStatic("InstallApk", m_jo, installPath, apkName);
}
}
#endif
}
}

在下载完毕后,需要写一个java类,并在里面调用安装接口。内容很简单,只需要简单的启动一个安装的Intent就可以了,随后就会出现系统提示,是否覆盖安装。至此,游戏内的下载及安装全部完成,等待覆盖安装完毕即可从新的客户端启动。

     public static void InstallApk(Context context, String path, String name) {
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setDataAndType(Uri.fromFile(new File(path, name)), "application/vnd.android.package-archive");
context.startActivity(intent);
}

Unity游戏内版本更新的更多相关文章

  1. Unity大中华区主办 第二届Unity 游戏及应用大赛 实力派精品手游盘点

    Unity是由Unity Technologies开发的一个让玩家轻松创建诸如三维视频游戏.建筑可视化.实时三维动画等类型互动内容的多平台的综合型游戏开发工具,是一个全面整合的专业游戏引擎.包含如今时 ...

  2. 2017年Unity游戏开发视频教程(入门到精通)

    本文是我发布的一个Unity游戏开发的学习目录,以后我会持续发布一系列的游戏开发教程,都会更新在这个页面上,适合人群有下面的几种: 想要做独立游戏的人 想要找游戏开发相关工作的人 对游戏开发感兴趣的人 ...

  3. 【Unity游戏开发】浅谈Lua和C#中的闭包

    一.前言 目前在Unity游戏开发中,比较流行的两种语言就是Lua和C#.通常的做法是:C#做些核心的功能和接口供Lua调用,Lua主要做些UI模块和一些业务逻辑.这样既能在保持一定的游戏运行效率的同 ...

  4. Unity外包团队:关于手机unity游戏开发的技术选型

    技术选型 Unity引擎内置了多人联机的解决方案,涵盖了从最底层的网络数据传输,到不同玩家之间的消息发送,再到游戏大厅这样的高级功能.考虑到Unity官方提供的云服务(Internet Service ...

  5. C#开发Unity游戏教程之游戏对象的行为逻辑方法

    C#开发Unity游戏教程之游戏对象的行为逻辑方法 游戏对象的行为逻辑——方法 方法(method),读者在第1章新建脚本时就见过了,而且在第2章对脚本做整体上的介绍时也介绍过,那么上一章呢,尽管主要 ...

  6. C#开发Unity游戏教程之游戏对象的属性变量

    C#开发Unity游戏教程之游戏对象的属性变量 Unity游戏对象的属性——变量 通过对上一章的学习,读者应该了解到了,游戏对象上的属性与脚本中的变量,建立联系的方式就是将脚本赋予游戏对象.上一章只是 ...

  7. Unity即将内置骨骼动画插件Anima2D

    Unity一直在寻找新的方法来帮助开发者,并为他们提供最好的工具.在此我们向大家宣布,Unity将内置流行的骨骼动画插件Anima2D,从2017年1月开始免费供所有Unity开发者使用! 同时也欢迎 ...

  8. Unity优化方向——优化Unity游戏中的脚本(译)

    原文地址:https://unity3d.com/cn/learn/tutorials/topics/performance-optimization/optimizing-scripts-unity ...

  9. unity游戏在ios11上不显示泰语解决办法

    最近在开发中遇到unity游戏在ios11上不显示泰语的问题,全部显示为方框内一个问号. 通过搜索发现这是Unity的一个bug,在2017.3中修复了 但升级unity风险很大,所以我采用了该文中提 ...

随机推荐

  1. 图片访问实时处理的实现(nodejs和php)

    我在访问时光网.网易云音乐等网站时,发现将它们页面中的一些图片URL修改一下就可以得到不同尺寸的图片,于是思考了其实现方案,我的思路是:URL Rewrite + 实时处理 + 缓存,对用户请求的UR ...

  2. 基于netty http协议栈的轻量级流程控制组件的实现

    今儿个是冬至,所谓“冬大过年”,公司也应景五点钟就放大伙儿回家吃饺子喝羊肉汤了,而我本着极高的职业素养依然坚持留在公司(实则因为没饺子吃没羊肉汤喝,只能呆公司吃食堂……).趁着这一个多小时的时间,想跟 ...

  3. Java中Comparable与Comparator的区别

    相同 Comparable和Comparator都是用来实现对象的比较.排序 要想对象比较.排序,都需要实现Comparable或Comparator接口 Comparable和Comparator都 ...

  4. UE4新手引导入门教程

    请大家去这个地址下载:file:///D:/UE4%20Doc/虚幻4新手引导入门教程.pdf

  5. node中子进程同步输出

    管道 通过"child_process"模块fork出来的子进程都是返回一个ChildProcess对象实例,ChildProcess类比较特殊无法手动创建该对象实例,只能使用fo ...

  6. 【基于WinForm+Access局域网共享数据库的项目总结】之篇一:WinForm开发总体概述与技术实现

    篇一:WinForm开发总体概述与技术实现 篇二:WinForm开发扇形图统计和Excel数据导出 篇三:Access远程连接数据库和窗体打包部署 [小记]:最近基于WinForm+Access数据库 ...

  7. .Net Core上也可以使用的二维码组件

    我Fork了QRCoder,并且兼容了.Net Core,图形库用的是ZKWeb.System.Drawing Github: https://github.com/zkweb-framework/Q ...

  8. 【知识必备】ezSQL,最好用的数据库操作类,让php操作sql更简单~

    最近用php做了点小东东,用上了ezSQL,感觉真的很ez,所以拿来跟大家分享一下~ ezSQL是一个非常好用的PHP数据库操作类.著名的开源博客WordPress的数据库操作就使用了ezSQL的My ...

  9. WebSocket - ( 一.概述 )

    说到 WebSocket,不得不提 HTML5,作为近年来Web技术领域最大的改进与变化,包含CSS3.离线与存储.多媒体.连接性( Connectivity )等一系列领域,而即将介绍的 WebSo ...

  10. EC笔记:第4部分:21、必须返回对象时,别返回引用

    使用应用可以大幅减少构造函数与析构函数的调用次数,但是引用不可以滥用. 如下: struct St { int a; }; St &func(){ St t; return t; } 在返回t ...