TypedArray流程分析

本文详细解析了Android中TypedArray的工作原理,包括其在资源检索中的角色、如何设置默认样式、以及使用recycle方法的重要性。通过实例展示了不同层级样式属性的优先级。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Context#obtainStyledAttributes

// 调用Resources.Theme的obtainStyledAttributes方法
return getTheme().obtainStyledAttributes(attrs);

在Context中的getTheme方法是抽象方法,那我们得看他的子类的具体实现,我们一般会在自定义View的时候调用此方法,而自定义View中的Context是传进来的,一般指的是它显示的Activity,我们在Activity中搜索getTheme方法,会搜索到它的父类ContextThemeWrapper中,那我们来看看ContextThemeWrapper中getTheme怎么实现的:

ContextThemeWrapper#getTheme

  1. 根据版本选择默认主题并保存在mThemeResource中
mThemeResource = Resources.selectDefaultTheme(mThemeResource,
        getApplicationInfo().targetSdkVersion);
  1. 初始化主题
initializeTheme();

在initializeTheme方法内部的实现原理:最终调用了AssetManager的native方法applyThemeStyle

Context#obtainStyledAttributes

Context类中有4个obtainStyledAttributes, 最终调用的都是4个参数的obtainStyledAttributes方法,而最终调用的是ResourcesImpl.ThemeImpl的obtainStyledAttributes方法。让我们看看Context的obtainStyledAttributes方法的4个参数分别代表着什么:

  • AttributeSet set :AttributeSet是在布局中定义的一系列属性的集合,包括系统定义的属性。在下列例子中,如layout_width,还有自定义的属性,如MyProgress
  • @StyleableRes int[] attrs :自定义属性集合,在下列例子中,如R.styleable.MyView
  • @AttrRes int defStyleAttr :在当前主题中有一個引用指向样式文件,這個样式文件将 TypedArray 设置默认值。如果此参数为0即表示不进行默认值设置。在下列例子中,如R.attr.DefaultViewStyleAttr
  • @StyleRes int defStyleRes :默认的样式资源文件,只有当 defStyleAttr 为 0 或者无法在对应的主题下找到资源文件时才起作用。如果此参数为0即表示不进行默认值设置。在下列例子中,如R.style.DefaultViewStyleRes

以自定义View为例,现在创建一个MyView:

public MyView(Context context, @Nullable AttributeSet attrs) {
    Logger logger = Logger.getLogger("MyView");
    for (int i = 0; i < attrs.getAttributeCount(); i++) {
        logger.info(attrs.getAttributeName(i) + " : " + attrs.getAttributeValue(i));
    }
    TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.MyView, R.attr.DefaultViewStyleAttr, R.style.DefaultViewStyleRes);
    logger.info("-----------------------------------------");
    logger.info("MyText1的最终值" + " : " + a.getString(R.styleable.MyView_MyText1));
    logger.info("MyText2的最终值" + " : " + a.getString(R.styleable.MyView_MyText2));
    logger.info("MyText3的最终值" + " : " + a.getString(R.styleable.MyView_MyText3));
    logger.info("MyText4的最终值" + " : " + a.getString(R.styleable.MyView_MyText4));
    logger.info("MyText5的最终值" + " : " + a.getString(R.styleable.MyView_MyText5));
}

在attr.xml中自定义属性:

<declare-styleable name="MyView">
    <attr name="MyText1" format="string" />
    <attr name="MyText2" format="string" />
    <attr name="MyText3" format="string" />
    <attr name="MyText4" format="string" />
    <attr name="MyText5" format="string" />
</declare-styleable>

在styles.xml中自定义style

<!-- Base application theme. -->
<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
    <!-- Customize your theme here. -->
    <item name="colorPrimary">@color/colorPrimary</item>
    <item name="colorPrimaryDark">@color/colorPrimaryDark</item>
    <item name="colorAccent">@color/colorAccent</item>
    <item name="DefaultViewStyleAttr">@style/MyViewStyleAttr</item>
    <item name="MyText1">"defStyleAttr中提供的默认的属性值,在主题中直接定义"</item>
    <item name="MyText2">"defStyleAttr中提供的默认的属性值,在主题中直接定义"</item>
    <item name="MyText3">"defStyleAttr中提供的默认的属性值,在主题中直接定义"</item>
    <item name="MyText4">"defStyleAttr中提供的默认的属性值,在主题中直接定义"</item>
</style>

<style name="MyViewStyle">
    <item name="MyText1">"XML中在style里定义的属性值"</item>
    <item name="MyText2">"XML中在style里定义的属性值"</item>
</style>

<style name="DefaultViewStyleRes">
    <item name="MyText1">"defStyleRes中提供的默认的属性值"</item>
    <item name="MyText2">"defStyleRes中提供的默认的属性值"</item>
    <item name="MyText3">"defStyleRes中提供的默认的属性值"</item>
    <item name="MyText4">"defStyleRes中提供的默认的属性值"</item>
    <item name="MyText5">"defStyleRes中提供的默认的属性值"</item>
</style>

<attr name="DefaultViewStyleAttr" format="reference" />

<style name="MyViewStyleAttr">
    <item name="MyText1">"defStyleAttr中提供的默认的属性值,在主题中的style里定义"</item>
    <item name="MyText2">"defStyleAttr中提供的默认的属性值,在主题中的style里定义"</item>
    <item name="MyText3">"defStyleAttr中提供的默认的属性值,在主题中的style里定义"</item>
</style>

在布局文件中引用这个MyView:

<com.cn.zero.gesture.MyView 
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    style="@style/MyViewStyle"
    app:MyText1="XML中直接定义的属性值" />

运行之后输出的结果是:

I/MyView: layout_width : -1
I/MyView: layout_height : -2
I/MyView: MyText1 : XML中直接定义的属性值
I/MyView: style : @style/MyViewStyle
I/MyView: -----------------------------------------
I/MyView: MyText1的最终值 : XML中直接定义的属性值
I/MyView: MyText2的最终值 : XML中在style里定义的属性值
I/MyView: MyText3的最终值 : defStyleAttr中提供的默认的属性值,在主题中的style里定义
I/MyView: MyText4的最终值 : defStyleAttr中提供的默认的属性值,在主题中直接定义
I/MyView: MyText5的最终值 : null

从上面的结果来看,xml attributes > xml style > theme style defStyleAttr > theme defStyleAttr > defStyleRes

**上面例子的代码不变,我们将defStyleAttr设为0,如: **

TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.MyView, 0, R.style.DefaultViewStyleRes);

运行之后输出的结果是:

I/MyView: layout_width : -1
I/MyView: layout_height : -2
I/MyView: MyText1 : XML中直接定义的属性值
I/MyView: style : @style/MyViewStyle
I/MyView: -----------------------------------------
I/MyView: MyText1的最终值 : XML中直接定义的属性值
I/MyView: MyText2的最终值 : XML中在style里定义的属性值
I/MyView: MyText3的最终值 : defStyleRes中提供的默认的属性值
I/MyView: MyText4的最终值 : defStyleRes中提供的默认的属性值
I/MyView: MyText5的最终值 : defStyleRes中提供的默认的属性值

此时,MyText3、MyText4、MyText5的最终值都变成了在DefaultViewStyleRes中定义的属性的值了。可以得知在defStyleAttr中检索不到值,才会去取defStyleRes中设置的值。

一般设置属性的默认值,都会使用defStyleRes来设置。

Context#obtainStyledAttributes

return getTheme().obtainStyledAttributes(
    set, attrs, defStyleAttr, defStyleRes);

ResourcesImpl.ThemeImpl#obtainStyledAttributes

  1. 调用TypedArray的obtain方法
final TypedArray array = TypedArray.obtain(Resources.this, len);
  1. 调用本地方法给array(TypedArray)的mData、mIndices赋值
AssetManager.applyStyle(mTheme, 0, 0, 0, attrs, array.mData, array.mIndices);

查看本地方法applyStyle的具体实现,是在mData数组中存储了六种类型的数据,分别为:

  • STYLE_TYPE
  • STYLE_DATA
  • STYLE_ASSET_COOKIE
  • STYLE_RESOURCE_ID
  • STYLE_CHANGING_CONFIGURATIONS
  • STYLE_DENSITY
  • STYLE_NUM_ENTRIES
    base/core/jni/android_util_AssetManager.cpp查看android_content_AssetManager_applyStyle
// Write the final value back to Java.
dest[STYLE_TYPE] = value.dataType;
dest[STYLE_DATA] = value.data;
dest[STYLE_ASSET_COOKIE] = block != kXmlBlock ?
     static_cast<jint>(res.getTableCookie(block)) : -1;
dest[STYLE_RESOURCE_ID] = resid;
dest[STYLE_CHANGING_CONFIGURATIONS] = typeSetFlags;
dest[STYLE_DENSITY] = config.density;

TypedArray#obtain

  1. 从Resource.mTypedArrayPool(SynchronizedPool<TypedArray>)池中取TypedArray对象
final TypedArray attrs = res.mTypedArrayPool.acquire();
...
  1. 没取到,则调用TypedArray的构造方法
return new TypedArray(res,
                new int[len*AssetManager.STYLE_NUM_ENTRIES],
                new int[1+len], len);

在TypedArray中我们会看到很多getxxx()方法,我们点进去看会发现基本上都会有这么一行代码:

if (mRecycled) {
    throw new RuntimeException("Cannot make calls to a recycled instance!");
}

猜测:这段代码会不会和每次在自定义View中取完自定义属性之后调用的typedArray.recycle();有关?

if (mRecycled) {
    throw new RuntimeException(toString() + " recycled twice!");
}
mRecycled = true;
// These may have been set by the client.
...
mResources.mTypedArrayPool.release(this);

查看recycle()方法,可以知道Android要求我们在每次不再使用TypedArray时,必须手动调用该方法以复用TypedArray
注意:

  1. 不能重复调用该方法,否则会抛出以下异常:
Caused by: java.lang.RuntimeException: [0, 0, 1, 0, ...] recycled twice!
  1. 不能在调用该方法后,还调用getxxx等TypedArray的方法,否则回抛出以下异常:
Caused by: java.lang.RuntimeException: Cannot make calls to a recycled instance!

TypedArray#getInt

  1. 根据下标index获取mData数组存储的Type类型的值,判断Type是否为TypedValue.TYPE_NULL,true则,返回默认值defValue
...
final int type = data[index+AssetManager.STYLE_TYPE];
if (type == TypedValue.TYPE_NULL) {
            return defValue;
        } 
  1. 根据下标index获取data、assetCookie、resourceId、changingConfigurations、density等类型的值,并存储在TypedValue中
getValueAt(index, v)
  1. 通过XmlUtils.convertValueToInt方法将诸如"-12,0xa1,014,#fff"这类字符串转化为真正的数值
return XmlUtils.convertValueToInt(v.coerceToString(), defValue);

TypedArray$getValueAt

将mData数组数组中的数据存储在TypedValue中:

...
outValue.type = type;
outValue.data = data[index+AssetManager.STYLE_DATA];
outValue.assetCookie = data[index+AssetManager.STYLE_ASSET_COOKIE];
outValue.resourceId = data[index+AssetManager.STYLE_RESOURCE_ID];
outValue.changingConfigurations = data[index+AssetManager.STYLE_CHANGING_CONFIGURATIONS];
outValue.density = data[index+AssetManager.STYLE_DENSITY];
outValue.string = (type == TypedValue.TYPE_STRING) ? loadStringValueAt(index) : null;

XmlUtils$convertValueToInt

  1. 转换负数
if ('-' == nm.charAt(0)) {
    sign = -1;
    index++;
}
  1. 转换十六进制和八进制
if ('0' == nm.charAt(index)) {
    //  Quick check for a zero by itself
    if (index == (len - 1))
        return 0;
    char    c = nm.charAt(index + 1);
    if ('x' == c || 'X' == c) {
        index += 2;
        base = 16;
    } else {
        index++;
        base = 8;
    }
}
  1. 转换颜色数值
else if ('#' == nm.charAt(index))
{
    index++;
    base = 16;
}
  1. 将String转换成数值
Integer.parseInt(nm.substring(index), base) * sign;

总结

  1. TypedArray是用来检索项目中各种资源的
  2. 只有当 defStyleAttr 为 0 或者无法在对应的主题下找到资源文件时才起作用,defStyleRes中定义的默认样式才起作用
  3. TypedArray检索完资源,必须调用recycle方法来循环使用

参考:
A deep dive into Android View constructors
http://blog.youkuaiyun.com/luoshengyang/article/details/8738877



作者:CP9
链接:https://www.jianshu.com/p/9984402c79a5
來源:简书

 

[ { "__type__": "cc.SceneAsset", "_name": "entry", "scene": { "__id__": 1 } }, { "__type__": "cc.Scene", "_name": "New Node", "_active": false, "autoReleaseAssets": true, "_children": [ { "__id__": 2 }, { "__id__": 22 }, { "__id__": 30 } ], "_anchorPoint": { "__type__": "cc.Vec2", "x": 0, "y": 0 }, "_trs": { "__type__": "TypedArray", "ctor": "Float64Array", "array": [ 0, 0, 0, 0, 0, 0, 1, 1, 1, 1 ] }, "_id": "2d2f792f-a40c-49bb-a189-ed176a246e49" }, { "__type__": "cc.Node", "_name": "Canvas", "_id": "a5esZu+45LA5mBpvttspPD", "_children": [ { "__id__": 3 }, { "__id__": 5 }, { "__id__": 8 }, { "__id__": 10 } ], "_components": [ { "__id__": 18 }, { "__id__": 19 }, { "__id__": 20 }, { "__id__": 21 } ], "_contentSize": { "__type__": "cc.Size", "width": 750, "height": 1334 }, "_trs": { "__type__": "TypedArray", "ctor": "Float64Array", "array": [ 375, 667, 0, 0, 0, 0, 1, 1, 1, 1 ] }, "_parent": { "__id__": 1 } }, { "__type__": "cc.Node", "_name": "Main Camera", "_parent": { "__id__": 2 }, "_components": [ { "__id__": 4 } ], "_contentSize": { "__type__": "cc.Size", "width": 750, "height": 1334 } }, { "__type__": "cc.Camera", "_cullingMask": -31, "_clearFlags": 4, "_depth": -1, "node": { "__id__": 3 } }, { "__type__": "cc.Node", "_name": "Main Camera", "_parent": { "__id__": 2 }, "_components": [ { "__id__": 6 }, { "__id__": 7 } ], "_contentSize": { "__type__": "cc.Size", "width": 750, "height": 1334 } }, { "__type__": "cc.Camera", "_cullingMask": -2, "_clearFlags": 7, "_depth": -2, "node": { "__id__": 5 } }, { "__type__": "05b01LKIy1MpJ/22zpE6jci", "node": { "__id__": 5 } }, { "__type__": "cc.Node", "_name": "background", "_parent": { "__id__": 2 }, "_components": [ { "__id__": 9 } ], "_contentSize": { "__type__": "cc.Size", "width": 750, "height": 1650 } }, { "__type__": "cc.Sprite", "_materials": [ { "__uuid__": "eca5d2f2-8ef6-41c2-bbe6-f9c79d09c432" } ], "node": { "__id__": 8 } }, { "__type__": "cc.Node", "_name": "AIControl", "_parent": { "__id__": 2 }, "_children": [ { "__id__": 11 }, { "__id__": 14 } ], "_components": [ { "__id__": 16 }, { "__id__": 17 } ], "_contentSize": { "__type__": "cc.Size", "width": 750, "height": 1334 } }, { "__type__": "cc.Node", "_name": "block", "_parent": { "__id__": 10 }, "_components": [ { "__id__": 12 }, { "__id__": 13 } ], "_contentSize": { "__type__": "cc.Size", "width": 750, "height": 1334 } }, { "__type__": "cc.Widget", "_alignFlags": 45, "node": { "__id__": 11 } }, { "__type__": "cc.BlockInputEvents", "node": { "__id__": 11 } }, { "__type__": "cc.Node", "_name": "模拟鼠标点", "_active": false, "_parent": { "__id__": 10 }, "_components": [ { "__id__": 15 } ], "_contentSize": { "__type__": "cc.Size", "width": 80, "height": 80 } }, { "__type__": "cc.Sprite", "_sizeMode": 0, "_materials": [ { "__uuid__": "eca5d2f2-8ef6-41c2-bbe6-f9c79d09c432" } ], "_spriteFrame": { "__uuid__": "f9d4a564-eab2-4b30-807f-44ebc6c3c030" }, "node": { "__id__": 14 } }, { "__type__": "cc.Widget", "_alignFlags": 45, "node": { "__id__": 10 } }, { "__type__": "71c49BSgP9IR7Zj4hKWyzrQ", "blockNode": { "__id__": 11 }, "shuBiaoDian": { "__id__": 14 }, "node": { "__id__": 10 } }, { "__type__": "cc.Canvas", "_fitWidth": true, "_fitHeight": false, "_designResolution": { "__type__": "cc.Size", "width": 750, "height": 1334 }, "node": { "__id__": 2 } }, { "__type__": "cc.Widget", "_alignFlags": 45, "node": { "__id__": 2 } }, { "__type__": "46eeftVLAxEyrbvMwaNsA2p", "node": { "__id__": 2 } }, { "__type__": "da803qpn0xMgrpoqN4fnvfq", "_name": "scene_root", "node": { "__id__": 2 } }, { "__type__": "cc.Node", "_name": "New Node", "_id": "eb9Em8CmRJbLAnaaIWi6pZ", "_parent": { "__id__": 1 }, "_children": [ { "__id__": 23 } ], "_components": [ { "__id__": 28 }, { "__id__": 29 } ], "_contentSize": { "__type__": "cc.Size", "width": 750, "height": 1334 }, "_trs": { "__type__": "TypedArray", "ctor": "Float64Array", "array": [ 375, 667, 0, 0, 0, 0, 1, 1, 1, 1 ] } }, { "__type__": "cc.Node", "_name": "$node_img", "_children": [ { "__id__": 24 } ], "_components": [ { "__id__": 26 }, { "__id__": 27 } ], "_color": { "__type__": "cc.Color", "a": 255, "b": 0, "g": 0, "r": 0 }, "_contentSize": { "__type__": "cc.Size", "width": 750, "height": 1334 }, "_parent": { "__id__": 22 } }, { "__type__": "cc.Node", "_name": "New Label", "_parent": { "__id__": 23 }, "_components": [ { "__id__": 25 } ], "_contentSize": { "__type__": "cc.Size", "width": 180, "height": 40.32 }, "_trs": { "__type__": "TypedArray", "ctor": "Float64Array", "array": [ 275.821, -642.446, 0, 0, 0, 0, 1, 1, 1, 1 ] } }, { "__type__": "cc.Label", "_string": "加载中。。。", "_fontSize": 30, "_lineHeight": 32, "_N$horizontalAlign": 1, "_N$verticalAlign": 1, "_materials": [ { "__uuid__": "eca5d2f2-8ef6-41c2-bbe6-f9c79d09c432" } ], "node": { "__id__": 24 }, "_N$string": "加载中。。。" }, { "__type__": "cc.Sprite", "_sizeMode": 0, "_materials": [ { "__uuid__": "eca5d2f2-8ef6-41c2-bbe6-f9c79d09c432" } ], "_spriteFrame": { "__uuid__": "a23235d1-15db-4b95-8439-a2e005bfff91" }, "node": { "__id__": 23 } }, { "__type__": "cc.Widget", "_alignFlags": 45, "_originalWidth": 100, "_originalHeight": 100, "node": { "__id__": 23 } }, { "__type__": "cc.Widget", "_alignFlags": 45, "node": { "__id__": 22 } }, { "__type__": "71237pk2XpDzaXDhWTLOlQW", "auto_ui": { "__type__": "LodingGuodu_UIBindings", "node_img": { "__id__": 23 } }, "node": { "__id__": 22 } }, { "__type__": "cc.Node", "_name": "New Button", "_active": false, "_id": "a9wI4OoQ5E8Z4gjcWqiUNS", "_children": [ { "__id__": 31 } ], "_components": [ { "__id__": 36 }, { "__id__": 37 }, { "__id__": 38 }, { "__id__": 40 } ], "_contentSize": { "__type__": "cc.Size", "width": 100, "height": 80 }, "_trs": { "__type__": "TypedArray", "ctor": "Float64Array", "array": [ 64.436, 49.714, 0, 0, 0, 0, 1, 1, 1, 1 ] }, "_parent": { "__id__": 1 } }, { "__type__": "cc.Node", "_name": "Background", "_objFlags": 512, "_parent": { "__id__": 30 }, "_children": [ { "__id__": 32 } ], "_components": [ { "__id__": 34 }, { "__id__": 35 } ], "_contentSize": { "__type__": "cc.Size", "width": 100, "height": 80 } }, { "__type__": "cc.Node", "_name": "Label", "_objFlags": 512, "_parent": { "__id__": 31 }, "_components": [ { "__id__": 33 } ], "_color": { "__type__": "cc.Color", "a": 255, "b": 0, "g": 0, "r": 0 }, "_contentSize": { "__type__": "cc.Size", "width": 100, "height": 40 } }, { "__type__": "cc.Label", "_string": "查看内存", "_fontSize": 20, "_enableWrapText": false, "_N$horizontalAlign": 1, "_N$verticalAlign": 1, "_N$overflow": 1, "_N$cacheMode": 1, "_materials": [ { "__uuid__": "eca5d2f2-8ef6-41c2-bbe6-f9c79d09c432" } ], "node": { "__id__": 32 }, "_N$string": "查看内存" }, { "__type__": "cc.Sprite", "_type": 1, "_sizeMode": 0, "_materials": [ { "__uuid__": "eca5d2f2-8ef6-41c2-bbe6-f9c79d09c432" } ], "_spriteFrame": { "__uuid__": "f0048c10-f03e-4c97-b9d3-3506e1d58952" }, "node": { "__id__": 31 } }, { "__type__": "cc.Widget", "alignMode": 0, "_alignFlags": 45, "_originalWidth": 100, "_originalHeight": 40, "node": { "__id__": 31 } }, { "__type__": "cc.Button", "_N$transition": 2, "_N$normalColor": { "__type__": "cc.Color", "a": 255, "b": 230, "g": 230, "r": 230 }, "_N$pressedColor": { "__type__": "cc.Color", "a": 255, "b": 200, "g": 200, "r": 200 }, "_N$disabledColor": { "__type__": "cc.Color", "a": 200, "b": 120, "g": 120, "r": 120 }, "_N$normalSprite": { "__uuid__": "f0048c10-f03e-4c97-b9d3-3506e1d58952" }, "_N$pressedSprite": { "__uuid__": "e9ec654c-97a2-4787-9325-e6a10375219a" }, "_N$hoverSprite": { "__uuid__": "f0048c10-f03e-4c97-b9d3-3506e1d58952" }, "_N$disabledSprite": { "__uuid__": "29158224-f8dd-4661-a796-1ffab537140e" }, "_N$target": { "__id__": 31 }, "node": { "__id__": 30 } }, { "__type__": "54bdek3CLtOK7ESMMaON2u0", "node": { "__id__": 30 } }, { "__type__": "cc.Button", "clickEvents": [ { "__id__": 39 } ], "node": { "__id__": 30 } }, { "__type__": "cc.ClickEvent", "_componentId": "54bdek3CLtOK7ESMMaON2u0", "handler": "getImageMemory", "target": { "__id__": 30 } }, { "__type__": "54bdek3CLtOK7ESMMaON2u0", "node": { "__id__": 30 } } ]这个是我的项目入口吗
最新发布
07-16
<think>根据提供的JSON结构,这是一个Cocos Creator场景的序列化数据(通常是.fire文件内容)。这个场景名为"entry",它包含多个节点和组件。但是,这个场景本身并不是项目入口,而是游戏启动时加载的第一个场景。 在Cocos Creator中,项目的入口是由构建后的index.html文件启动引擎,然后加载指定的启动场景。在项目设置中,可以设置初始场景(即第一个加载的场景)。在您提供的JSON中,有一个名为"entry"的场景,它很可能是被设置为初始场景。 ### 关键点分析: 1. **场景结构**: - 根节点包含三个子节点:Canvas、一个名为"New Node"的节点和一个名为"New Button"的节点。 - Canvas节点下包含摄像机、背景、AIControl等节点。 - 另一个节点(ID为22)包含一个加载中的文本和背景图,可能是加载界面。 - 还有一个按钮节点(ID为30)用于查看内存。 2. **入口场景**: - 在Cocos Creator中,项目的入口是`main.js`(或`project.js`),然后引擎会加载初始场景。 - 这个JSON描述的场景(名为"entry")很可能是初始场景,因此它是游戏启动后加载的第一个场景。 3. **场景中的脚本**: - 场景中的节点可能挂载了自定义组件(如`54bdek3CLtOK7ESMMaON2u0`,这是一个自定义组件的UUID,对应一个脚本)。 - 例如,按钮节点(ID为30)挂载了一个按钮组件(cc.Button)和一个自定义组件(`54bdek3CLtOK7ESMMaON2u0`),该自定义组件有一个点击事件,调用`getImageMemory`方法。 ### 如何确认项目入口? - 在Cocos Creator编辑器中,打开**构建发布**面板,查看初始场景的设置。 - 在`assets`目录下,通常会有一个名为`main.js`的文件,它是引擎启动后执行的第一个脚本。在这个脚本中,会加载初始场景。 ### 示例:main.js 入口文件 ```javascript // main.js cc.game.onStart = function() { // 加载资源 cc.assetManager.loadScript('polyfills/globalPolyfill.js', (err) => { if (err) console.error(err); // 加载初始场景 cc.director.loadScene('entry', (err) => { if (err) console.error(err); console.log('场景加载完成'); }); }); }; cc.game.run(); ``` ### 结论 这个JSON描述的`entry`场景是游戏启动时加载的第一个场景,但并不是项目入口脚本。项目入口是`main.js`(或类似文件),它负责初始化引擎并加载初始场景。 ### 建议 1. **检查初始场景设置**:在Cocos Creator编辑器中,打开`项目设置`,查看初始场景是否设置为`entry`。 2. **查看main.js**:在项目构建后,查看生成的`main.js`文件,确认它加载的是哪个场景。 §§相关问题§§: 1. 如何在Cocos Creator中设置初始启动场景? 2. Cocos Creator的main.js文件在项目中的具体作用是什么? 3. 如何动态切换Cocos Creator的启动场景? 4. Cocos Creator的场景资源(.fire文件)结构是如何组织的?</think>根据您提供的 JSON 数据结构,**这不是项目入口文件**,而是 Cocos Creator 场景的序列化数据(通常是 `.fire` 文件内容)。让我详细解释: ### 关键分析 1. **数据结构类型**: ```json [ { "__type__": "cc.SceneAsset", "_name": "entry", "scene": { "__id__": 1 } }, ... ] ``` 这是 Cocos Creator 场景的序列化格式,描述了场景中的节点、组件和层级关系。 2. **入口场景特征**: - 场景名为 `entry`(常见入口场景名) - 包含 Canvas 节点(所有 UI 的容器) - 有 Main Camera 组件 - 包含加载相关元素(如"加载中..."文本) 3. **但这不是项目入口文件**: - 项目真正的入口是 `main.js` 或 `application.js` - 这个文件描述的是场景内容,不是执行逻辑 ### 项目入口点解析 ```mermaid graph TD A[index.html] --> B[引擎初始化] B --> C[加载main.js] C --> D[加载首场景] D --> E[entry.fire] E --> F[创建场景节点] ``` ### 如何确认真正的入口点 1. **在 Cocos Creator 编辑器中**: - 打开 **项目设置** > **通用设置** - 查看 **默认场景** 设置(通常是 `entry`) 2. **在代码中**: 真正的入口在 `main.js` 中,类似这样: ```javascript // main.js cc.game.onStart = function() { // 1. 适配屏幕 cc.view.setDesignResolutionSize(750, 1334, cc.ResolutionPolicy.SHOW_ALL); // 2. 加载首场景 cc.director.loadScene("entry", () => { console.log("场景加载完成"); }); }; cc.game.run(); ``` 3. **在您的场景中**: 您提供的 JSON 中的这个节点很可能是加载界面: ```json { "__type__": "cc.Node", "_name": "New Node", "_id": "eb9Em8CmRJbLAnaaIWi6pZ", "_components": [ { "__id__": 28 }, { "__id__": 29 } ], "_children": [ { "__id__": 23 } ] } ``` ### 场景加载流程示例 ```javascript // 在 entry 场景中的组件脚本 cc.Class({ extends: cc.Component, onLoad() { // 加载进度条 this.progressBar = this.node.getChildByName("progress_bar").getComponent(cc.ProgressBar); // 资源加载 this.loadResources(); }, loadResources() { const resources = [ { type: "prefab", path: "prefabs/character" }, { type: "texture", path: "textures/background" }, { type: "audio", path: "sounds/bgm" } ]; resources.forEach((res, index) => { cc.resources.load(res.path, (err, asset) => { this.progressBar.progress = (index + 1) / resources.length; if (index === resources.length - 1) { this.onResourcesLoaded(); } }); }); }, onResourcesLoaded() { // 跳转到主场景 cc.director.loadScene("main"); } }); ``` ### 最佳实践建议 1. **创建明确的入口场景**: - 在 assets 中创建 `entry` 场景 - 添加加载界面和进度条 - 挂载加载脚本 2. **项目结构推荐**: ``` assets/ ├── scenes/ │ ├── entry.fire # 入口场景 │ ├── main.fire # 主场景 │ └── ... ├── scripts/ │ ├── EntryController.js # 入口场景逻辑 │ └── ... └── main.js # 项目入口文件 ``` 3. **入口场景控制器示例**: ```javascript // EntryController.js cc.Class({ extends: cc.Component, properties: { progressBar: cc.ProgressBar, statusLabel: cc.Label }, onLoad() { this.loadedCount = 0; this.totalToLoad = 3; // 需要加载的资源类型数量 this.loadConfigs(); this.loadTextures(); this.loadPrefabs(); }, updateProgress() { const progress = this.loadedCount / this.totalToLoad; this.progressBar.progress = progress; this.statusLabel.string = `加载中... ${Math.round(progress * 100)}%`; }, loadConfigs() { cc.resources.load("config/game", cc.JsonAsset, (err, asset) => { window.GameConfig = asset.json; this.onResourceLoaded(); }); }, loadTextures() { cc.assetManager.loadBundle("textures", (err) => { this.onResourceLoaded(); }); }, loadPrefabs() { cc.assetManager.loadBundle("prefabs", (err) => { this.onResourceLoaded(); }); }, onResourceLoaded() { this.loadedCount++; this.updateProgress(); if (this.loadedCount >= this.totalToLoad) { cc.director.loadScene("main"); } } }); ``` ### 如何设置入口场景 1. **在编辑器中**: - 打开 **项目设置** > **通用设置** - 在 **默认场景** 中选择您的入口场景(如 `entry`) 2. **在构建时**: - 构建面板中的 **初始场景** 设置会覆盖编辑器设置
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值