Unity游戏内版本更新

最近研究了一下游戏内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中不断查询当前下载状态,是否有异常,以及是否已经下载完毕。如果下载完毕,则校验文件,并开始安装。

  1 using UnityEngine;
  2 using System;
  3 using System.Collections;
  4 using System.Threading;
  5 using System.IO;
  6 using System.Net;
  7 using System.Security.Cryptography;
  8 using System.Text;
  9 using System;
 10 
 11 public class VersionUpdater : MonoBehaviour
 12 {
 13     public class RequestState
 14     {
 15         public const int BUFFER_SIZE = 1024;
 16         public byte[] BufferRead;
 17         public HttpWebRequest request;
 18         public HttpWebResponse response;
 19         public Stream responseStream;
 20     }
 21 
 22     public enum DownloadState
 23     {
 24         DOWNLOADING,
 25         FINISHED,
 26         FAILED
 27     }
 28 
 29     public delegate void ProgressCallback(long curr, long length, float rate, DownloadState state);
 30     public ProgressCallback progressCallback;
 31 
 32     string url = "";
 33     string installPath = "";
 34     string apkName = "";
 35     string errorMsg = "";
 36 
 37     private FileStream fileStream = null;
 38     private long length = 1;
 39     private long curr = 0;
 40     private long last = 0;
 41     private const float UpdateTime = 0.5f;
 42     private float rate = 0;
 43     private DownloadState downState = DownloadState.DOWNLOADING;
 44 
 45 
 46     public void DownloadApkAsync(string url, string md5, string path, string name)
 47     {
 48         this.url = url;
 49         this.installPath = path;
 50         this.apkName = name;
 51         this.errorMsg = "";
 52         downState = DownloadState.DOWNLOADING;
 53 
 54         DownloadApkAsync();
 55     }
 56 
 57     private void DownloadApkAsync()
 58     {
 59         if (string.IsNullOrEmpty(url)) return;
 60         if (string.IsNullOrEmpty(installPath)) return;
 61         if (string.IsNullOrEmpty(apkName)) return;
 62 
 63         string fullpath = installPath + "/" + apkName;
 64 
 65         IAsyncResult result = null;
 66         try
 67         {
 68             fileStream = new FileStream(fullpath, FileMode.Create, FileAccess.Write);
 69 
 70             Uri uri = new Uri(url);
 71             HttpWebRequest request = (HttpWebRequest)WebRequest.Create(uri);
 72 
 73             request.Method = "GET";
 74 
 75             RequestState requestState = new RequestState();
 76             requestState.BufferRead = new byte[RequestState.BUFFER_SIZE];
 77             requestState.request = request;
 78 
 79             curr = 0;
 80             length = 1;
 81             rate = 0.0f;
 82             downState = DownloadState.DOWNLOADING;
 83             result = (IAsyncResult)request.BeginGetResponse(new AsyncCallback(ResponeCallback), requestState);
 84         }
 85         catch (Exception e)
 86         {
 87             errorMsg = "Begin Create Exception!";
 88             errorMsg += string.Format("Message:{0}", e.Message);
 89             StopDownload(result);
 90             downState = DownloadState.FAILED;
 91         }
 92 
 93         StartCoroutine(updateProgress());
 94     }
 95 
 96     IEnumerator updateProgress()
 97     {
 98         while (curr <= length)
 99         {
100             yield return new WaitForSeconds(UpdateTime);
101 
102             rate = (curr - last) / UpdateTime;
103             last = curr;
104 
105             if (downState == DownloadState.FAILED)
106             {
107                 Debug.LogError(errorMsg);
108                 if (fileStream != null)
109                     fileStream.Close();
110                 if (progressCallback != null)
111                     progressCallback( curr, length, rate, DownloadState.FAILED);
112                 break;
113             }
114 
115             if (progressCallback != null)
116                 progressCallback( curr, length, rate, DownloadState.DOWNLOADING);
117 
118             if (downState == DownloadState.FINISHED)
119             {
120                 if (progressCallback != null)
121                     progressCallback( curr, length, rate, DownloadState.FINISHED);
122                 break;
123             }
124         }
125     }
126 
127     void StopDownload(IAsyncResult result)
128     {
129         if (result == null) return;
130         RequestState requestState = (RequestState)result.AsyncState;
131         requestState.request.Abort();
132     }
133 
134     void ResponeCallback(IAsyncResult result)
135     {
136         try
137         {
138             if (downState != DownloadState.FAILED)
139             {
140                 RequestState requestState = (RequestState)result.AsyncState;
141                 HttpWebRequest request = requestState.request;
142                 requestState.response = (HttpWebResponse)request.EndGetResponse(result);
143 
144                 Stream responseStream = requestState.response.GetResponseStream();
145                 requestState.responseStream = responseStream;
146 
147                 length = requestState.response.ContentLength;
148 
149                 IAsyncResult readResult = responseStream.BeginRead(requestState.BufferRead, 0, RequestState.BUFFER_SIZE, new AsyncCallback(ReadCallback), requestState);
150                 return;
151             }
152         }
153         catch (Exception e)
154         {
155             string msg = "ResponseCallback exception!\n";
156             msg += string.Format("Message:{0}", e.Message);
157             StopDownload(result);
158             errorMsg = msg;
159             downState = DownloadState.FAILED;
160         }
161     }
162 
163     void ReadCallback(IAsyncResult result)
164     {
165         try
166         {
167             if (downState != DownloadState.FAILED)
168             {
169                 RequestState requestState = (RequestState)result.AsyncState;
170                 Stream responseStream = requestState.responseStream;
171                 int read = responseStream.EndRead(result);
172                 if (read > 0)
173                 {
174                     fileStream.Write(requestState.BufferRead, 0, read);
175                     fileStream.Flush();
176                     curr += read;
177 
178                     IAsyncResult readResult = responseStream.BeginRead(requestState.BufferRead, 0, RequestState.BUFFER_SIZE, new AsyncCallback(ReadCallback), requestState);
179                     return;
180                 }
181                 else
182                 {
183                     Debug.Log("download end");
184                     responseStream.Close();
185                     fileStream.Close();
186 
187                     downState = DownloadState.FINISHED;
188                 }
189             }
190         }
191         catch (Exception e)
192         {
193             string msg = "ReadCallBack exception!";
194             msg += string.Format("Message:{0}", e.Message);
195             StopDownload(result);
196             errorMsg = msg;
197             downState = DownloadState.FAILED;
198         }
199     }
200 
201 
202     public void InstallApk()
203     {
204 #if UNITY_ANDROID && !UNITY_EDITOR
205         Debug.Log("begin install");
206         using (AndroidJavaObject jo = new AndroidJavaObject("com.kevonyang.androidhelper.AndroidHelper"))
207         {
208             if (jo == null)
209             {
210                 WMDebug.Debug.LogError("VersionUpdater: Failed to get com.kevonyang.androidhelper.AndroidHelper");
211                 return;
212             }
213             using (AndroidJavaClass jc = new AndroidJavaClass("com.unity3d.player.UnityPlayer"))
214             {
215                 if (jc == null)
216                 {
217                     WMDebug.Debug.LogError("VersionUpdater: Failed to get com.unity3d.player.UnityPlayer");
218                     return;
219                 }
220                 AndroidJavaObject m_jo = jc.GetStatic<AndroidJavaObject>("currentActivity");
221                 if (m_jo == null)
222                 {
223                     WMDebug.Debug.LogError("VersionUpdater: Failed to get currentActivity");
224                     return;
225                 }
226 
227                 jo.CallStatic("InstallApk", m_jo, installPath, apkName);
228             }
229         }
230 #endif
231     }
232 }
View Code

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

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

 

posted @ 2015-12-09 01:56  穆游  阅读(5570)  评论(0编辑  收藏  举报