unity调整分辨率在android下闪屏

unity版本 4.3.1

为了减少发热量,我们游戏对分辨率进行的缩减,但在缩小的那一祯可以明显看到闪烁。

参考了一些文章,了解了下闪烁的原因:

1、在unity启动时默认是使用上层layout的width/height,游戏基本上都是全屏,所以上来就是全屏显示。

2、在unity中使用Screen.SetResolution,可以调整分辨率,反编译unity android层代码可以看到是调用了surface.setFixedSize,就是这里的原因,在调整期间会闪屏。

既然默认使用的是上层layout的width/height,我们直接修改上层的layout不就OK了?噢,不行,layout是指实际显示的分辨大小,我们的目标是指渲染的界面要小,然后通过缩放进行全屏显示。

所以只能使用surface.setFixedSize方法,那这样只需要在调整分辨前在main acitivity.onCreate方法中设置上就OK了。

查看了unityplayer.surcefaceview,发现是私有的。私有的也难不倒我们,直接使用反射取出私有变量。

需要同时在onCreate和onConfigurationChanged中修改。

修改完后发现触点和unity中的对应不上了,检查一下问题:

1、发现unity中触点是设置的缩小分辨率后对应的,但如果将竖屏和横屏切换一下,即可正确对应上,应该是在oncreate调整分辨率后还需要调整触点。

上述错误。经过很辛苦的查找,终于查找到原来是unity中提供的classes.jar有bug。反编译后,发现:

	protected void triggerResizeCall() {
		Log.i("hack unity", "triggerResizeCall v:" + v + " w:" + w + " v:" + v
				+ " w:" + w);
		this.nativeResize(this.v, this.w, this.v, this.w);
	}

这里第三个参数和第四个参数应该使用屏幕的大小,而不应该是使用分辨率的大小。坑啊。

找到原因就好处理了,hack掉unityplayer,重新提供一个triggerResizeCall,完美解决闪屏问题。

实际上调整分辨率闪屏还有很多更便捷的解决方法,比如unity的splash image设置为全黑,然后在第一个场景加载时,调用分辨率玩家就看不到闪屏了,反正都是黑的,在第一个场景调整完分辨后,再伪造显示一个splash image,哈哈。

 

参考文章:

1) http://stackoverflow.com/questions/7185644/android-opengl-crazy-aspect-ratio-after-sleep

2)http://android-developers.blogspot.it/2013/09/using-hardware-scaler-for-performance.html

一篇优化android 性能的文章

http://android-developers.blogspot.it/2013/09/using-hardware-scaler-for-performance.html

adb devices中看不到设备

1、adb devices中看不到设备。

以前也遇到过这种问题,解决方案是重装安装android use driver,还需要修改里面的android_winusb.inf文件,很是麻烦。

这里找到一个简单的办法:

修改adb_usb.ini

在任务管理器中结束adb命令,再对下面的修改可能产生影响

在模拟器存放的目录<例如:C:\Documents and Settings\Administrator\.android>下找到或新建一个adb_usb.ini文件,同时增加或写入上面设备的0xVID(VID就是上面的VID_后面跟的数字,例如:0x1782),弄好了再使用重新启动android sdk中的adb就能连接上了

2、adb devices中看到设备是offline状态

-.-!!升级一下adb版本即可。

3、小米手机获取root权限。

安装miui开发版本,adb root进入后使用su获得root权限。

unity 在游戏启动时按home键,再切回游戏黑屏

我们的游戏在启动时,如果切回后台,会几率性出现黑屏及资源加载错误。

我抓了一下android层的异常:

01-20 22:21:48.362: W/IInputConnectionWrapper(6525): showStatusIcon on inactive InputConnection
01-20 22:21:48.440: E/BufferQueue(190): [SurfaceView] connect: BufferQueue has been abandoned!
01-20 22:21:48.440: E/libEGL(6525): EGLNativeWindowType 0x76b53920 already connected to another API
01-20 22:21:48.440: E/libEGL(6525): eglCreateWindowSurface:525 error 3003 (EGL_BAD_ALLOC)
01-20 22:21:48.440: E/Unity(6525): [EGL] Failed to create surface
01-20 22:21:48.440: E/Unity(6525):  
01-20 22:21:48.440: E/Unity(6525): (Filename:  Line: 132)
01-20 22:21:48.440: E/Unity(6525): [EGL] Error:: EGL_BAD_ALLOC: EGL failed to allocate resources for the requested operation.
01-20 22:21:48.440: E/Unity(6525):  
01-20 22:21:48.440: E/Unity(6525): (Filename: ./PlatformDependent/AndroidPlayer/ContextGLES.cpp Line: 132)
01-20 22:21:48.441: E/libEGL(6525): call to OpenGL ES API with no current context (logged once per thread)

在网上搜索了很多文章,没有合适的解决方案。

在测试我们公司另一个项目的游戏时,发现他们没有这个问题,比较我们两个游戏,发现在启动速度上差别很大。

感觉这个问题的出现和启动慢有关系。查看启动部分代码,发现启动的类的field都是直接在类加载时进行的初始化。

感觉这里会阻塞线程,导致的unity初始时间非常长。

写了一个测试用例,分别在Awake(),Start()及类初始化时添加Thread.sleep(15000)的强制延迟。

发现Awake,Start不会阻塞unity的加载,会立即加载出splash image。在类初始化时,消耗大量时间,打出apk包,在手机上运行,也没有阻塞splash image显示。这下可真是奇怪了。

我先把我们游戏中在类加载时初始化的内容全转移至Awake中,再打个包测试下。

经过我们这边和珠海测试团队测试,进游戏时丢贴图的问题没有再次复现。是个好消息。

只剩下最后一个黑屏的问题,这个黑屏并不是BUG,只是客户端代码流程没有处理好。

——————————————————————————————

2015.01.22 更新:

本来以为黑屏是游戏中故意设置的黑屏,把游戏中的黑屏改为绿屏,结果发现黑屏问题依旧。

试着抓了一下异常:

01-22 13:36:59.538: E/XSJData(5438): serverId is null
01-22 13:36:59.675: E/XSJData(5438): serverId is null
01-22 13:38:07.740: E/linker(9195): load_library(linker.cpp:760): library "libmaliinstr.so" not found
01-22 13:38:07.744: E/(9195): appName=com.xsj.moon, acAppName=com.android.cts.openglperf
01-22 13:38:07.744: E/(9195): 0
01-22 13:38:07.744: E/(9195): appName=com.xsj.moon, acAppName=com.android.browser
01-22 13:38:07.744: E/(9195): 0
01-22 13:38:13.985: E/linker(9195): load_library(linker.cpp:760): library "/data/app/com.xsj.moon-1.apk/assets/bin/Data/Managed/mscorlib.dll.so" not found
01-22 13:38:14.501: E/(9195): appName=com.xsj.moon, acAppName=com.android.cts.openglperf
01-22 13:38:14.501: E/(9195): 0
01-22 13:38:14.501: E/(9195): appName=com.xsj.moon, acAppName=com.android.browser
01-22 13:38:14.501: E/(9195): 0
01-22 13:38:14.714: E/linker(9195): load_library(linker.cpp:760): library "/data/app/com.xsj.moon-1.apk/assets/bin/Data/Managed/UnityEngine.dll.so" not found
01-22 13:38:14.800: E/linker(9195): load_library(linker.cpp:760): library "/data/app/com.xsj.moon-1.apk/assets/bin/Data/Managed/Assembly-CSharp-firstpass.dll.so" not found
01-22 13:38:14.902: E/linker(9195): load_library(linker.cpp:760): library "/data/app/com.xsj.moon-1.apk/assets/bin/Data/Managed/Assembly-CSharp.dll.so" not found
01-22 13:38:14.906: E/linker(9195): load_library(linker.cpp:760): library "/data/app/com.xsj.moon-1.apk/assets/bin/Data/Managed/Assembly-UnityScript.dll.so" not found
01-22 13:38:14.909: E/linker(9195): load_library(linker.cpp:760): library "/data/app/com.xsj.moon-1.apk/assets/bin/Data/Managed/HOTween.dll.so" not found
01-22 13:38:14.913: E/linker(9195): load_library(linker.cpp:760): library "/data/app/com.xsj.moon-1.apk/assets/bin/Data/Managed/JsonFx.Json.dll.so" not found
01-22 13:38:14.916: E/linker(9195): load_library(linker.cpp:760): library "/data/app/com.xsj.moon-1.apk/assets/bin/Data/Managed/P31RestKit.dll.so" not found
01-22 13:38:14.918: E/linker(9195): load_library(linker.cpp:760): library "/data/app/com.xsj.moon-1.apk/assets/bin/Data/Managed/Vectrosity.dll.so" not found
01-22 13:38:14.919: E/linker(9195): load_library(linker.cpp:760): library "/data/app/com.xsj.moon-1.apk/assets/bin/Data/Managed/UMGameAnalyticsLibForAndroid.dll.so" not found
01-22 13:38:14.920: E/linker(9195): load_library(linker.cpp:760): library "/data/app/com.xsj.moon-1.apk/assets/bin/Data/Managed/UMGameAnalyticsLibForiOS.dll.so" not found
01-22 13:38:17.802: E/linker(9195): load_library(linker.cpp:760): library "/data/app/com.xsj.moon-1.apk/assets/bin/Data/Managed/System.Core.dll.so" not found
01-22 13:38:17.872: E/linker(9195): load_library(linker.cpp:760): library "/data/app/com.xsj.moon-1.apk/assets/bin/Data/Managed/System.dll.so" not found
01-22 13:38:18.303: E/linker(9195): load_library(linker.cpp:760): library "/data/app/com.xsj.moon-1.apk/assets/bin/Data/Managed/System.Xml.dll.so" not found
01-22 13:38:19.756: E/lsy(9195): UnityAwake
01-22 13:38:20.957: E/linker(9195): load_library(linker.cpp:760): library "/data/app/com.xsj.moon-1.apk/assets/bin/Data/Managed/System.dll.so" not found
01-22 13:38:24.040: E/linker(9195): load_library(linker.cpp:760): library "fmod" not found
01-22 13:38:24.048: E/linker(9195): load_library(linker.cpp:760): library "fmodstudio" not found
01-22 13:38:24.062: E/linker(9195): load_library(linker.cpp:760): library "fmodstudio" not found
01-22 13:38:26.182: E/linker(9195): load_library(linker.cpp:760): library "fmodstudio" not found
01-22 13:38:26.252: E/linker(9195): load_library(linker.cpp:760): library "fmodstudio" not found
01-22 13:38:26.255: E/linker(9195): load_library(linker.cpp:760): library "fmod" not found
01-22 13:38:26.738: E/XSJData(9195): serverId is null
01-22 13:38:26.868: E/XSJData(9195): serverId is null
01-22 13:38:28.460: E/WhetstoneSDK-JNI(9195): on Load native Whetstone

看起来还是程序中有BUG,并不是简单的逻辑异常。

unity中包中只存在对应的dll不存在对应的dll.so,这里应该是unity内部的加载机制被破坏掉了。

在goolge上搜索下unity加载dll的流程。

抓了一下正常流程的日志,在正常流程中也有上面的输出。看起来不是上面的原因。

游戏中重构了网络模块

我们游戏中断线和超时这一块出问题非常多。

这一次我专门抽出了一周时间,了解了下c#下的socket相关函数重写了网络模块。

首要目标是避免另开线程,使用nio。然后是内部处理超时,断线和其它IO异常,重连3次失败后,才提交给游戏自己处理。重连时,自动登陆并续发消息。

这里本来还需要添加一个补发消息的机制(断线后网络传输中导致丢失的消息),但是测试只有续发消息,已经解决掉了原来99%问题。所以补发消息先放一下,先再重构其它地方。

重构后测试的结果非常好,以前各种网络问题都没有复现,断线对玩家来说是不可见的,只要长时间断线(三次重连失败)才会给玩家一个提示框,让玩家手动点击去重连。

//----------------------------------------------
//           NET        : NetManager
//           AUTHOR: zhu kun qian
//----------------------------------------------

using System.IO;

public class NetMsg
{
    // 有必要用id吗?
    // 
    public cmd.CmdType cmdType;
    public uint loop;// 唯一的循环id
    public byte[] data;// protobuf二进制数据
    public byte[] totalData;//完整的消息二进制数据
    public MemoryStream netdata;

    // 这里能避免一次array copy不?
    // TODO:暂不考虑这避免arry copy的消耗,以后有时间时,可以考虑处理下。
}

 

//----------------------------------------------
//           NET        : NetManager
//           AUTHOR: zhu kun qian
//----------------------------------------------

using System.Net;
using System.Net.Sockets;
using System.Runtime.Remoting.Messaging;
using UnityEngine;
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;

/*
 * 没有使用任何阻塞函数。
 * 
 * connect是block函数,所以改为使用ConnectAsync, beginConnect也是异步,也可以使用
 * 
 * 连接成功后,socket修改为non-blocking
*/
public class NetManagerSocket
{

    // 如果断开连接,直接new socket

    public string ip;
    public int port;

    private Socket socket;
    private byte[] readBuf = new byte[64 * 1024];// 最高缓存接收64k数据。

    private int readBufIndex = 0;//已经从readBuf中读取的字节数
    private int available = 0;// readbuf已经从socket中接收的字节数

    public SocketError socketError = SocketError.Success;// 这里应该是传递到上层,还是在这里处理?

    // ---------------------------------------
    // -- decoder相关
    private int length = 0;// protobuf消息体中的长度
    private int needRead = 0;// protobuf消息体中剩余需要读取的长度
    // ---------------------------------------

    public void connect()
    {
        socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
        socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.KeepAlive, true);
        socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.SendTimeout, 5000);// 设置5秒发送超时
        socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, false);
        socket.NoDelay = true;
        // socket.Blocking = false;
        socket.Blocking = true;//设置为非阻塞

        socketError = SocketError.Success;

        Debug.Log("dontLinger:" + socket.GetSocketOption(SocketOptionLevel.Socket, SocketOptionName.DontLinger));
        try
        {
            // 这里需要修改为异步connect
            // http://msdn.microsoft.com/en-us/library/d7ew360f%28v=vs.110%29.aspx
            // Connect method will block, unless you specifically set the Blocking property to false prior to calling Connect.
            // If you are using a connection-oriented protocol like TCP and you do disable blocking, Connect will throw a SocketException because it needs time to make the connection
            // 结论:很奇怪,并没有按文档上描述的执行,设置为blocking,并没有抛出异常。

            // socket.Connect(ip, port);

            // The asynchronous BeginConnect operation must be completed by calling the EndConnect method.
            // IAsyncResult result= socket.BeginConnect(ip, port, new AsyncCallback(connectCallback), socket);

            // Debug.Log("asyncResult is completed:"+result.IsCompleted);

            // 异步处理必须设置为blocking,why?
            //*
            SocketAsyncEventArgs eventArgs = new SocketAsyncEventArgs();
            eventArgs.RemoteEndPoint = new IPEndPoint(IPAddress.Parse(ip), port);
            eventArgs.Completed += new EventHandler<SocketAsyncEventArgs>(connectCallback2);
            eventArgs.UserToken = socket;

            socket.ConnectAsync(eventArgs);

            // */
            // 这里需要添加超时处理吗?
        }
        catch (Exception ex)
        {
            Debug.LogError("socket connection exception:" + ex);
            socketError = SocketError.ConnectionRefused;
        }
    }

    private void connectCallback2(object src, SocketAsyncEventArgs result)
    {
        Debug.Log("connectCallback2, isCompleted:" + result + " " + result.SocketError);
        if (result.SocketError != SocketError.Success)
        {
            socketError = result.SocketError;
        }
        else
        {
            Socket socket = (Socket)src;
            socket.Blocking = false;
        }
    }

    void connectCallback(IAsyncResult result)
    {
        Debug.Log("connectCallback22, isCompleted:" + result.IsCompleted);
        // connected = true;
        try
        {
            Socket socket = (Socket)result.AsyncState;
            socket.EndConnect(result);

            Debug.Log("socket is connected:" + socket.Connected);
            socket.Send(new byte[] { });
        }
        catch (Exception e)
        {
            Debug.LogError("error:" + e);
            socketError = SocketError.ConnectionRefused;
        }
    }

    public void disconnect()
    {
        if (socket != null)
        {
            socket.Close();
            socket = null;
        }
    }

    //

    public NetMsg read()
    {
        // 这里同步读也可以,但只读数据接收到足够一个消息的情况
        // 使用availed预判进行同步读,不会阻塞主线程
        // 解析消息放在这里。
        if (socket != null && socket.Available > 0)
        {
            if (available == 0)
            {
                if (socket.Available < 12)
                {
                    // 一个数据包,最小为12字节
                    return null;
                }
                // 开始从socket中读入一条数据
                // 如果足够,就直接解析为消息返回
                // 如果不够,就将数据放在cache中

                socketRead(2);
                if (socketError != SocketError.Success)
                {
                    return null;
                }

                length = readUshort();

                if (length == 0)
                {
                    socketRead(4);
                    if (socketError != SocketError.Success)
                    {
                        return null;
                    }
                    length = readInt();
                }
                int socketAvailable = socket.Available;
                needRead = length;
                if (socketAvailable < needRead)
                {
                    // 不足时,socket有多少缓存数据就读多少
                    socketRead(socketAvailable);
                    if (socketError != SocketError.Success)
                    {
                        return null;
                    }

                    needRead = needRead - socketAvailable;
                    return null;
                }
                else
                {
                    // 数据足够,解析数据
                    socketRead(needRead);
                    if (socketError != SocketError.Success)
                    {
                        return null;
                    }
                    return readMsg();
                }
            }
            else
            {
                // 继续从socket中接收数据
                int socketAvailable = socket.Available;
                if (socketAvailable < needRead)
                {
                    // 数据依旧不足
                    socketRead(socketAvailable);
                    if (socketError != SocketError.Success)
                    {
                        return null;
                    }

                    needRead = needRead - socketAvailable;
                    return null;
                }
                else
                {
                    // 数据足够,解析数据
                    socketRead(needRead);
                    if (socketError != SocketError.Success)
                    {
                        return null;
                    }

                    return readMsg();
                }
            }
        }
        return null;
    }

    private void readReset()
    {
        // 读入一个完整消息后,重置数据
        available = 0;
        readBufIndex = 0;
        length = 0;
        needRead = 0;
    }
    private NetMsg readMsg()
    {
        NetMsg netMsg = new NetMsg();

        UInt32 loop = readUInt();
        short cmdId = readShort();

        byte[] protoData = null;
        if (cmdId > 0)
        {
            protoData = new byte[length - 10];
            Array.Copy(readBuf, readBufIndex, protoData, 0, length - 10);
        }
        else
        {
            // 有压缩
            MemoryStream inms = new MemoryStream(readBuf, readBufIndex, length - 10);
            MemoryStream outms = new MemoryStream();
            SevenZipTool.Unzip(inms, outms);
            protoData = outms.ToArray();
            cmdId = (short)-cmdId;
        }
        

        netMsg.loop = loop;
        netMsg.cmdType = (cmd.CmdType)cmdId;
        netMsg.data = protoData;
        netMsg.netdata=new MemoryStream(protoData);
        readReset();
        return netMsg;
    }

    private short readShort()
    {
        short ret = BitConverter.ToInt16(readBuf, readBufIndex);
        readBufIndex += 2;
        return Endian.SwapInt16(ret);
    }
    private ushort readUshort()
    {
        ushort ret = BitConverter.ToUInt16(readBuf, readBufIndex);
        readBufIndex += 2;
        return Endian.SwapUInt16(ret);
    }

    private int readInt()
    {
        int ret = BitConverter.ToInt32(readBuf, readBufIndex);
        readBufIndex += 4;
        return Endian.SwapInt32(ret);
    }

    private uint readUInt()
    {
        uint ret = BitConverter.ToUInt32(readBuf, readBufIndex);
        readBufIndex += 4;
        return Endian.SwapUInt32(ret);

    }
    private void socketRead(int readLen)
    {    //从socket中读入数据入在readBuf中
        socket.Receive(readBuf, available, readLen, SocketFlags.None, out socketError);
        if (socketError != SocketError.Success)
        {
            Debug.LogError("socket Read error:" + socketError);
        }
        available += readLen;
    }

    public int send(NetMsg netMsg, int offset)
    {
        if (netMsg.totalData == null)
        {
            encode(netMsg);
        }

        int sendNum = socket.Send(netMsg.totalData, offset, netMsg.totalData.Length - offset, SocketFlags.None, out socketError);
        if (socketError != SocketError.Success)
        {
            Debug.LogError("socket send error:" + socketError);
            return 0;
        }

        if (sendNum + offset == netMsg.totalData.Length)
        {
            return -1;//标志,全部发送完成
        }
        return sendNum;
    }


    private void encode(NetMsg netMsg)
    {
        MemoryStream outstream = new MemoryStream();
        byte[] _t = null;

        int totalLength = netMsg.data.Length + 10;
        if (totalLength > 65535)
        {
            _t = BitConverter.GetBytes(Endian.SwapInt16((short)0));
            outstream.Write(_t, 0, _t.Length);
            _t = BitConverter.GetBytes(Endian.SwapInt32(totalLength));
            outstream.Write(_t, 0, _t.Length);
        }
        else
        {
            _t = BitConverter.GetBytes(Endian.SwapInt16((short)totalLength));
            outstream.Write(_t, 0, _t.Length);
        }

        _t = BitConverter.GetBytes(Endian.SwapUInt32(netMsg.loop));
        outstream.Write(_t, 0, _t.Length);
        _t = BitConverter.GetBytes(Endian.SwapInt16((short)netMsg.cmdType));
        outstream.Write(_t, 0, _t.Length);
        outstream.Write(netMsg.data, 0, netMsg.data.Length);
        _t = BitConverter.GetBytes(Endian.SwapInt32((int)0));
        outstream.Write(_t, 0, _t.Length);

        _t = outstream.ToArray();
        netMsg.totalData = _t;
    }

    public bool isConnected()
    {
        return socket != null && (socket.Connected);
    }
}
//----------------------------------------------
//           NET        : NetManager
//           AUTHOR: zhu kun qian
//----------------------------------------------

using System;
using System.Linq;
using System.Net.Sockets;

using UnityEngine;
using System.Collections.Generic;
using System.IO;


public class NetManager
{
    public uint loop;// 永不重复,一直加1

    public string ip = "";
    public int port = 0;

    private NetManagerSocket socket = null;

    private int maxQuerySize = 100;//接收和发送最多cache 100条消息
    private int maxReconnectTimes = 3;//重连最多重试3次

    // 发送相关
    public Queue<NetMsg> msgQuery = new Queue<NetMsg>(); // 发送消息队列
    public NetMsg reconnectTokenLogin = null;//重连发送的登陆消息
    public bool loginSuccess = false;

    private int sendBytes = 0;
    private float sendBytesTimeout = 0f; // 发送消息超时

    // 接收的消息队列
    private Queue<NetMsg> receviedMsgQueue = new Queue<NetMsg>();// 从服务器接收到的消息

    private int reconnectTryTimes = 0; // 重连次数
    private float connectTimeout = 0f; // 连接超时
    public EventDelegate.Callback reconnectErrorCallback; // 如果出现内部无法处理的得连(1、连试3次无法重连成功 2、累积的消息数量过量,需要重启游戏客户端)
    

    // 如果断线后,是使用原socket重连,还是使用新的socket?新new出来
    public static NetManager Instance = null;
    public NetManager()
    {
        Instance = this;
    }

    public bool isConnected()
    {
        return socket != null && socket.isConnected();
    }
    public void connect()
    {
        if (socket != null)
        {
            Debug.LogError("socket is not closed,try close");
            socket.disconnect();
        }

        Debug.Log("start connect ip:" + ip + " port:" + port);

        socket = new NetManagerSocket();
        socket.ip = ip;
        socket.port = port;
        socket.connect();

        sendBytes = 0;
        sendBytesTimeout = 0f;

        connectTimeout = 0f;
    }

    public void disconnect()
    {
        if (socket == null)
        {
            Debug.LogError("socket is null");
            return;
        }
        socket.disconnect();
        socket = null;
    }

    public void onUpdate()
    {
        // 每祯执行,没有阻塞
        if (socket != null)
        {
            // 改为在socket中处理呢
            if (socket.socketError != SocketError.Success)
            {
                // 如果遇到sokcet错误,不需要等等超时,立即重连
                Debug.LogError("socket error:" + socket.socketError);
                tryReconnect();
                return;
            }

            if (socket.isConnected())
            {
                NetMsg msg = socket.read();
                if (msg != null)
                {
                    receviedMsgQueue.Enqueue(msg);
                }
                NetMsg netMsg = null;
                if (reconnectTokenLogin != null)
                {
                    netMsg = reconnectTokenLogin;
                }
                else
                {
                    if (loginSuccess)
                    {
                        lock (msgQuery)
                        {
                            if (msgQuery.Count > 0)
                            {
                                netMsg = msgQuery.First();
                            }
                        }
                    }
                }
                if (netMsg != null)
                {
                    socketSend(netMsg);
                }
            }
            else
            {
                if (connectTimeout == 0f)
                {
                    connectTimeout = Time.realtimeSinceStartup;
                }
                else if (Time.realtimeSinceStartup - connectTimeout > 5)
                {
                    // 连接5秒超时,处理重连
                    tryReconnect();
                }
            }
        }
    }

    private void tryReconnect()
    {
        if (reconnectTryTimes >= 3)
        {
            // 跳出错误处理回调,交给外部处理
            if (reconnectErrorCallback != null)
            {
                reconnectErrorCallback();
            }
            disconnect();
            return;
        }
        Debug.LogError("socket connect timeout, try reconnect:" + reconnectTryTimes + " " + socket.socketError);
        reconnectTryTimes++;
        disconnect();
        connect();
        // 重试需要,重新发送登陆消息505
        LoginState.Instance.loginWithTokenSub(true);

    }

    public NetMsg readMsg()
    {
        if (receviedMsgQueue.Count == 0)
        {
            return null;
        }
        return receviedMsgQueue.Dequeue();
    }

    // true:超时
    private void socketSend(NetMsg netMsg)
    {
        // 发送数据
        bool newMsg = false;// 新发送的消息
        if (sendBytes == 0)
        {
            newMsg = true;

        }
        int num = socket.send(netMsg, sendBytes);
        if (num > 0)
        {
            sendBytes += num;
        }
        if (num == -1)
        {
            // 全部发送完成
            if (cmd.CmdType.tokenLogin == netMsg.cmdType)
            {
                reconnectTokenLogin = null;
            }
            else
            {
                lock (msgQuery)
                {
                    msgQuery.Dequeue();
                }
            }
            sendBytes = 0;
            sendBytesTimeout = 0f;
        }
        else
        {
            // 未发送完成,处理超时逻辑
            if (newMsg)
            {
                sendBytesTimeout = Time.realtimeSinceStartup;
            }
            else
            {
                // 检查时间是否超时
                if (Time.realtimeSinceStartup - sendBytesTimeout > 5)
                {
                    // 超过5秒
                    Debug.LogError("socket timeout.try reconnect");
                    // 重连重发
                    if (socket.socketError != SocketError.Success)
                    {
                        Debug.LogError("socket error:" + socket.socketError);
                    }
                    socket.socketError = SocketError.TimedOut;
                }
            }
        }
    }

    public void SendCmd<T>(cmd.CmdType cmdType, T protoObj)
    {
        send(cmdType,protoObj);
    }
    public void send<T>(cmd.CmdType cmdType, T protoObj)
    {
        NetMsg netMsg = new NetMsg();
        netMsg.loop = ++loop;
        netMsg.cmdType = cmdType;

        MemoryStream outms = new MemoryStream();
        ProtoBuf.Serializer.Serialize(outms, protoObj);
        netMsg.data = outms.ToArray();

        // todo:因为放在onupdate中,感觉这个lock也是可以避免掉的。暂时先加上,以后测试后再考虑去掉。
        // 只要能确认不会多线程操作,就可以去掉这个lock
        if (cmdType == cmd.CmdType.tokenLogin)
        {
            reconnectTokenLogin = netMsg;
            loginSuccess = false;
            return;
        }
        lock (msgQuery)
        {
            msgQuery.Enqueue(netMsg);
            // 在onupdate中发送,这样只差3ms,是可以接受的
        }

        if (msgQuery.Count > maxQuerySize)
        {
            Debug.LogError("msgQuery more than max size:" + msgQuery.Count);
        }
    }
}








2015.01.19记:我重构时,未添加断线补发未送达的消息,我们客户端写的逻辑不严谨,在登陆游戏按钮上连续多次点击,会导致卡住登陆。我这里只是使用禁止多次点击来避免这个问题,如果使用有断线补发未送达的消息也可以不会出现这个问题。总之,不要依赖客户端代码很强壮,最好还是添加上断线补发消息。

unity3d streaming asset文件加载

如果使用www加载资源的方式:

参考:http://docs.unity3d.com/Manual/StreamingAssets.html

安卓的路径需要 “jar:file://”开头,因为安卓自身是apk压缩格式

ios和win需要添加 “file:///”开头。

 

2014.1.4附:

1、这是现在用的加载方式(加载速度很快)

 WWW www = new WWW(path);
                yield return www;
                if (!string.IsNullOrEmpty(www.error))
                {
                    Debug.LogError("www error:" + www.error);
                }
                if (www.assetBundle.mainAsset != null)
                {
                    AssetBundle bundle = www.assetBundle;
                    ConfigDataCSVScriptableObject obj = bundle.mainAsset as ConfigDataCSVScriptableObject;
                    callback(obj);
                    bundle.Unload(true);
                    www.Dispose();
                }
                else
                {
                    Debug.LogError("can't find assetbundle with url:" + path);
                }

2、这是原来用的加载方式(加载速度很慢,慢上4~5倍)

if (File.Exists(path))
                {
                    byte[] bytes = File.ReadAllBytes(path);
                    AssetBundleCreateRequest request = AssetBundle.CreateFromMemory(bytes);
                    yield return request;
                    while (!request.isDone)
                    {
                        yield return request.isDone;
                    }
                    AssetBundle bundle = request.assetBundle;
                    
                    ConfigDataCSVScriptableObject obj = bundle.mainAsset as ConfigDataCSVScriptableObject;
                    callback(obj);
                    bytes = null;
                    bundle.Unload(true);
                }
                else
                {
                    Debug.LogError("can't find assetbundle with path:" + path);
                }

以我现在的能力,还无法了解为什么第二种为什么会很慢,也可能是中间存在一次内存copy的原因?

2014.01.06 附,查找一下慢的原因。

记录了不同加载方式解析消耗的时间,发现解析消耗的时间相差无已,看起来应该加载入内存消耗的时间不相同。

找到原因了:

AssetBundleCreateRequest request = AssetBundle.CreateFromMemory(bytes);

这一句导致的异常的慢。

WWW是加载时直接转为了assetbundle,少了这一步,所以非常快。

如果将第二种方式修改为使用WWW加载assetbundle,速度提升了近7倍。

 

text配置表优化

项目中字符串配置表是最大的一张表,足足有一万多条记录。

我们目前的存储方式是按csv类似格式存为scriptableobject。这张表的解析基本上占用总解析时间的25%~35%,因此优化这张表非常有必要。

查看了下text表中。其中重复内容大概按25%左右。因为表是按key,value键值对存储,计算了下key占用了45%的空间,这里优化的余地非常大。

1、不再存储key,而是将key改为int型的hashcode,这样,可以节省45%的空间

以下是java中的hashcode实现,测试了下,目前没发现有重复。

public int hashCode() {
int h = hash;
if (h == 0 && value.length > 0) {
char val[] = value;

for (int i = 0; i < value.length; i++) {
h = 31 * h + val[i];
}
hash = h;
}
return h;
}

2、针对有重复的value的情况,另存一个Map<hashcode,hashcode>,如果发现在map中存在当前使用的hashcode,则替代为使用map中的对应的value hashcode去查找。这样可以再减少25%的空间。

android生成的keystore默认密码

好吧,我们一个项目开发人员一不小心使用了默认生成的debug.keystore提交了app,结果线上无法换keystore,我们只好将debug.keystore拿过来用了。

debug.keystore默认密码是 android。

unity3d 中尽量避免使用foreach

foreach会消耗掉额外的内存,生成gc alloc,为了压榨手机的最后一部分性能,尽量避免使用foreach。

遍历数组直接使用:

for(int m=0;m<array.length;m++){

// array[m] 操作

}

遍历dictinory使用,这种方式不会造成gc

  • var enumerator = dummyDic.GetEnumerator();
  • while (enumerator.MoveNext()) {
  • // loop body goes here
  • }

unity命令和打包。

针对不同的平台(android/ios/pc),unity会对不同平台进行针对性优化。

因此如果要针对不同平台打包,最好针对每个平台将项目目录复制出来一份。这样可以避免unity发现平台转化了,对资源进行重新优化。当然了,如果做的游戏小,这点时间是可以忽略不计的。可惜我们的项目转一次资源就得半小时-.-!!。