Egret QQ玩一玩适配【踩坑日记】
需要申明一点,这是我接过最坑的渠道了,各种神奇的问题,首先是接口比较奇怪而且新旧版本搞得很混乱,其次是平台底层实现性能差而且很多限制。此外,这里需要理清楚一个概念:QQ 玩一玩
和 QQ 玩吧
并非同一个东西,QQ 玩一玩也叫 QQ 轻游戏
或 厘米游戏
,是基于 bricks 引擎实现的。
技术限制
玩一玩平台不支持基于DOM Document对象的HTML元素处理
玩一玩平台不支持皮肤的远程加载,所有皮肤必须声明到egret项目的
default.thm.json
文件当中。不允许动态执行代码能力。
不支持的功能:
渲染相关
不规则遮罩
动态截屏
位图缓存
触摸相关
像素级碰撞检测
点击穿透
调试相关
脏矩形调试显示
fps监视器
屏幕调试日志
适配步骤
参考 Egret 的官方文档 ,我们当前使用的引擎版本为 5.2.6
,直接使用 egret run –target bricks
命令生成一个 Xcode 工程。
假如 iOS 系统是最新的 11.4.1 ,则需要安装最新版本的 Xcode (这也要求 Mac 是 10.13.2 或更新的系统)
由于我们游戏中使用了 fairygui 来制作 UI,在厘米游戏这里需要使用二进制的导出形式,原来的 xml 导出无法加载出来。即全局设置中:扩展名改为 zip ,然后勾选 使用二进制格式
。
资源管理
玩一玩与微信在资源限制上是相似:
微信要求首包 4M 以内,其他资源放在 CDN;
手 Q 玩一玩要求首包 10M 以内,其他资源放在 CDN。
打包发布
打包:
参考官方文档 文件打包与运行 的相关规则,将代码和部分资源打成一个 zip 包,命名为
cmshow_game_xxx.zip
,其中 xxx 是游戏的 gameId 。测试:
从手 Q7.6.0 开始 QQ 轻游戏不再提供特殊版本手 Q,开发者统一通过上传脚本至管理端进行测试。
我们游戏使用的是 egret 引擎,在 Egret Launcher 中可以直接发布 QQ玩一玩
的版本包,其发布出来的是一个 Xcode 工程,而工程中 PublicBrickEngineGame\Res
目录下便是最终上传给平台的游戏代码包。除了首次发布需要用到 Egret Launcher ,之后更新代码直接使用 egret publish --target bricks
即可。
常见报错
2018-09-05 11:32:37.055825+0800 PublicBrickEngineGame[76458:6693859] [MC] Lazy loading NSBundle MobileCoreServices.framework
2018-09-05 11:32:37.056608+0800 PublicBrickEngineGame[76458:6693859] [MC] Loaded MobileCoreServices.framework
level=1, code=0, info1=SetKeepScreenOn , info2=, info3=
level=0, code=0, info1=brick_log, info2=filemanage.js is loaded, info3=
level=0, code=0, info1=brick_log, info2=Load Canvas.js succeed., info3=
level=0, code=0, info1=brick_log, info2=Load Sprite.js succeed., info3=
level=0, code=0, info1=BK.File.bkIsFileExist! error = No such file or directory, info2=, info3=
level=1, code=0, info1=brick_log, info2=Xcode 环境, info3=
level=0, code=0, info1=brick_log, info2=Load SandBoxCanvas.js succeed., info3=
level=1, code=0, info1=brick_log, info2= Using default export (`import mobx from 'mobx'`) is deprecated and won’t work in mobx@4.0.0
Use `import * as mobx from 'mobx'` instead, info3=
level=1, code=0, info1=brick_log, info2= [11:32:40+239ms] Failed getting local storage item (key: MusicEnabled)., info3=
level=1, code=0, info1=brick_log, info2= [11:32:40+241ms] Failed getting local storage item (key: SoundEnabled)., info3=
level=1, code=-1, info1=BK.TickerPlt.-[TickerPlatform onFrame:]! isAppActive = 0, info2=, info3=
关键问题在于最后一句 BK.TickerPlt.-[TickerPlatform onFrame:]! isAppActive = 0
直接黑屏 QQ玩一玩XCode运行模拟器黑屏,求助 是 egret 的bug ,更新引擎只 5.2.8,然后在 js/main.min.js
末尾加入如下内容:
;global.Main = Main;
加载资源卡住:
info2=BK.MQQ.SsoRequest.callback errCode:1
这是通过 Http 去加载图片时出现的,这是表示索取的资源地址不存,检测 CDN 资源即可。
WebSocket
这是需要使用 BK.WebSocket 进行重写的部分,使用普通的 websocket 库的话会在获取 websocket 的 readyState 时报错。
假如是断开网络:
会先有 BK.Socket.DisconnectEvent
事件,然后触发 onError
回调,并带有错误码和错误信息:
错误码:1006
错误信息:abnormal closure
息屏 > 5 分钟,服务器会主动断开连接,也会在 websockt 的 onError 会收到错误码:
错误码:1006
错误信息:abnormal closure
假如是服务器通过直接断开 TCP 实现主动将玩家踢下线的话,需要主动掉一次 close 再创建新的连接,否则会一直卡在 connecting 状态没办法切换到 connected 状态。
设计断线重连方案:
不做自动重连,只有发送网络请求时才检测当前网络的可用性并进行重连恢复
当出现异常状况只记录网络不可用状态
假如网络正常但发包等待回包超时,需要弹窗提示
被踢下线也应该有弹窗提示
恢复网络前先检测网络是否可用,假如不可用需要弹窗提示
恢复失败需要弹窗提示
另外,假如网络协议发送底层实现是队列式异步发送而非阻塞的话,最好把登录和重登这些与业务逻辑无关的协议使用单独阻塞的通道去发送,不要与业务队列混到一起,否则处理重连时会很混乱。
数据转化
一般的平台都是使用 ArrayBuffer 来存储二进制数据,而厘米秀使用了自己的一个特殊的结构 BK.Buff ,所以需要两个接口来实现:
ArrayBuffer 转 BK.Buff
// ArrayBuffer 转为 BK.Buff public getBKBuffFromArrayBuffer(arrayBuffer: ArrayBuffer) {let len = arrayBuffer.byteLength;let bkBuff = new BK.Buffer(len, true);let uint = new Uint8Array(arrayBuffer);for (let i = 0; i < len; i++) {bkBuff[i] = bkBuff.writeUint8Buffer(uint[i]);}return bkBuff; }
BK.Buff 转 ArrayBuffer
// BK.Buff 转为 ArrayBuffer public getArrayBufferFromBKBuff(bkBuff: BK.Buffer) {let len = bkBuff.bufferLength();let array = new Uint8Array(len); for (let i = 0; i < len; i++) {array[i] = bkBuff.readUint8Buffer();}return array.buffer; }
调试
Xcode 调试
使用 Egret Launcher 工具打出的包本身就是一个 Xcode 工程,可以在 Mac 下的 Xcode 打开,然后安装到 iOS 手机上运行游戏。
除了可以直接在 Xcode 中看到执行的日志,还可以直接在 Safair 上远程调试游戏,具体步骤参考:Xcode工程下使用Safari远程调试游戏 。上面的方法也只能解决一些类似资源加载和存储、网络和音频,但关于平台登录相关的就测试不了,因为通过 openId 获取 openKey 会返回错误码 1;
VSCode 调试插件
为此,厘米游戏官方也推出了替代的调试方案,即借助 Visual Studio Code 和额外的插件,实现在 Windows 和 Mac 下实现调试。参考 7.3 调试工具 在 VS Code 中安装对应的调试插件。
在 VS Code 中打开 PublicBrickEngineGame\Res
目录,然后打开 main.js
脚本(这是厘米游戏的启动入口),在编辑窗口的右上角会出现 应用管理
、调试
、部署
和 配置
4 个按钮。根据提示配置相应的 gameId 、appId 和 appKey 即可开始调试。
然而,使用 node.js 版本出现 使用检查器协议进行调试,因为无法确定 Node.js 版本 (Error: connect ECONNREFUSED 127.0.0.1:2507)
错误,看了 pre-attach.js
源码发现是调用 adb reverse
的时候报错,那么问题就出在 adb 工具,结果发现原本使用的 adb 工具的版本为 1.0.31
,换成 1.0.39
的版本就正常了。
而使用 python 版本则打开了 debugBrick.apk ,而且按照插件中的 pre-attach.py
这个任务 python 脚本执行到最后的 try to attach brick ...
日志,但是启动便黑屏了,也没有任何日志输出。
常见问题
1.账号权限的问题
由于调试工具黑屏无法显示界面内容,所以只能选择上传到后台再扫码测试了,结果出现了:
使用手机扫码之后,提示 该游戏已下架,无法继续玩耍
,原因是管理后台开发管理处提示: Warning: appid没有通过审核,不可申请上架;
。
2.启动报错
官方客服提示只能用 Android 手机进行测试,扫码提示 启动失败,请稍后重试哦~
,然后在手机文件管理器中打开 内部存储/tencent/MobileQQ/.apollo/game
目录下(是隐藏目录,需要设置显示隐藏文件或文件夹才能查看),查看是否有以游戏 GameId 取名的目录和 .zip
包,这就是游戏包体保存的文件夹。
平台包内文件的缓存路径:
GameRes://resource/
和GameSandBox://
实际上也都在game/xxx
目录下。
最后,发现使用开发者主账号一直启动不了,而使用另一个 QQ 添加到白名单反而可以,十分奇葩的问题。
3.adb 查看报错日志
确保 adb devices -l
下可以看到手机设备,然后使用 adb logcat
来捕获游戏日志,需要使用 sava_native_log
标签来进行过滤:
adb shell "logcat |grep sava_native_log"
其中 sava_native_log
是玩一玩日志输出的标识。或者,直接写入到文件中:
adb logcat -v time process > log.txt
需要注意,使用
BK.Script.log
打印的日志在这里是不会输出的,只会输出 console.log 的日志。
4.Android 扫码启动卡在 99%
通常是由于代码报错导致的,经过一下午的排查才发现是少了 promise.js
的引入(参考:玩一玩游戏FAQ)
5.动画很鬼畜或抖动,遮罩不生效
这是因为打包的时候,默认帧率是 30 :
egret.runEgret({renderMode: window.renderMode,frameRate: 30,contentWidth: 640,contentHeight: 1136,entryClassName: "Main",scaleMode: "showAll",orientation: "auto",background: 0x888888}
);
应该改为 60 帧,然后 UI 适配方案根据自己游戏的情况调整:
egret.runEgret({renderMode: window.renderMode,frameRate: 60, // 默认是 30 帧,会有各种鬼畜的问题(动画抽搐、遮罩变黑)contentWidth: 720, // 默认是 640 X 1136contentHeight: 1280,entryClassName: "Main",scaleMode: "fixedWidth", // 默认 showAllorientation: "portrait", // 默认 autobackground: 0x888888}
);
6.扫码弹窗提示报错
在扫码之后弹窗报错信息如下:
[game:3958]Execute JS Error!
[TypeError:undefined is not an object (evaluating 'e.prototype')]:line = 13,column = 20,
可以直接将 内部存储/tencent/MobileQQ/.apollo/game/游戏GameId/main.js
脚本复制到打包前的工程中,替换原本的 main.js ,使用 VS Code 调试工具调试,可以看到执行输出:
*********************************出现未捕获异常***********************************
11-01 10:38:40.875 17189 17213 I System.out: sava_native_log [printNativeLog], level:1,code:1,info1:jswrapper.__onMessageCallback! brick_exception ERROR: Uncaught TypeError: Cannot read property 'prototype' of undefined, /sdcard/tencent/MobileQQ/Test/main.js:0:0
**********************************异常信息结束***********************************
*********************************出现未捕获异常***********************************
11-01 10:38:40.875 17189 17213 I System.out: brick_exception STACK:
11-01 10:38:40.875 17189 17213 I System.out: brick_exception [0]__extends@/sdcard/tencent/MobileQQ/Test/main.js:13
**********************************异常信息结束***********************************
定位到问题出现在 main.js 中的第 13 行,即 r.prototype = e.prototype, t.prototype = new r();
这一行,那么加一个 try catch 捕获导致异常的调用:
var window = this;
var global = global || this;
global.bricks = {};
this.navigator = { userAgent: 'bricks' };
this.setTimeout = this.setTimeout || function () {
};
var __extends = function (t, e) {function r() {this.constructor = t;}for (var i in e)e.hasOwnProperty(i) && (t[i] = e[i]);try{r.prototype = e.prototype, t.prototype = new r();}catch(e){console.log('err = '+t.name);console.log('err = '+e.stack);}
};
再次调试,发现报错调用堆栈信息:
[11-1-2018 11:19:19] [BRICK_LOG] LEVEL = 1, ERRCODE = 0, INFO = err = AdapterTexture
[11-1-2018 11:19:19] [BRICK_LOG] LEVEL = 1, ERRCODE = 0, INFO = err = TypeError: Cannot read property 'prototype' of undefinedat __extends (/sdcard/tencent/MobileQQ/Test/main.js:14:20)at /sdcard/tencent/MobileQQ/Test/main.js:66122:9at window.spine.window.spine (/sdcard/tencent/MobileQQ/Test/main.js:66138:6)at /sdcard/tencent/MobileQQ/Test/main.js:66267:2at /sdcard/tencent/MobileQQ/Test/main.js:158576:2
我出现此问题的原因是:修改了 spine 运行时的引入方式,之前的做法是源码引入跟工程一起打包,后来把它单独抽出来,以库的方式引入,从而导致了此问题。
7.游戏内所有文字往下偏移
这是因为 egret.brick.js
在将 TextField 转为 BKTextField 时计算高度有问题,修改如下:
// 修改前
BKCanvasRenderer.prototype.renderText = function (node, context) {...context.fillText(text, x + context.$offsetX, -y + context.$offsetY + node.height);
}
// 修改后
BKCanvasRenderer.prototype.renderText = function (node, context) {...context.fillText(text, x + context.$offsetX, -y + context.$offsetY + node.height + context.lineWidth + 2); // 解决文字整体下移的问题
}
8.邀请类分享
分享可用的接口有两个:
share
shareToArk
给分享的 extendInfo 塞入扩展信息,然后在 onLoad 和 onEnterforeground 监听中去通过 GameStatusInfo.gameParam
获取这个扩展信息。
9.玩家头像
与其他渠道不同,玩一玩获取玩家头像的方式并非在登录渠道时直接返回头像的网络地址,而是需要使用玩家的 openId 通过 BK.MQQ.Account.getHeadEx
接口去下载头像,下载头像保存的路径是:"GameSandBox://_head/" + openId + ".jpg"
,大概逻辑如下:
let openId = GameStatusInfo.openId;
let tex = await new Promise<egret.Texture>(resolve => {let absolutePath = "GameSandBox://_head/" + openId + ".jpg";let isExit = BK.FileUtil.isFileExist(absolutePath);gLog(absolutePath + " is exit :" + isExit);//如果指定目录中存在此图像就直接显示否则从网络获取if (isExit) {loadTexture(absolutePath).then(tex => {resolve(tex);});} else {BK.MQQ.Account.getHeadEx(GameStatusInfo.openId, function (oId, imgPath) {gLog("openId:" + oId + " imgPath:" + imgPath);loadTexture(imgPath).then(tex => {resolve(tex);});});}
})
return tex;
function loadTexture(path: string){let texture = new egret.Texture();let imageData = new egret.BitmapData(path);gLog('图片:' + path + '加载成功');texture._setBitmapData(imageData);return texture;
}
其次,圆形头像裁剪似乎有问题,大概情况是:
使用 Shape 作为 mask 的或直接导致裁剪后的图片不显示;
使用不透明的圆形图片作为 mask 的话,有些设备上能裁剪成功,但有些设备上也会出现图片直接显示不出来的情况。
因此,玩一玩平台中使用玩家头像的成本比较高:一方面是不支持额外的裁剪,只能使用方形的;另一方面是需要先下载头像到本地才能展示。
性能测试
玩一玩提交审核时需要提供自测报告,可以借助 wetest 工具来测试,Android 手机下载 WeTest 助手 ,然后在助手中注册一个账号,选择 通用性能分析
,选择 QQ 应用,然后点击 开始测试
然后在 QQ 中打开游戏开始玩游戏即可。(前提是手机已经 root 了,假如是非 root 的手机,则还需要借助 PC 上的 cube-pc 助手),手机用 USB 线连着 pc 助手进行测试。但是,非 root 的手机无法测试 Mono 内存和 Drawcall)。
此外需要在设置中打开弹出悬浮框的权限
测试报告需要在网页打开 通用测试报告 ,登录与助手一致的 WeTest 账号,即可查看测试数据。
性能瓶颈
FPS (帧率)
问题:首先是 FPS 帧率方面,在 QQ 玩一玩平台,使用 egret 引擎开发的游戏中假如使用 spine 动画会有明显的掉帧现象。
解决方案:将所有的 spine 动画通过 DragonBones Pro 转为二进制格式的龙骨动画,掉帧的问题基本上能够解决。
内存和 DrawCall
在 WeTest 上想要测这两项数据需要 root 手机才能获得
2018.11.29 补充
BK.localStorage 有坑
之前很作死地使用玩一玩新版 API 引入的 BK.localStorage
来存储本地数据,结果偶现的在启动时弹窗报错:
Error:Storage setItem failed, item bytes is too large
看报错内容会以为是往 localStorage 中写入了太大的数据导致无法读取,其实不然,是平台底层实现的问题导致启动时初始化已写入的 localStorage 数据解析失败导致的。而且,官方技术人员反馈这个报错还是概率性的而非必现。
当然,也有必现的方法:写入带有三字节的内容,例如:emoji 表情
解决方案:
可用的解决方案有两种
自己通过读写本地缓存文件的方式实现一套 localStorage ,假如还想实现多账号数据隔离,以用户的 openId 作为缓存文件的名称即可;
在数据写入时进行一次 base64 编码,取出时再进行 base64 解码,也能解决此问题。
这里提供第一个方案的代码:
// 确保多用户隔离const cacheFilePath = `GameSandBox://` + GameStatusInfo.openId;let storage: any;const qqfm = QQFileManager.instance;function GetStorageItem(key: string): any {if (!storage) {ReadCacheFile();}return storage[key];}
function SetStorageItem(key: string, value: string) {storage[key] = value;SaveCacheFile();}
function ReadCacheFile() {if (qqfm.exists(cacheFilePath)) {let txt = qqfm.readFile(cacheFilePath, true) as string;if (txt) {console.log('storage 数据:', txt);storage = JSON.parse(txt);} else {storage = {};}} else {storage = {};}}
function SaveCacheFile() {if (!qqfm.exists(cacheFilePath)) {console.log('storage 缓存文件:' + cacheFilePath + ' 不存在');}if (!qqfm.writeStringToFile(cacheFilePath, JSON.stringify(storage))) {console.error('写 storage 缓存文件:' + cacheFilePath + ' 失败');}}
function RemoveStorageItem(key: string) {delete storage[key];SaveCacheFile();}
function ClearStorage() {storage = {};if (qqfm.exists(cacheFilePath)) {qqfm.removeFile(cacheFilePath);console.log('storage 缓存文件:' + cacheFilePath + ' 已清理');}}
LocalStorage.getItem = function <T>(key: string, defValue?: T): T {try {let rawValue = GetStorageItem(key);if (!rawValue) {rawValue = JSON.stringify(defValue);if (!rawValue || rawValue.length == 0) {console.warn('qq LocalStorage set "" unable');} else {SetStorageItem(key, rawValue);}}// rawValue = rawValue.replace(/@\^@/g, '"');return JSON.parse(rawValue);} catch (error) {console.error(`Failed getting local storage item (key: ${key}).`);return defValue;}}
LocalStorage.setItem = function (key: string, value: any) {try {let valueStr = JSON.stringify(value);// valueStr = valueStr.replace(/"/g, '@^@');if (valueStr.length >= 4194304 || valueStr.length < 0) {gWarn('qq LocalStorage.setItem fail, value to large');return;}if (!valueStr || valueStr.length == 0) {console.warn('qq LocalStorage set "" unable');return;}SetStorageItem(key, valueStr);} catch (error) {console.error(`Failed setting local storage item (key: ${key}).`);}}
LocalStorage.removeItem = function (key: string) {return RemoveStorageItem(key);}
LocalStorage.clear = function () {return ClearStorage();}
第二个方案加入 base 64 的编码和解码的函数即可:参考 js的Base64编码与解码
参考
Egret 玩一玩平台开发指南
QQ玩一玩(轻游戏)开发环境搭建与调试
QQ轻游戏入门到精通OR放弃?
全面屏手机QQ玩一玩平台的遮罩问题
厘米游戏引擎接入指南/4.手机QQ相关/4.2.分享
Egret QQ玩一玩适配【踩坑日记】相关推荐
- 全志哪吒D1-H Tina Linux Ubuntu 22.04入门踩坑日记
哪吒D1-H Tina Linux入门踩坑日记 系统环境 源码编译 mklibs-readelf的C++标准问题 m4的SIGSTKSZ问题 libfakeroot的_STAT_VER问题 read_ ...
- Win11 + Ubuntu18.04 双系统踩坑日记
Win11 + Ubuntu18.04 双系统踩坑日记 前言 准备工作 硬件配置 镜像下载 Win11镜像下载 Ubuntu镜像下载 启动盘准备 Win11启动盘 Ubuntu启动盘 Win11安装 ...
- 【Flutter混合开发踩坑日记之‘applicationVariants‘ for extension ‘android‘】
Flutter混合开发踩坑日记之'applicationVariants' for extension 'android' 正文 坑一:Could not get unknown property ' ...
- Swarm-BZZ踩坑日记之 如何让METMASK小狐狸显示gbzz
刚入门bzz的新手还不知道小狐狸是什么的请移步上一章节:Swarm-BZZ踩坑日记之 如何在METMASK小狐狸导入节点地址 在浏览器安装好小狐狸,并添加自己的钱包地址后 会发现只显示ETH,并不显示 ...
- ReactNative 在丁香医生项目中引入的踩坑日记
ReactNative 在丁香医生项目中引入的踩坑日记 this没绑定到函数导致空指针 参考 React-Native 踩坑第二弹-undefined is not a function(evalua ...
- springboot踩坑日记—nacos: Error watching Nacos Service change
springboot踩坑日记-nacos: Error watching Nacos Service change Spring Boot :: (v2.1.5.RELEASE) 错误代码: 07-3 ...
- 微信小程序踩坑日记-微信小程序首次加载样式错乱问题
微信小程序踩坑日记-微信小程序首次加载样式错乱问题 在实际开发项目中,遇到了个棘手的问题,就是在某些因素下,进入小程序发现有些样式发生偏移.错乱等问题 问题原因:-未知(估计是组件的问题) ↓ 解决办 ...
- c++字符串操作之std::ostringstream踩坑日记
c++字符串操作之std::ostringstream踩坑日记 在开发过程中经常会遇到字符串操作,而std::string又没有format操作,这就很难受了. 于是我找到了std::ostrings ...
- Antd Pro V4 protable详解(ps:踩坑日记)
Antd Pro V4 protable详解(ps:踩坑日记) 写在前面: 在这篇文章中,你会了解到: protable 中的cloumns属性详解 protable数据加载和处理(两种方法,直接使用 ...
- midjourney指令笔记+踩坑日记+gpt论文润色指令
跟人拼团入手了midjourney,长期记录更新. midjourney指令笔记+踩坑日记 指令笔记 踩坑日记 GPT论文润色指令 指令一 指令二 指令三 指令四 指令五 指令笔记 关键词参考网址:p ...
最新文章
- [unreal4入门系列之二] 下载和安装虚幻4游戏引擎
- UnicodeEncodeError: 'locale' codec can't encode character '\u5e74' in position 2: encoding error
- 大数据算法:排位问题(2)
- IDEA2021.03 项目全部变红,但是可以正常编译运行
- 有向图的深度和广度遍历
- javascript对象包含哪些要素_javascript有哪几种对象?
- spss方差分析_【案例】SPSS统计分析:多因素方差分析
- Java中String类 compareTo()方法比较字符串详解
- caffe手写数字分类-学习曲线
- lucene中文分词搜索的核心代码
- hadoop配置历史服务器
- Java 中的 IO 和 NIO
- 树莓派4b 调整屏幕分辨率
- Dota数据集切割以及保存为yolo和voc格式——HBB
- 微信小程序 带可拖动进度条和时间显示的音频播放器
- 行为识别 - TAM: Temporal Adaptive Module for Video Recognition
- 教育平台用户注册模块
- 河南新乡:牧野区王村镇手绘文明墙巩固文明果
- matlab 幅度和幅值
- 【PyCharm】python2.7 3.7遇到的bug
热门文章
- 教学反思计算机专业,【计算机教学反思】_计算机教学反思参考资料-毕业论文范文网...
- Unity学习记录:使用触发器制作人物靠近物体后交互的方法
- dt.Select()
- 99%的人都想要的广告拦截软件
- CC00003.LBCHAC——|LBCHACHPC概述|
- 为什么会对电视剧上瘾?
- html5圣诞贺卡,用CorelDRAW制作漂亮别致的圣诞贺卡
- 不同部位长青春痘说明不同器官有毛病吗? (转自 八月的阳光)
- 网络爬虫爬取拉勾招聘网
- 足球与oracle系列(4):从巴西惨败于德国,想到,差异的RAC拓扑对比!