这个的分享个人认为还是很有用的,unity的WWW类的扩展,可以更容易的操作请求头,例如cookie等,并且加入了自定义时间长短的超时的控制。
另外,移除了调用方代码对协程的依赖。例如进度等可以通过封装类来更加的面向对象的获取。
请求完成通过委托回调来触发。
其中的中文注释被同事搞乱码了。我改回来了一部分成英文,将就看吧。。。
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
using System;
using System.IO;
using System.Text;
/// <summary>
/// Unity WWW extends,don`t support chinese url
/// yx
/// </summary>
public class WWWEX {
/// <summary>
/// ios backup to cloud flag
/// </summary>
public static bool IosBackUp = false;
public static int max = 10;
public static bool Log = false;
public const float DefaultTimeout = 5;//s
static List<WWW> pool = new List<WWW>(max);
WWW instwww = null;
public float Progress {
get {
if (instwww == null)
return 0;
else {
return instwww.progress;
}
}
}
public bool IsDown {
get {
if (instwww == null)
return false;
else {
return instwww.isDone;
}
}
}
/// <summary>
/// set a function to make cache path,it`s not necessary
/// </summary>
public Func<string, string> CachePathInterface;
private bool disposed = false;
public bool IsDisposed {
get {
return disposed;
}
}
public WWWEX() {
CachePathInterface = GetCacheURIEX;
}
/// <summary>
/// if you want to save file to path with your mind,use this constructor to new a inst
/// </summary>
/// <param name="RewindCachePath"></param>
public WWWEX(Func<string, string> RewindCachePath) {
CachePathInterface = RewindCachePath;
}
/// <summary>
/// load from cache or web,arg:version`s usage is like www;
/// </summary>
public void LoadFromCacheOrDownload(string url, string version, Action<WWWResult> callback, float timeout = DefaultTimeout) {
disposed = false;
CoroutineWrapper.EXEE(_LoadFromCacheOrDownload, url, version, callback,timeout);
}
private IEnumerator _LoadFromCacheOrDownload(object[] p) {
while (pool.Count >= max) {
if (disposed) {
yield break;
}
yield return new WaitForEndOfFrame();
}
string url = (string)p[0];
string verson = p[1].ToString();
float timeout = (float)p[3];
string sb = CachePathInterface(url);
bool cached = File.Exists(sb) && CompareVersion(sb,verson);
WWW t_instwww = null;
if (cached) {
//Debug.Log("load from local");
t_instwww = new WWW("file:///" + sb);
} else {
//Debug.Log("load from www");
t_instwww = new WWW(url);
}
yield return new WaitForEndOfFrame();
instwww = t_instwww;
pool.Add(t_instwww);
float curTime = 0;
while (t_instwww != null && !t_instwww.isDone) {
if (disposed) {//break
pool.Remove(t_instwww);
//t_instwww.Dispose();
t_instwww = null;
instwww = null;
yield break;
}
else if (curTime > timeout && t_instwww.progress <= 0) {//timeout
pool.Remove(t_instwww);
t_instwww = null;
instwww = null;
break;
}
yield return new WaitForEndOfFrame();
curTime += Time.deltaTime;
}
if (t_instwww == null) {
var result = new WWWResult(null);
result.SetOverrideError("timeout");
if (p[2] != null)
((Action<WWWResult>)p[2])(result);
yield break;
}
pool.Remove(t_instwww);
if (!cached && t_instwww.error == null) {
//Debug.Log("??????棺" + sb);
if (!Directory.Exists(Path.GetDirectoryName(sb)))
Directory.CreateDirectory(Path.GetDirectoryName(sb));
File.WriteAllBytes(sb, t_instwww.bytes);
string metaPath = CreateMetaFile(sb, verson);
#if UNITY_IPHONE
if (Application.platform == RuntimePlatform.IPhonePlayer && !IosBackUp) {
UnityEngine.iOS.Device.SetNoBackupFlag(sb);
}
UnityEngine.iOS.Device.SetNoBackupFlag(metaPath);
#endif
}
if (Log) {
LogWWW(t_instwww);
}
if (p[2] != null)
((Action<WWWResult>)p[2])(new WWWResult(t_instwww));
}
/// <summary>
/// www
/// </summary>
public void www(string url, WWWForm form, Action<WWWResult> callback,float timeout = DefaultTimeout) {
disposed = false;
CoroutineWrapper.EXEE(_www, url, form, callback,timeout);
}
public void www(string url, byte[] data, Dictionary<string, string> header, Action<WWWResult> callback,float timeout = DefaultTimeout) {
disposed = false;
CoroutineWrapper.EXEE(_wwwWithHeader, url, data,header, callback,timeout);
}
private IEnumerator _www(object[] p) {
while (pool.Count >= max) {
yield return new WaitForEndOfFrame();
if (disposed) {
yield break;
}
}
WWW t_instwww = null;
if (p[1] == null)
t_instwww = new WWW((string)p[0]);
else
t_instwww = new WWW((string)p[0], p[1] as WWWForm);
float timeout = (float)p[3];
yield return new WaitForEndOfFrame();
instwww = t_instwww;
pool.Add(t_instwww);
float curTime = 0;
while (t_instwww != null && !t_instwww.isDone) {
if (disposed) {
pool.Remove(t_instwww);
//t_instwww.Dispose();
t_instwww = null;
instwww = null;
yield break;
}
else if (curTime > timeout && t_instwww.bytesDownloaded == 0) {
pool.Remove(t_instwww);
t_instwww = null;
instwww = null;
break;
}
yield return new WaitForEndOfFrame();
curTime += Time.deltaTime;
}
if (t_instwww == null) {
var result = new WWWResult(null);
result.SetOverrideError("timeout");
if (p[2] != null)
((Action<WWWResult>)p[2])(result);
yield break;
}
pool.Remove(t_instwww);
if (Log) {
LogWWW(t_instwww);
}
if (p[2] != null)
((Action<WWWResult>)p[2])(new WWWResult(t_instwww));
}
private IEnumerator _wwwWithHeader(object[] p) {
while (pool.Count >= max) {
yield return new WaitForEndOfFrame();
if (disposed) {
yield break;
}
}
WWW t_instwww = new WWW((string)p[0], p[1] as byte[], p[2] as Dictionary<string, string>);
float timeout = (float)p[4];
yield return new WaitForEndOfFrame();
instwww = t_instwww;
pool.Add(t_instwww);
float curTime = 0;
while (t_instwww != null && !t_instwww.isDone) {
if (disposed) {
pool.Remove(t_instwww);
//t_instwww.Dispose();
t_instwww = null;
instwww = null;
yield break;
}
else if (curTime > timeout && t_instwww.progress <= 0) {
pool.Remove(t_instwww);
t_instwww = null;
instwww = null;
break;
}
yield return new WaitForEndOfFrame();
curTime += Time.deltaTime;
}
if (t_instwww == null) {
if (p[3] != null) {
var result = new WWWResult(null);
result.SetOverrideError("timeout");
((Action<WWWResult>)p[3])(result);
}
yield break;
}
pool.Remove(t_instwww);
if (Log) {
LogWWW(t_instwww);
}
if (p[3] != null)
((Action<WWWResult>)p[3])(new WWWResult(t_instwww));
}
/// <summary>
/// cache a file
/// </summary>
public void CacheIt(string url, string version, Action<bool, string> callback,float timeout = DefaultTimeout) {
disposed = false;
CoroutineWrapper.EXEE(_CacheIt, url, version, callback,timeout);
}
private IEnumerator _CacheIt(object[] p) {
while (pool.Count >= max) {
yield return new WaitForEndOfFrame();
if (disposed) {
yield break;
}
}
string url = (string)p[0];
string verson = p[1].ToString();
float timeout = (float)p[3];
if (url.EndsWith("/")) {
if (p[2] != null)
((Action<bool, string>)p[2])(false, "url is dir");
yield break;
}
string sb = CachePathInterface(url);
bool success = true;
bool cached = File.Exists(sb) && CompareVersion(sb, verson);
if (cached) {
;
} else {
//Debug.Log("load from www");
WWW t_instwww = new WWW(url);
yield return new WaitForEndOfFrame();
instwww = t_instwww;
pool.Add(t_instwww);
float curTime = 0;
while (t_instwww != null && !t_instwww.isDone) {
if (disposed) {
pool.Remove(t_instwww);
//t_instwww.Dispose();
t_instwww = null;
instwww = null;
yield break;
}
else if (curTime > timeout && t_instwww.progress <= 0) {
pool.Remove(t_instwww);
t_instwww = null;
instwww = null;
success = false;
sb = "timeout";
break;
}
yield return new WaitForEndOfFrame();
curTime += Time.deltaTime;
}
if (t_instwww == null) {
if (p[2] != null)
((Action<bool, string>)p[2])(success, sb);
yield break;
}
pool.Remove(t_instwww);
if (!cached && t_instwww.error == null) {
if (!Directory.Exists(Path.GetDirectoryName(sb)))
Directory.CreateDirectory(Path.GetDirectoryName(sb));
File.WriteAllBytes(sb, t_instwww.bytes);
string metaPath = CreateMetaFile(sb, verson);
#if UNITY_IPHONE
if (Application.platform == RuntimePlatform.IPhonePlayer && !IosBackUp) {
UnityEngine.iOS.Device.SetNoBackupFlag(sb);
}
UnityEngine.iOS.Device.SetNoBackupFlag(metaPath);
#endif
}
else if (t_instwww.error != null) {
success = false;
sb = t_instwww.error;
}
if (Log) {
LogWWW(t_instwww);
}
}
if (p[2] != null)
((Action<bool,string>)p[2])(success,sb);
}
public void Dispose() {
disposed = true;
//if (instwww!=null) {
// //instwww.Dispose();
// instwww = null;
//}
}
public bool CheckCache(string weburl) {
string fpath = this.CachePathInterface(weburl);
return File.Exists(fpath);
}
/// <summary>
/// get cache path with a certain url
/// you should use GetCacheURIEX preferred
/// </summary>
public static string GetCacheURI(string weburl) {
StringBuilder sb = new StringBuilder(weburl);
int i = sb.ToString().IndexOf('?');
if (i >= 0)
sb.Remove(i, sb.Length - i);
sb.Replace("http://", "").Replace(":","/").Replace("/", "+").Replace("\\", "+");
string ffilename = Path.GetFileNameWithoutExtension(sb.ToString());
//
string mapedName = MapStr(ffilename,0,ffilename.LastIndexOf('+'),'+','.');
int t_index = ffilename.LastIndexOf('+');
t_index = t_index>=0? t_index:0;
string fn = ffilename.Substring(t_index);
mapedName += Chinese2UniCode(fn, 0, fn.Length);
sb.Replace(ffilename, mapedName);
//
sb.Insert(0, Application.persistentDataPath + "/wwwcache/");
return sb.ToString();
}
/// <summary>
/// get cache path with a certain url
/// </summary>
public static string GetCacheURIEX(string weburl) {
string sb = GetCacheURI(weburl);
int i = sb.LastIndexOf('+');
string path, name;
path = sb.Substring(0, i);
name = sb.Substring(i + 1);
sb = path + "/" + name;
return sb;
}
private static string MapStr(string o,int start,int end,params char[] ignore) {
StringBuilder sb = new StringBuilder(o.Substring(0,start));
for (int i = start; i < end; i++) {
if(ignore!=null){
char t = Array.Find<char>(ignore, n => { return n.Equals(o[i]); });
if (t == '\0') {
string c;
if (o[i] >= 0 && o[i] <= 127)
c = Convert.ToChar((Convert.ToInt32(o[i]) + i) % 26 + 97).ToString();
else {
c = o[i].ToString();
Debug.Log(c);
}
sb.Append(c);
} else
sb.Append(t);
} else {
string c;
if (o[i] >= 0 && o[i] <= 127)
c = Convert.ToChar((Convert.ToInt32(o[i]) + i) % 26 + 97).ToString();
else
c = o[i].ToString();
sb.Append(c);
}
}
return sb.ToString();
}
/// <summary>
/// this function has some bug,so class doesn`t support chinese in fact
/// </summary>
/// <returns></returns>
private static string Chinese2UniCode(string o,int start,int end,params char[] ignore) {
StringBuilder sb = new StringBuilder(o.Substring(0, start));
for (int i = start; i < end; i++) {
if (ignore != null) {
char t = Array.Find<char>(ignore, n => { return n.Equals(o[i]); });
if (t == '\0') {
string c;
if (o[i] >= 0 && o[i] <= 127)
c = o[i].ToString();
else
c = string.Format("U{0:x4}", Convert.ToInt32(o[i]));
sb.Append(c);
} else
sb.Append(t);
} else {
string c;
if (o[i] >= 0 && o[i] <= 127)
c = o[i].ToString();
else
c = string.Format("U{0:x4}", Convert.ToInt32(o[i]));
sb.Append(c);
}
}
return sb.ToString();
}
/// <summary>
/// create a meta file to record file info
/// </summary>
private static string CreateMetaFile(string FilePath,string version) {
string metaFilePath = FilePath + ".yxMeta";
var table = HashtableEX.Construct(
"version",version
);
string serialize = table.toJson();
serialize = XORStr(serialize, Path.GetFileName(FilePath));
FileStream fs = new FileStream(metaFilePath, FileMode.Create);
StreamWriter sw = new StreamWriter(fs);
sw.Write(serialize);
sw.Flush();
sw.Close();
return metaFilePath;
}
/// <summary>
///
/// </summary>
private static Hashtable LoadMetaFile(string FilePath) {
string metaFilePath = FilePath + ".yxMeta";
if (!File.Exists(metaFilePath)) {
return new Hashtable();
}
StreamReader sr = new StreamReader(metaFilePath);
string serialize = sr.ReadToEnd();
sr.Close();
serialize = XORStr(serialize, Path.GetFileName(FilePath));
try {
return serialize.hashtableFromJson();
}
catch {
return new Hashtable();
}
}
/// <summary>
///
/// </summary>
private static string GetVersionFromMeta(string FilePath) {
var table = LoadMetaFile(FilePath);
return table.SGet<string>("version");
}
/// <summary>
///
/// </summary>
private static bool CompareVersion(string FilePath,string remoteVersion) {
return GetVersionFromMeta(FilePath) == remoteVersion;
}
/// <summary>
/// check file status
/// </summary>
/// <returns>0:normal 1:local ahead -1:remote ahead -2:local not exsit -3:meta error</returns>
public static int CheckFile(string URL, string RemoteVersion,bool EX = false) {
string FilePath;
if (!EX)
FilePath = GetCacheURI(URL);
else
FilePath = GetCacheURIEX(URL);
if (!File.Exists(FilePath)) {
return -2;
}
string local = GetVersionFromMeta(FilePath);
if (local == RemoteVersion) {
return 0;
}
else {
try {
int i_local = int.Parse(local);
int i_remote = int.Parse(RemoteVersion);
if (i_local<=i_remote) {
return -1;
}
else {
return 1;
}
}
catch {
return -3;
}
}
}
/// <summary>
/// xor
/// </summary>
private static string XORStr(string p, string key) {
byte[] ks = Encoding.UTF8.GetBytes(key);
int kl = ks.Length;
byte[] bs = Encoding.UTF8.GetBytes(p);
for (int i = 0; i < bs.Length; i++) {
bs[i] = (byte)(bs[i] ^ ks[i % kl]);
}
return Encoding.UTF8.GetString(bs);
}
private static void LogWWW(WWW www) {
Debug.Log(www.url);
if (www.error!=null)
{
Debug.Log(www.error);
}
var by = www.bytes;
if (by.Length <= 10240) {//10k
Debug.Log(www.text);
}
return;
//follow code has some bug in ios,and it just want to write a log,so I haven`t cost time to fix it
if (www==null || www.error == null){
return;
}
StringBuilder sb = new StringBuilder();
sb.AppendLine();
sb.AppendLine(string.Format("-------{0}-------", System.DateTime.Now.ToString("yyyy-MM-dd hh:mm:ss")));
sb.AppendLine("url:"+www.url);
var rspHead = www.responseHeaders;
sb.AppendLine("responseHeader:");
foreach (var i in rspHead) {
sb.AppendLine(" "+i.Key+""+i.Value);
}
sb.AppendLine("error:"+www.error);
var b = www.bytes;
if (b.Length<=10240) {//????????10k
sb.AppendLine("text:"+www.text);
}
else {
byte[] trim = new byte[40];
Array.Copy(b,trim,trim.Length);
sb.AppendLine(string.Format("text: ???????{0},???????????????,?40????:{1}",b.Length,Encoding.UTF8.GetString(trim)));
}
sb.AppendLine();
File.AppendAllText(Application.persistentDataPath+"/WWWError.log", sb.ToString());
}
public static void ClearCache(string URL,bool EX) {
string FilePath;
if (!EX)
FilePath = GetCacheURI(URL);
else
FilePath = GetCacheURIEX(URL);
string metaPath = FilePath + ".yxMeta";
if (File.Exists(FilePath)) {
File.Delete(FilePath);
}
if (File.Exists(metaPath)) {
File.Delete(metaPath);
}
}
public static void ClearCache(string FilePath) {
string metaPath = FilePath + ".yxMeta";
if (File.Exists(FilePath)) {
File.Delete(FilePath);
}
if (File.Exists(metaPath)) {
File.Delete(metaPath);
}
}
/// <summary>
/// a wrapper of a WWW inst which has done
/// </summary>
public class WWWResult {
WWW wwwInst;
string overrideError = null;
public WWWResult(WWW www) {
wwwInst = www;
}
public void SetOverrideError(string error) {
overrideError = error;
}
public AssetBundle assetBundle {
get {
if (wwwInst!=null) {
return wwwInst.assetBundle;
}
else {
return null;
}
}
}
public AudioClip audioClip {
get {
if (wwwInst != null) {
return wwwInst.audioClip;
}
else {
return null;
}
}
}
public byte[] bytes {
get {
if (wwwInst != null) {
return wwwInst.bytes;
}
else {
return null;
}
}
}
public string error {
get {
if (wwwInst != null) {
if (!overrideError.IsNullOrEmpty()) {
return overrideError;
}
return wwwInst.error;
}
else {
return overrideError;
}
}
}
public Dictionary<string, string> responseHeaders {
get {
if (wwwInst != null) {
return wwwInst.responseHeaders;
}
else {
return null;
}
}
}
public int size {
get {
if (wwwInst != null) {
return wwwInst.size;
}
else {
return -1;
}
}
}
public string text {
get {
if (wwwInst != null) {
if (!overrideError.IsNullOrEmpty()) {
return overrideError;
}
return wwwInst.text;
}
else {
return overrideError;
}
}
}
public Texture2D texture {
get {
if (wwwInst != null) {
return wwwInst.texture;
}
else {
return null;
}
}
}
public Texture2D textureNonReadable {
get {
if (wwwInst != null) {
return wwwInst.textureNonReadable;
}
else {
return null;
}
}
}
public string url {
get {
if (wwwInst != null) {
return wwwInst.url;
}
else {
return null;
}
}
}
public AudioClip GetAudioClip(bool threeD) {
if (wwwInst != null) {
return wwwInst.GetAudioClip(threeD);
}
else {
return null;
}
}
public AudioClip GetAudioClip(bool threeD, bool stream) {
if (wwwInst != null) {
return wwwInst.GetAudioClip(threeD,stream);
}
else {
return null;
}
}
public AudioClip GetAudioClip(bool threeD, bool stream, AudioType audioType) {
if (wwwInst != null) {
return wwwInst.GetAudioClip(threeD, stream, audioType);
}
else {
return null;
}
}
public AudioClip GetAudioClipCompressed() {
if (wwwInst != null) {
return wwwInst.GetAudioClipCompressed();
}
else {
return null;
}
}
public AudioClip GetAudioClipCompressed(bool threeD) {
if (wwwInst != null) {
return wwwInst.GetAudioClipCompressed(threeD);
}
else {
return null;
}
}
public AudioClip GetAudioClipCompressed(bool threeD, AudioType audioType) {
if (wwwInst != null) {
return wwwInst.GetAudioClipCompressed(threeD, audioType);
}
else {
return null;
}
}
public void LoadImageIntoTexture(Texture2D tex) {
if (wwwInst != null) {
wwwInst.LoadImageIntoTexture(tex);
}
else {
return;
}
}
}
}
依赖另一个脚本来转移协程:
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
using System;
using System.Reflection;
/// <summary>
/// Э?????д???
/// </summary>
public class CoroutineWrapper : MonoBehaviour {
#region ????
static CoroutineWrapper inst;
private readonly static object mutex = new object();
public static CoroutineWrapper Inst {
get {
if (inst == null) {
var obj = new GameObject("CoroutineWrapper");
DontDestroyOnLoad(obj);
inst = obj.AddComponent<CoroutineWrapper>();
inst.Init();
}
return inst;
}
}
void Init() {
inst = this;
}
#endregion
/// <summary>
/// ????????????
/// </summary>
/// <param name="frames"></param>
/// <param name="ev"></param>
/// <returns></returns>
public IEnumerator ExeDelay(int frames,Action ev) {
for (int i = 0; i < frames;i++ ) {
yield return new WaitForEndOfFrame();
}
ev();
}
/// <summary>
/// ????????????
/// </summary>
/// <param name="sec"></param>
/// <param name="ev"></param>
/// <returns></returns>
public IEnumerator ExeDelayS(float sec, Action ev) {
yield return new WaitForSeconds(sec);
ev();;
}
/// <summary>
/// ????????????
/// </summary>
/// <param name="sec"></param>
/// <param name="ev"></param>
public static void EXES(float sec, Action ev) {
inst.StartCoroutine(inst.ExeDelayS(sec,ev));
}
/// <summary>
/// ????????????
/// </summary>
/// <param name="frames"></param>
/// <param name="ev"></param>
public static void EXEF(int frames, Action ev) {
inst.StartCoroutine(inst.ExeDelay(frames, ev));
}
/// <summary>
/// ?????????Э??
/// </summary>
/// <param name="ien">Э?????</param>
public static void EXEE(Func<IEnumerator> ien) {
inst.StartCoroutine(ien());
}
/// <summary>
///
/// </summary>
/// <param name="ien">Э?????</param>
/// <param name="p">??????</param>
public static void EXEE(Func<object[],IEnumerator> ien,params object[] p) {
inst.StartCoroutine(ien(p));
}
/// <summary>
/// call fun with any type args,function must be public
/// </summary>
/// <param name="t"></param>
/// <param name="FuncName"></param>
/// <param name="inst"></param>
/// <param name="p"></param>
public static void ReflectCallCoroutine(Type t, string FuncName,object inst, params object[] p) {
Type[] pt = null;
if (p!=null) {
pt = new Type[p.Length];
for (int i = 0; i < pt.Length; i++) {
pt[i] = p[i].GetType();
}
}
MethodInfo method;
if (pt!=null) {
method = t.GetMethod(FuncName,pt);
} else {
method = t.GetMethod(FuncName);
}
if (method == null) {
Debug.LogFormat("can`t find method {0} with give params",FuncName);
return;
}
var u3dCoroutineCaller = inst.GetType().GetMethod("StartCoroutine", new Type[] { typeof(IEnumerator) });
if (u3dCoroutineCaller == null) {
Debug.Log("can`t find u3d coroutine caller");
return;
}
u3dCoroutineCaller.Invoke(inst, new object[]{method.Invoke(inst, p)});
}
/// <summary>
/// this action will be called per frame
/// </summary>
public event Action OnPerFrame;
void Update() {
if (OnPerFrame != null)
OnPerFrame();
}
}
好了,举个栗子:
先初始化一下工具:
void Awake() {
var t = CoroutineWrapper.Inst;
}
再开始你的http访问,例如下面这个,是提交评论
public WWWEX CommentProjImpl(string uid, string comment, Action<WWWEX.WWWResult> callback) {
string url = "xxx";
WWWEX wex = new WWWEX();
WWWForm form = new WWWForm();
form.AddField("uniqid",uid);
form.AddField("content", comment);
wex.www(url, form.data, MakeHeader(), callback);//MakeHeader是一个生成一些请求头字段的方法,生成的一个Dictionary<string, string>,可以不管
return wex;
}
然后,这个函数就返回了一个WWWEX的实例,要获取进度之类的就是可以通过这个实例的成员去访问。