|
本帖最后由 qinqin 于 2015-6-6 02:52 編輯
好久沒(méi)寫(xiě)過(guò)筆記了,這次難得遇到那么全的補(bǔ)丁,從上層直通底層,又碰巧在跟蹤接口方面是我的弱項(xiàng),所以不分析一下太對(duì)不住自己了。。。
分析方法由上到下,從接口往底層跟。
接口的使用方法:
import android.view.DisplayManagerAw;
final DisplayManagerAw mDisplayManager = (DisplayManagerAw) mContext.getSystemService(Context.DISPLAY_SERVICE_AW);
int hdmiMode
hdmiMode = mDisplayManager.getSuitableHdmiNativeMode();
返回的hdmiMode是frameworks/base/core/java/android/view/DisplayManagerAw.java 對(duì)應(yīng)的模式,比如 5對(duì)應(yīng) 720P_60HZ, 10對(duì)應(yīng)1080P_60HZ
android/frameworks/base/core/java/android/view/DisplayManagerAw.java
public int getSuitableHdmiNativeMode()
{
int displayno = 0;
int i;
try
{ // 從最高分辨率遍歷所有Hdmi分辨率,測(cè)試哪一項(xiàng)是支持的。
for (i = DISPLAY_TVFORMAT_1080P_60HZ; i >= DISPLAY_TVFORMAT_480I; i--)
{
if (mService.isSupportHdmiNativeMode(i) == 1) // 這一步是關(guān)鍵
return i;
}
return -1;
}
catch (RemoteException ex)
{
return -1;
}
}
mService.isSupportHdmiNativeMode(i) 是 mService 里面的方法,所以找mService的類(lèi)。
private IDisplayManagerAw mService; ---> import android.view.IDisplayManagerAw;
---> android\frameworks\base\core\java\android\view\IDisplayManagerAw.aidl
關(guān)鍵代碼:
interface IDisplayManagerAw
{
// ... 省略
int isSupportHdmiNativeMode(int mode);
}
對(duì)應(yīng):android\frameworks\base\services\java\com\android\serv\DisplayManagerServiceAw.java
public class DisplayManagerServiceAw extends IDisplayManagerAw.Stub
{
private static final String TAG = "DisplayManagerServiceAw";
// 聲明本地方法,明顯是JNI方法
private native int nativeIsSupportHdmiNativeMode(int mode);
// ... 省略
public int isSupportHdmiNativeMode(int mode)
{
return nativeIsSupportHdmiNativeMode(mode);
}
}
去JNI目錄看看:
android\frameworks\base\services/jni/com_android_server_DisplayManagerServiceAw.cpp
namespace android
{
// ... 省略代碼
// 這里是具體實(shí)現(xiàn)
static jint isSupportHdmiNativeMode_native(JNIEnv *env, jobject clazz,int mode)
{ // Client ??? 發(fā)的是這個(gè)命令 DISPLAY_CMD_ISSUPPORTHDMINATIVEMODE
return SurfaceComposerClient::setDisplayProp(DISPLAY_CMD_ISSUPPORTHDMINATIVEMODE,mode,0,0);
}
static JNINativeMethod method_table[] = {
// ... 省略其他映射
// nativeIsSupportHdmiNativeMode() 實(shí)際調(diào)用的是 isSupportHdmiNativeMode_native()
{ "nativeIsSupportHdmiNativeMode", "(I)I", (void*)isSupportHdmiNativeMode_native },
};
int register_android_server_DisplayManagerServiceAw(JNIEnv *env)
{// 注意這里
jclass clazz = env->FindClass("com/android/server/DisplayManagerServiceAw");
if (clazz == NULL)
{
ALOGE("Can't find com/android/server/DisplayManagerServiceAw");
return -1;
}
// 注意這里,與 DisplayManagerServiceAw 有對(duì)應(yīng)關(guān)系
return jniRegisterNativeMethods(env, "com/android/server/DisplayManagerServiceAw",
method_table, NELEM(method_table));
}
};
看接收命令處實(shí)現(xiàn)了什么:
android/framework/native/include/ui/DisplayCommand.h
#define DISPLAY_CMD_ISSUPPORTHDMINATIVEMODE 30
看其實(shí)現(xiàn):android/framework/native/services/surfaceflinger/DisplayDispatcher.cpp
int DisplayDispatcher::setDispProp(int cmd,int param0,int param1,int param2)
{
switch(cmd)
{
// ... 省略
case DISPLAY_CMD_ISSUPPORTHDMINATIVEMODE:
return mDevice->issupporthdminativemode(mDevice,param0);
}
}
mDevice下的方法 issupporthdminativemode(),找 mDevice 對(duì)象
display_device_t* mDevice; 是在 DisplayDispatcher.h 中定義。
display_device_t 結(jié)構(gòu)體在 android/framework/native/include/hardware/display.h
struct display_device_t
{
// ... 省略
/*判斷某種hdmi制式是否被電視本地支持*/
int (*setdisplayareapercent)(struct display_device_t *dev, int displayno,int percent);
// ... 省略
};
是一個(gè)函數(shù)指針,看其指向了哪里:android/framework/native/include/hardware/display.cpp
android/device/softwinner/wing-common/hardware/libhardware/display/display.cpp
static int open_display(const struct hw_module_t* module, const char* name,
struct hw_device_t** device)
{
// ... 代碼省略
ctx->device.issupporthdminativemode = display_issupporthdminativemode;
// ... 代碼省略
return status;
}
// 實(shí)現(xiàn)的地方
int display_issupporthdminativemode(struct display_device_t *dev,int mode)
{
struct display_context_t* ctx = (struct display_context_t*)dev;
if(ctx)
{
if(ctx->mFD_disp)
{
unsigned long args[4];
args[0] = 0;
// 這里通過(guò)最開(kāi)始傳進(jìn)來(lái)的 DISPLAY_TVFORMAT_1080P_60HZ ...
// 實(shí)際上是查表 g_tv_para[] mode 跟 g_tv_para[ i].mode 比較。
// 將得到的 g_tv_para[ i].driver_mode 通過(guò) ioctl 傳遞給驅(qū)動(dòng)
args[1] = display_get_driver_tv_format(mode);
// 這里是往設(shè)備驅(qū)動(dòng)發(fā)送 DISP_CMD_HDMI_NATIVE_SUPPORT_MODE 命令
if(ioctl(ctx->mFD_disp,DISP_CMD_HDMI_NATIVE_SUPPORT_MODE,args))
{
return 1;
}
}
}
return 0;
}
// 這里就是它的實(shí)現(xiàn),從代碼上很容易看到是在查表
static int display_get_driver_tv_format(int format)
{
unsigned int i = 0;
for(i=0; i<sizeof(g_tv_para)/sizeof(struct tv_para_t); i++)
{
if(g_tv_para[ i].mode == format)
{
return g_tv_para[ i].driver_mode;
}
}
return -1;
}
// 這里就是這張表的樣子
struct tv_para_t
{
int type;// bit3:cvbs, bit2:ypbpr, bit1:vga, bit0:hdmi
int mode;
int width;
int height;
int valid_width;
int valid_height;
int driver_mode;
};
static struct tv_para_t g_tv_para[]=
{
{8, DISPLAY_TVFORMAT_NTSC, 720, 480, 690, 450, DISP_TV_MOD_NTSC},
{8, DISPLAY_TVFORMAT_PAL, 720, 576, 690, 546, DISP_TV_MOD_PAL},
{8, DISPLAY_TVFORMAT_PAL_M, 720, 576, 690, 546, DISP_TV_MOD_PAL_M},
{8, DISPLAY_TVFORMAT_PAL_NC, 720, 576, 690, 546, DISP_TV_MOD_PAL_NC},
{5, DISPLAY_TVFORMAT_480I, 720, 480, 690, 450, DISP_TV_MOD_480I},
{5, DISPLAY_TVFORMAT_576I, 720, 576, 690, 546, DISP_TV_MOD_576I},
{5, DISPLAY_TVFORMAT_480P, 720, 480, 690, 450, DISP_TV_MOD_480P},
{5, DISPLAY_TVFORMAT_576P, 720, 576, 690, 546, DISP_TV_MOD_576P},
{5, DISPLAY_TVFORMAT_720P_50HZ, 1280, 720, 1220, 690, DISP_TV_MOD_720P_50HZ},
{5, DISPLAY_TVFORMAT_720P_60HZ, 1280, 720, 1220, 690, DISP_TV_MOD_720P_60HZ},
{5, DISPLAY_TVFORMAT_1080I_50HZ, 1920, 1080, 1880, 1040, DISP_TV_MOD_1080I_50HZ},
{5, DISPLAY_TVFORMAT_1080I_60HZ, 1920, 1080, 1880, 1040, DISP_TV_MOD_1080I_60HZ},
{1, DISPLAY_TVFORMAT_1080P_24HZ, 1920, 1080, 1880, 1040, DISP_TV_MOD_1080P_24HZ},
{5, DISPLAY_TVFORMAT_1080P_50HZ, 1920, 1080, 1880, 1040, DISP_TV_MOD_1080P_50HZ},
{5, DISPLAY_TVFORMAT_1080P_60HZ, 1920, 1080, 1880, 1040, DISP_TV_MOD_1080P_60HZ},
{1, DISPLAY_TVFORMAT_1080P_25HZ, 1920, 1080, 1880, 1040, DISP_TV_MOD_1080P_25HZ},
{1, DISPLAY_TVFORMAT_1080P_30HZ, 1920, 1080, 1880, 1040, DISP_TV_MOD_1080P_30HZ},
{1, DISPLAY_TVFORMAT_1080P_24HZ_3D_FP, 1920, 1080, 1920, 1080, DISP_TV_MOD_1080P_24HZ_3D_FP},
{1, DISPLAY_TVFORMAT_720P_50HZ_3D_FP, 1280, 720, 1280, 720, DISP_TV_MOD_720P_50HZ_3D_FP},
{1, DISPLAY_TVFORMAT_720P_60HZ_3D_FP, 1280, 720, 1280, 720, DISP_TV_MOD_720P_60HZ_3D_FP},
{2, DISPLAY_VGA_H1680_V1050, 1668, 1050, 1668, 1050, DISP_VGA_H1680_V1050},
{2, DISPLAY_VGA_H1440_V900, 1440, 900, 1440, 900, DISP_VGA_H1440_V900},
{2, DISPLAY_VGA_H1360_V768, 1360, 768, 1360, 768, DISP_VGA_H1360_V768},
{2, DISPLAY_VGA_H1280_V1024, 1280, 1024, 1280, 1024, DISP_VGA_H1280_V1024},
{2, DISPLAY_VGA_H1024_V768, 1024, 768, 1204, 768, DISP_VGA_H1024_V768},
{2, DISPLAY_VGA_H800_V600, 800, 600, 800, 600, DISP_VGA_H800_V600},
{2, DISPLAY_VGA_H640_V480, 640, 480, 640, 480, DISP_VGA_H640_V480},
{2, DISPLAY_VGA_H1440_V900_RB, 1440, 900, 1440, 900, DISP_VGA_H1440_V900},
{2, DISPLAY_VGA_H1920_V1080, 1920, 1080, 1920, 1080, DISP_VGA_H1920_V1080},
{2, DISPLAY_VGA_H1280_V720, 1280, 720, 1280, 720, DISP_VGA_H1280_V720},
};
// 繼續(xù)分析 先看 DISP_CMD_HDMI_NATIVE_SUPPORT_MODE 命令的定義
android/device/softwinner/common/hardware/include/drv_display.h
typedef enum tag_DISP_CMD
{
// ... 省略
//----hdmi----
DISP_CMD_HDMI_ON = 0x1c0,
DISP_CMD_HDMI_OFF = 0x1c1,
DISP_CMD_HDMI_SET_MODE = 0x1c2,
DISP_CMD_HDMI_GET_MODE = 0x1c3,
DISP_CMD_HDMI_SUPPORT_MODE = 0x1c4,
DISP_CMD_HDMI_GET_HPD_STATUS = 0x1c5,
DISP_CMD_HDMI_SET_SRC = 0x1c6,
DISP_CMD_HDMI_NATIVE_SUPPORT_MODE = 0x1c7, <----
// ... 省略
}
接下來(lái)分析Linux 設(shè)備驅(qū)動(dòng)接到這個(gè)命令之后會(huì)怎么處理。
lichee/linux-3.4/include/linux/drv_display.h
typedef enum tag_DISP_CMD
{
// ... 省略
//----hdmi----
DISP_CMD_HDMI_ON = 0x1c0,
DISP_CMD_HDMI_OFF = 0x1c1,
DISP_CMD_HDMI_SET_MODE = 0x1c2,
DISP_CMD_HDMI_GET_MODE = 0x1c3,
DISP_CMD_HDMI_SUPPORT_MODE = 0x1c4,
DISP_CMD_HDMI_GET_HPD_STATUS = 0x1c5,
DISP_CMD_HDMI_SET_SRC = 0x1c6,
DISP_CMD_HDMI_NATIVE_SUPPORT_MODE = 0x1c7, <----
}__disp_cmd_t;
看看接收到這個(gè)命令之后做了些什么: 這里是驅(qū)動(dòng)程序
linux-3.4/drivers/video/sun7i/disp/dev_disp.c
long disp_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
// ... 省略一些代碼
switch(cmd)
{
// ... 省略一大波 case 代碼
// 這里調(diào)用了 BSP_disp_hdmi_check_native_support_mode()
case DISP_CMD_HDMI_NATIVE_SUPPORT_MODE:
ret = BSP_disp_hdmi_check_native_support_mode(ubuffer[0], ubuffer[1]);
break;
// ... 省略一大波 case 代碼
default:
break;
}
return ret;
}
定義在 linux-3.4\drivers\video\sun7i\disp\de_bsp\de\disp_display.h
extern __s32 BSP_disp_hdmi_check_native_support_mode(__u32 sel, __u8 mode);
看看 BSP_disp_hdmi_check_native_support_mode() 是怎么實(shí)現(xiàn)的
linux-3.4/drivers/video/sun7i/disp/de_bsp/de/disp_hdmi.c
__s32 BSP_disp_hdmi_check_native_support_mode(__u32 sel, __u8 mode)
{
if(gdisp.init_para.hdmi_native_mode_support)
{
// 如果該函數(shù)指針有被賦值則調(diào)用 尼瑪... 又來(lái)一次...
return gdisp.init_para.hdmi_native_mode_support(mode);
}
else
{
DE_WRN("hdmi_native_mode_support is NULL\n");
return DIS_NOT_SUPPORT;
}
}
gdisp的結(jié)構(gòu)體定義在這:linux-3.4\drivers\video\sun7i\disp\de_bsp\de\disp_display.h
typedef struct
{
__disp_bsp_init_para init_para;//para from driver
// ... 省略
}__disp_dev_t;
extern __disp_dev_t gdisp;
__disp_bsp_init_para 結(jié)構(gòu)體定義
typedef struct
{
// ... 省略
// 這里新增了一個(gè)成員
__s32 (*hdmi_native_mode_support)(__disp_tv_mode_t mode);
}__disp_bsp_init_para;
看 gdisp.init_para 初始化的地方:linux-3.4/drivers/video/sun7i/disp/de_bsp/de/disp_hdmi.c
__s32 BSP_disp_set_hdmi_func(__disp_hdmi_func * func)
{
// ... 省略代碼
// ... 無(wú)語(yǔ)了,又來(lái)...
gdisp.init_para.hdmi_native_mode_support = func->hdmi_native_mode_support;
return DIS_SUCCESS;
}
hdmi_native_mode_support()是 fun的成員,先看看 fun 的結(jié)構(gòu) __disp_hdmi_func 這個(gè)結(jié)構(gòu)體。
lichee/linux-3.4/include/linux/drv_display.h
typedef struct
{
// ... 省略
// 新增的
__s32 (*hdmi_native_mode_support)(__disp_tv_mode_t mode);
}__disp_hdmi_func;
好了,接著看誰(shuí)調(diào)用了這個(gè) BSP_disp_set_hdmi_func(),往上跟,看func到底指向了哪個(gè)函數(shù)
linux-3.4\drivers\video\sun7i\disp\Dev_disp.c
__s32 disp_set_hdmi_func(__disp_hdmi_func * func)
{
BSP_disp_set_hdmi_func(func);
return 0;
}
看誰(shuí)調(diào)用了 disp_set_hdmi_func()
linux-3.4\drivers\video\sun7i\hdmi\drv_hdmi.c
__s32 Hdmi_init(void)
{
// ... 省略代碼
// 終于找到了,在這里賦值
disp_func.hdmi_native_mode_support = Hdmi_native_mode_support;
// 在這里調(diào)用了 disp_set_hdmi_func()
disp_set_hdmi_func(&disp_func);
return 0;
}
看看 Hdmi_native_mode_support() 的實(shí)現(xiàn)
__s32 Hdmi_native_mode_support(__disp_tv_mode_t mode)
{
__u32 hdmi_mode;
switch(mode)
{
case DISP_TV_MOD_480I:
hdmi_mode = HDMI1440_480I;
break;
case DISP_TV_MOD_576I:
hdmi_mode = HDMI1440_576I;
break;
case DISP_TV_MOD_480P:
hdmi_mode = HDMI480P;
break;
case DISP_TV_MOD_576P:
hdmi_mode = HDMI576P;
break;
case DISP_TV_MOD_720P_50HZ:
hdmi_mode = HDMI720P_50;
break;
case DISP_TV_MOD_720P_60HZ:
hdmi_mode = HDMI720P_60;
break;
case DISP_TV_MOD_1080I_50HZ:
hdmi_mode = HDMI1080I_50;
break;
case DISP_TV_MOD_1080I_60HZ:
hdmi_mode = HDMI1080I_60;
break;
case DISP_TV_MOD_1080P_24HZ:
hdmi_mode = HDMI1080P_24;
break;
case DISP_TV_MOD_1080P_50HZ:
hdmi_mode = HDMI1080P_50;
break;
case DISP_TV_MOD_1080P_60HZ:
hdmi_mode = HDMI1080P_60;
break;
case DISP_TV_MOD_1080P_25HZ:
hdmi_mode = HDMI1080P_25;
break;
case DISP_TV_MOD_1080P_30HZ:
hdmi_mode = HDMI1080P_30;
break;
case DISP_TV_MOD_1080P_24HZ_3D_FP:
hdmi_mode = HDMI1080P_24_3D_FP;
break;
case DISP_TV_MOD_720P_50HZ_3D_FP:
hdmi_mode = HDMI720P_50_3D_FP;
break;
case DISP_TV_MOD_720P_60HZ_3D_FP:
hdmi_mode = HDMI720P_60_3D_FP;
break;
default:
hdmi_mode = HDMI720P_50;
break;
}
// 竟然。。。。。。
return Hdmi_hal_native_mode_support(hdmi_mode);
}
繼續(xù)分析 Hdmi_hal_native_mode_support()
聲明在此:
linux-3.4\drivers\video\sun7i\hdmi/hdmi_hal.h
extern __s32 Hdmi_hal_native_mode_support(__u32 mode);
實(shí)現(xiàn)在此:
linux-3.4\drivers\video\sun7i\hdmi\aw\hdmi_hal.c
__s32 Hdmi_hal_native_mode_support(__u32 mode)
{
if(Hpd_Check() == 0)
{
return 0;
}
else
{
int counter = 1000;
// hdmi_state < 5 ??? 應(yīng)該是等待 接收 電視中傳過(guò)來(lái)的數(shù)據(jù) 里面包含有分辨率這些信息
while(hdmi_state < HDMI_State_Wait_Video_config && counter--)
{
hdmi_delay_ms(1);
}
// 這里實(shí)際上是查詢(xún)上層傳遞下來(lái)的分辨率是否被支持
return Device_Native_Support_VIC[mode];
}
}
先看看數(shù)組的定義:
linux-3.4\drivers/video/sun7i/hdmi/aw/hdmi_core.h
extern __u8Device_Native_Support_VIC[512];
linux-3.4\drivers/video/sun7i/hdmi/aw/hdmi_core.c
__u8 Device_Native_Support_VIC[512];
賦值在這里:
linux-3.4\drivers\video\sun7i\hdmi\aw\hdmi_edid.c
// 看樣子這里還不是關(guān)鍵
__s32 Parse_VideoData_Block(__u8 *pbuf,__u8 size)
{
int i=0;
while(i<size)
{
Device_Support_VIC[pbuf[ i] &0x7f] = 1;
if(pbuf[ i] &0x80)
{
__inf("Parse_VideoData_Block: VIC %d(native) support\n", pbuf[ i]&0x7f);
// 這里是記錄對(duì)應(yīng)的電視機(jī)上支持哪些分辨率 1 表示支持
Device_Native_Support_VIC[pbuf[ i] &0x7f] = 1;
}
else
{
__inf("Parse_VideoData_Block: VIC %d support\n", pbuf[ i]);
}
i++;
}
return 0;
}
通過(guò)搜索,看看哪個(gè)地方調(diào)用了該函數(shù):
linux-3.4\drivers\video\sun7i\hdmi\aw\hdmi_edid.c
這個(gè)函數(shù)涉及到芯片的HDMI控制器,比較復(fù)雜,要是真的深入分析的話(huà),估計(jì)要花上好一段時(shí)間.
__s32 ParseEDID(void)
{
//collect the EDID ucdata of segment 0
__u8 BlockCount ;
__u32 i,offset ;
__inf("ParseEDID\n");
memset(Device_Support_VIC,0,sizeof(Device_Support_VIC));
// 這里清空了該數(shù)組,也就是說(shuō),默認(rèn)全為 0
memset(Device_Native_Support_VIC,0,sizeof(Device_Native_Support_VIC));
memset(EDID_Buf,0,sizeof(EDID_Buf));
isHDMI = 0;
YCbCr444_Support = 0;
DDC_Init(); // 初始化DDC 、使能、復(fù)位DDC、設(shè)置DDC時(shí)鐘
GetEDIDData(0, EDID_Buf);// 讀取數(shù)據(jù)
// 校驗(yàn)數(shù)據(jù)
if( EDID_CheckSum(0, EDID_Buf) != 0)
{
return 0;
}
// 校驗(yàn)數(shù)據(jù)頭
if( EDID_Header_Check(EDID_Buf)!= 0)
{
return 0;
}
// 檢查版本
if( EDID_Version_Check(EDID_Buf)!= 0)
{
return 0;
}
Parse_DTD_Block(EDID_Buf + 0x36);
Parse_DTD_Block(EDID_Buf + 0x48);
BlockCount = EDID_Buf[0x7E];
if( BlockCount > 0 )
{
if ( BlockCount > 4 )
{
BlockCount = 4 ;
}
// 讀取所有數(shù)據(jù)塊
for( i = 1 ; i <= BlockCount ; i++ )
{
GetEDIDData(i, EDID_Buf) ;
if( EDID_CheckSum(i, EDID_Buf)!= 0)
{
return 0;
}
if((EDID_Buf[0x80*i+0]==2)/*&&(EDID_Buf[0x80*i+1]==1)*/)
{
if( (EDID_Buf[0x80*i+1]>=1))
{
if(EDID_Buf[0x80*i+3]&0x20)
{
YCbCr444_Support = 1;
__inf("device support YCbCr44 output\n");
if(rgb_only == 1)
{
__inf("rgb only test!\n");
YCbCr444_Support = 0;
}
}
}
offset = EDID_Buf[0x80*i+2];
if(offset > 4)//deal with reserved data block
{
__u8 bsum = 4;
while(bsum < offset)
{
__u8 tag = EDID_Buf[0x80*i+bsum]>>5;
__u8 len = EDID_Buf[0x80*i+bsum]&0x1f;
if( (len >0) && ((bsum + len + 1) > offset) )
{
__inf("len or bsum size error\n");
return 0;
}else
{
if( tag == 1)//ADB
{
Parse_AudioData_Block(EDID_Buf+0x80*i+bsum+1,len);
}
else if( tag == 2)//VDB
{// 記錄下來(lái)
Parse_VideoData_Block(EDID_Buf+0x80*i+bsum+1,len);
}
else if( tag == 3)//vendor specific
{
Parse_HDMI_VSDB(EDID_Buf+0x80*i+bsum+1,len);
}
}
bsum += (len +1);
}
}else
{
__inf("no data in reserved block%d\n",i);
}
if(offset >= 4)//deal with 18-byte timing block
{
while(offset < (0x80-18))
{
Parse_DTD_Block(EDID_Buf + 0x80*i + offset);
offset += 18;
}
}else
{
__inf("no datail timing in block%d\n",i);
}
}
}
}
return 0 ;
}
最后總結(jié):
當(dāng)HDMI接入時(shí),會(huì)通過(guò)HDMI控制器得到電視的一些信息并且保存起來(lái),而 getSuitableHdmiNativeMode() 這個(gè)接口
實(shí)際上只是在已被保存下來(lái)的信息中查詢(xún)得到該電視最高支持的分辨率。
不得不吐槽下,為了實(shí)現(xiàn)這個(gè)功能,繞來(lái)繞去的。。。
|
|