问题发现:当在Upade中使用倒计时的时候,会出现大量的内存分配,这个内存分配主要是tostring()或string.format引起的,这就导致了频繁的GC。
1. 先看4种状态下的获取string的内存比较(int.tostring(),SpeedString,char.tostring,StringBuilder.Tostring())
代码如下:
public void Update()
{
IntTostring();
StringBuilderTostring();
CharsTostring();
SpeedStringToString();
}
System.Text.StringBuilder builder = new System.Text.StringBuilder(50);
private void IntTostring(int time = 10000)
{
string timeStr = time.ToString();
}
private void StringBuilderTostring(int time =1)
{
builder.Length = 0;
builder.Append("1000");
string timeStr = builder.ToString();
}
private void CharsTostring(int time = 1)
{
chars[0] = (char)(time + '0');
chars[1] = (char)(time + '0');
chars[2] = (char)(time + '0');
string timeStr = chars.ToString();
}
int i =10000;
private void SpeedStringToString()
{
speedStr.Clear();
speedStr.Append(Random.Range(0,100000));
string str = speedStr.string_base;
}
SpeedString speedStr = new SpeedString(10);
char[] chars = new char[4];
SpeedString的实现(参考http://forum.unity3d.com/threads/stringbuilder-problem.122262/)
public class SpeedString
{
public string string_base;
public System.Text.StringBuilder string_builder;
private char[] int_parser = new char[11];
public SpeedString(int capacity)
{
string_builder = new System.Text.StringBuilder(capacity, capacity);
string_base = (string)string_builder.GetType().GetField(
"_str",
System.Reflection.BindingFlags.NonPublic |
System.Reflection.BindingFlags.Instance).GetValue(string_builder);
}
private int i;
public void Clear()
{
string_builder.Length = 0;
for (i = 0; i < string_builder.Capacity; i++)
{
string_builder.Append(' ');
}
string_builder.Length = 0;
}
//public void Draw(ref string text){
// text.text = "";
// text.text = string_base;
// text.cachedTextGenerator.Invalidate();
//}
public void Append(string value)
{
string_builder.Append(value);
}
int count;
public void Append(int value)
{
if (value >= 0)
{
count = ToCharArray((uint)value, int_parser, 0);
}
else
{
int_parser[0] = '-';
count = ToCharArray((uint)-value, int_parser, 1) + 1;
}
for (i = 0; i < count; i++)
{
string_builder.Append(int_parser[i]);
}
}
private static int ToCharArray(uint value, char[] buffer, int bufferIndex)
{
if (value == 0)
{
buffer[bufferIndex] = '0';
return 1;
}
int len = 1;
for (uint rem = value / 10; rem > 0; rem /= 10)
{
len++;
}
for (int i = len - 1; i >= 0; i--)
{
buffer[bufferIndex + i] = (char)('0' + (value % 10));
value /= 10;
}
return len;
}
}
- 选择使用SpeedString来在Update()中进行赋值(或战斗中血条的显示及技能CD)
使用NGUI遇到的问题:
(1). UILabel.text = str不会刷新界面。
原因:NGUI中text赋值的实现如 代码2:(由于使用SpeedString后,给NGUI反复赋值都是使用的同一个string内存空间(SpeedString.string_base),因此mText == value始终为true,为了刷新界面,提出更改的部分,实现如 代码3)
代码2
public string text
{
get
{
return mText;
}
set
{
if (mText == value) return;
if (string.IsNullOrEmpty(value))
{
if (!string.IsNullOrEmpty(mText))
{
mText = "";
MarkAsChanged();
ProcessAndRequest();
}
}
else if (mText != value)
{
mText = value;
MarkAsChanged();
ProcessAndRequest();
}
if (autoResizeBoxCollider) ResizeCollider();
}
}
代码3
public void SetText(string _text, bool bForce = false)
{//3.7.7
if (bForce)
{
if(mText != _text)
mText = _text;
MarkAsChanged();
ProcessAndRequestForce();
if (autoResizeBoxCollider) ResizeCollider();
}
else
this.text = _text;
}
对NGUI 3.9.7版本的,使用如下代码:
public void SetText(string _text, bool bForce = false)
{
if (bForce)
{
if (mText != _text || mProcessedText!=_text)
{
mText = _text;
mProcessedText = _text;
}
MarkAsChanged();
ProcessAndRequestForce();
if (autoResizeBoxCollider) ResizeCollider();
}
else
this.text = _text;
}
ProcessAndRequestForce是对NGUI中的又已修改,因为使用的原ProcessAndRequest过程发现,UILabel中的void ProcessText (bool legacyMode, bool full)会导致内存分配,为了避免,修改了这个函数
void ProcessText (bool legacyMode, bool full,bool bFroce = false)
{
//........省略.....................
// Wrap the text
bool fits = true;
if ( !bFroce || (mText != mProcessedText && bFroce))
fits = NGUIText.WrapText(mText, out mProcessedText, true);
//........省略.....................
}
void ProcessAndRequestForce()
{
#if UNITY_EDITOR
if (!Application.isPlaying && !NGUITools.GetActive(this)) return;
if (!mAllowProcessing) return;
#endif
if (ambigiousFont != null) ProcessText(false, true,true);
}
最后就将给UILabel赋值的内存分配降低到了0。
///
关于 读者提出的修改SetText的方法的问题:
public void SetText(string _text,bool bForce = false)
{
if(bForce)
{
if (mText != _text)
mText = _text;
MarkAsChanged();
ProcessAndRequestForce();
if (autoResizeBoxCollider) ResizeCollider();
}
{
text = _text;
}
}
public void SetTextNew(string _text, bool bForce = false)
{
if (bForce)
{
if (mText != _text)
{
mText = _text;
ProcessAndRequestForce();
}
MarkAsChanged();
if (autoResizeBoxCollider) ResizeCollider();
}
{
text = _text;
}
}
原代码的测试结果
读者的测试结果
内存中多了UIPanelLateUpdate中的内存消耗。
谢谢 hyf2713 提出不同的方法,有问题希望大家提出,或者有更好的修改方法