元宇宙(Metaverse),是人类运用数字技术构建的,由现实世界映射或超越现实世界,可与现实世界交互的虚拟世界,具备新型社会体系的数字生活空间。

可见元宇宙第一步是创建专属虚拟形象,但创建3D虚拟形象需要3D基础知识。对于大部分android开发者(包括我本人)来说没有这方面的积累。难道因此我们就难以进入元宇宙的世界吗?不,今天我们借助即构平台提供的Avatar SDK,只要有Android基础即可进入最火的元宇宙世界!先看效果:

上面gif被压缩的比较狠,这里放一张截图:

1 免费注册即构开发者

前往即构控制台网站:https://console.zego.im/注册开发者账户。注册成功后,创建项目:

控制台中可以得到AppID和AppSign两个数据,这两个数据是重要凭证,后面会用到。

由于我们用到了即构的Avatar功能,但目前官方没有提供线上自动开启方式,需要主动找客服申请(当然,这是免费的),只需提供自己项目的包名,即可开通Avatar权限。打开https://doc-zh.zego.im/article/15206右下角有“联系我们”,点击即可跟客服申请免费开通权限。

注意,如果不向客服申请Avatar权限,调用AvatarSDK会失败!

2 准备开发环境

前往即构官方元宇宙开发SDK网站https://doc-zh.zego.im/article/15302下载SDK,得到如下文件列表:

接下来过程如下:

  1. 打开SDK目录,将里面的ZegoAvatar.aar拷贝至app/libs目录下。
  2. 添加SDK引用。打开app/build.gradle文件,在dependencies节点引入 libs下所有的jaraar:

dependencies {implementation fileTree(dir: 'libs', include: ['*.jar', "*.aar"]) //通配引入//其他略
}
  1. 设置权限。根据实际应用需要,设置应用所需权限。进入app/src/main/AndroidManifest.xml 文件,添加权限。
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /><uses-featureandroid:glEsVersion="0x00020000"android:required="true" />
<uses-feature android:name="android.hardware.camera" />
<uses-feature android:name="android.hardware.camera.autofocus" />

因为Android 6.0在一些比较重要的权限上要求必须申请动态权限,不能只通过 AndroidMainfest.xml文件申请静态权限。具体动态请求权限代码可看附件源码。

3 导入资源

上一小节下载的zip文件中,有个assets目录。里面包含了Avatar形象相关资源,如:衣服、眉毛、鞋子等。这是即构官方免费提供的资源,可以满足一般性需求了。当然了,如果想要自己定制资源也是可以的。assets文件内容如下:

资源名称 说明
AIModel.bundle Avatar 的 AI 模型资源。当使用表情随动、声音随动、AI 捏脸等能力时,必须先将该资源的绝对路径设置给 Avatar SDK。
base.bundle 美术资源,包含基础 3D 人物模型资源、资源映射表、人物模型默认外形等。
Packages 美妆、挂件、装饰等资源。 每个资源 200 KB ~ 1 MB 不等,跟资源复杂度相关。

注意:由于资源文件很大,上面下载的美术资源只包含少量必须资源。如果需要全部资源,可以去官网找客服索要:https://doc-zh.zego.im/article/14882

上面的文件需要存放到Android本地SDCard上,这里有2个方案可供参考:

  • 方案一: 将资源先放入到app/src/assets目录内,然后在app启动时,自动将assets的内容拷贝到SDcard中。
  • 方案二: 将资源放入到服务器端,运行时自动从服务器端下载。

为了简单起见,我们这里采用方案一。参考代码如下, 详细代码可以看附件:

AssetsFileTransfer.copyAssetsDir2Phone(this.getApplication(),"AIModel.bundle", "assets");
AssetsFileTransfer.copyAssetsDir2Phone(this.getApplication(),"base.bundle", "assets");
AssetsFileTransfer.copyAssetsDir2Phone(this.getApplication(),"Packages", "assets");

4 创建虚拟形象

创建虚拟形象本质上来说就是调用即构的Avatar SDK,其大致流程如下:

接下来我们逐步实现上面流程。

需要注意的是,上面示意图中采用的是AvatarView,可以非常方便的直接展示Avatar形象,但是不方便后期将画面通过RTC实时传递, 因此,我们后面的具体实现视通过TextureView替代AvatarView。

4.1 申请权鉴

这里再次强调一下,一定要打开https://doc-zh.zego.im/article/15206点击右下角有“联系我们”,向客服申请免费开通Avatar权限。否则无法使用Avatar SDK

申请权鉴代码如下:


public class KeyCenter { // 控制台地址: https://console.zego.im/dashboard public static long APP_ID = 这里值可以在控制台查询,参考第一节;  //这里填写APPIDpublic static String APP_SIGN =  这里值可以在控制台查询,参考第一节; // 鉴权服务器的地址public final static String BACKEND_API_URL = "https://aieffects-api.zego.im?Action=DescribeAvatarLicense";public static String avatarLicense = null;public static String getURL(String authInfo) {Uri.Builder builder = Uri.parse(BACKEND_API_URL).buildUpon();builder.appendQueryParameter("AppId", String.valueOf(APP_ID));builder.appendQueryParameter("AuthInfo", authInfo);return builder.build().toString();}public interface IGetLicenseCallback {void onGetLicense(int code, String message, ZegoLicense license);}/*** 在线拉取 license* @param context* @param callback*/public static void getLicense(Context context, final IGetLicenseCallback callback) {requestLicense(ZegoAvatarService.getAuthInfo(APP_SIGN, context), callback);}/*** 获取license* */public static void requestLicense(String authInfo, final IGetLicenseCallback callback) {String url = getURL(authInfo);HttpRequest.asyncGet(url, ZegoLicense.class, (code, message, responseJsonBean) -> {if (callback != null) {callback.onGetLicense(code, message, responseJsonBean);}});}public class ZegoLicense {@SerializedName("License")private String license;public String getLicense() {return license;}public void setLicense(String license) {this.license = license;}}}

在获取Lincense时,只需调用getLicense函数,例如在Activity类中只需如下调用:

KeyCenter.getLicense(this, (code, message, response) -> {if (code == 0) {KeyCenter.avatarLicense = response.getLicense();showLoading("正在初始化...");avatarMngr = AvatarMngr.getInstance(getApplication());avatarMngr.setLicense(KeyCenter.avatarLicense, this);} else {toast("License 获取失败, code: " + code);}});

4.2 初始化AvatarService

初始化AvatarService过程比较漫长(可能要几秒),通过开启worker线程后台加载以避免主线程阻塞。因此我们定义一个回调函数,待完成初始化后回调通知:


public interface OnAvatarServiceInitSucced {void onInitSucced();
}public void setLicense(String license, OnAvatarServiceInitSucced listener) {this.listener = listener;ZegoAvatarService.addServiceObserver(this);String aiPath = FileUtils.getPhonePath(mApp, "AIModel.bundle", "assets"); //   AI 模型的绝对路径ZegoServiceConfig config = new ZegoServiceConfig(license, aiPath);ZegoAvatarService.init(mApp, config);
}@Override
public void onStateChange(ZegoAvatarServiceState state) {if (state == ZegoAvatarServiceState.InitSucceed) {Log.i("ZegoAvatar", "Init success");// 要记得及时移除通知ZegoAvatarService.removeServiceObserver(this);if (listener != null) listener.onInitSucced();}
}

这里setLicense函数内完成初始化AvatarService,初始化完成后会回调onStateChange函数。但是要注意,在初始化之前必须把资源文件拷贝到本地SDCard,即完成资源导入:

private void initRes(Application app) {// 先把资源拷贝到SD卡,注意:线上使用时,需要做一下判断,避免多次拷贝。资源也可以做成从网络下载。if (!FileUtils.checkFile(app, "AIModel.bundle", "assets"))FileUtils.copyAssetsDir2Phone(app, "AIModel.bundle", "assets");if (!FileUtils.checkFile(app, "base.bundle", "assets"))FileUtils.copyAssetsDir2Phone(app, "base.bundle", "assets");if (!FileUtils.checkFile(app, "human.bundle", "assets"))FileUtils.copyAssetsDir2Phone(app, "human.bundle", "assets");if (!FileUtils.checkFile(app, "Packages", "assets"))FileUtils.copyAssetsDir2Phone(app, "Packages", "assets");
}

4.3 创建虚拟形象

前面在https://doc-zh.zego.im/article/15302下载SDK包含了helper目录,这个目录里面有非常重要的两个文件:

其中ZegoCharacterHelper文件是个接口定义类,即虽然是个类,但具体的实现全部在ZegoCharacterHelperImpl中。我们先一睹为快,看看ZegoCharacterHelper包含了哪些可处理的属性:

public class ZegoCharacterHelper {public static final String MODEL_ID_MALE = "male";public static final String MODEL_ID_FEMALE = "female";//****************************** 捏脸维度的 key 值 ******************************/public static final String FACESHAPE_BROW_SIZE_Y = "faceshape_brow_size_y";// 眉毛厚度, 取值范围0.0-1.0,默认值0.5public static final String FACESHAPE_BROW_SIZE_X = "faceshape_brow_size_x";// 眉毛长度, 取值范围0.0-1.0,默认值0.5public static final String FACESHAPE_BROW_ALL_Y = "faceshape_brow_all_y";// 眉毛高度, 取值范围0.0-1.0,默认值0.5public static final String FACESHAPE_BROW_ALL_ROLL_Z = "faceshape_brow_all_roll_z";// 眉毛旋转, 取值范围0.0-1.0,默认值0.5public static final String FACESHAPE_EYE_SIZE = "faceshape_eye_size"; // 眼睛大小, 取值范围0.0-1.0,默认值0.5public static final String FACESHAPE_EYE_SIZE_Y = "faceshape_eye_size_y";public static final String FACESHAPE_EYE_ROLL_Y = "faceshape_eye_roll_y";// 眼睛高度, 取值范围0.0-1.0,默认值0.5public static final String FACESHAPE_EYE_ROLL_Z = "faceshape_eye_roll_z";// 眼睛旋转, 取值范围0.0-1.0,默认值0.5public static final String FACESHAPE_EYE_X = "faceshape_eye_x";// 双眼眼距, 取值范围0.0-1.0,默认值0.5public static final String FACESHAPE_NOSE_ALL_X = "faceshape_nose_all_x";// 鼻子宽度, 取值范围0.0-1.0,默认值0.5public static final String FACESHAPE_NOSE_ALL_Y = "faceshape_nose_all_y";// 鼻子高度, 取值范围0.0-1.0,默认值0.5public static final String FACESHAPE_NOSE_SIZE_Z = "faceshape_nose_size_z";public static final String FACESHAPE_NOSE_ALL_ROLL_Y = "faceshape_nose_all_roll_y";// 鼻头旋转, 取值范围0.0-1.0,默认值0.5public static final String FACESHAPE_NOSTRIL_ROLL_Y = "faceshape_nostril_roll_y";// 鼻翼旋转, 取值范围0.0-1.0,默认值0.5public static final String FACESHAPE_NOSTRIL_X = "faceshape_nostril_x";public static final String FACESHAPE_MOUTH_ALL_Y = "faceshape_mouth_all_y";// 嘴巴上下, 取值范围0.0-1.0,默认值0.5public static final String FACESHAPE_LIP_ALL_SIZE_Y = "faceshape_lip_all_size_y";// 嘴唇厚度, 取值范围0.0-1.0,默认值0.5public static final String FACESHAPE_LIPCORNER_Y = "faceshape_lipcorner_y";// 嘴角旋转, 取值范围0.0-1.0,默认值0.5public static final String FACESHAPE_LIP_UPPER_SIZE_X = "faceshape_lip_upper_size_x"; // 上唇宽度, 取值范围0.0-1.0,默认值0.5public static final String FACESHAPE_LIP_LOWER_SIZE_X = "faceshape_lip_lower_size_x"; // 下唇宽度, 取值范围0.0-1.0,默认值0.5public static final String FACESHAPE_JAW_ALL_SIZE_X = "faceshape_jaw_all_size_x";// 下巴宽度, 取值范围0.0-1.0,默认值0.5public static final String FACESHAPE_JAW_Y = "faceshape_jaw_y";// 下巴高度, 取值范围0.0-1.0,默认值0.5public static final String FACESHAPE_CHEEK_ALL_SIZE_X = "faceshape_cheek_all_size_x";// 脸颊宽度, 取值范围0.0-1.0,默认值0.5//其他函数略
}

可以看到,上面数据基本包含所有人脸属性了,基本具备了捏脸能力,篇幅原因,我们这里不具体去实现。有这方面需求的读者,可以通过在界面上调整上面相关属性来实现。

接下来我们开始创建虚拟形象,首先创建一个User实体类:


public class User {public String userName; //用户名public String userId; //用户IDpublic boolean isMan; //性别public int width; //预览宽度public int height; //预览高度public int bgColor; //背景颜色public int shirtIdx = 0; // T-shirt资源idpublic int browIdx = 0; //眉毛资源idpublic User(String userName, String userId, int width, int height) {this.userName = userName;this.userId = userId;this.width = width;this.height = height;this.isMan = true;bgColor = Color.argb(255, 33, 66, 99);}
}

示例作用,为了简单起见,我们这里只针对眉毛和衣服资源做选取。接下来创建一个Activity:

public class AvatarActivity extends BaseActivity  {private int vWidth = 720;private int vHeight = 1080; private User user = new User("C_0001", "C_0001", vWidth, vHeight);private TextureView mTextureView;  //用于显示Avatar形象private AvatarMngr mAvatarMngr; // 用于维护管理Avatarprivate ColorPickerDialog colorPickerDialog; //用于背景色选取@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_avatar);// ....// 其他初始化界面相关代码略...// ....user.isMan = true;mTextureView = findViewById(R.id.avatar_view);  mZegoMngr = ZegoMngr.getInstance(getApplication()); // 开启虚拟形象预览mAvatarMngr.start(mTextureView, user);}

最后一行代码中开启了虚拟形象预览,那么这里开启虚拟形象预览具体做了哪些工作呢?show me the code:

public class AvatarMngr implements ZegoAvatarServiceDelegate, RTCMngr.CaptureListener {private static final String TAG = "AvatarMngr";private static AvatarMngr mInstance;private boolean mIsStop = false;private User mUser = null;private TextureBgRender mBgRender = null;private OnAvatarServiceInitSucced listener;private ZegoCharacterHelper mCharacterHelper;private Application mApp;public interface OnAvatarServiceInitSucced {void onInitSucced();}public void setLicense(String license, OnAvatarServiceInitSucced listener) {this.listener = listener;ZegoAvatarService.addServiceObserver(this);String aiPath = FileUtils.getPhonePath(mApp, "AIModel.bundle", "assets"); //   AI 模型的绝对路径ZegoServiceConfig config = new ZegoServiceConfig(license, aiPath);ZegoAvatarService.init(mApp, config);}public void stop() {mIsStop = true;mUser = null;stopExpression();}public void updateUser(User user) {mUser = user;if (user.shirtIdx == 0) {mCharacterHelper.setPackage("m-shirt01");} else {mCharacterHelper.setPackage("m-shirt02");}if (user.browIdx == 0) {mCharacterHelper.setPackage("brows_1");} else {mCharacterHelper.setPackage("brows_2");}}/*** 启动Avatar,调用此函数之前,请确保已经调用过setLicense** @param avatarView*/public void start(TextureView avatarView, User user) {mUser = user;mIsStop = false;initAvatar(avatarView, user);startExpression();}private void initAvatar(TextureView avatarView, User user) {String sex = ZegoCharacterHelper.MODEL_ID_MALE;if (!user.isMan) sex = ZegoCharacterHelper.MODEL_ID_FEMALE;// 创建 helper 简化调用// base.bundle 是头模, human.bundle 是全身人模mCharacterHelper = new ZegoCharacterHelper(FileUtils.getPhonePath(mApp, "human.bundle", "assets"));mCharacterHelper.setExtendPackagePath(FileUtils.getPhonePath(mApp, "Packages", "assets"));// 设置形象配置mCharacterHelper.setDefaultAvatar(sex);updateUser(user);// 获取当前妆容数据, 可以保存到用户资料中String json = mCharacterHelper.getAvatarJson();}// 启动表情检测private void startExpression() {// 启动表情检测前要申请摄像头权限, 这里是在 MainActivity 已经申请过了ZegoAvatarService.getInteractEngine().startDetectExpression(ZegoExpressionDetectMode.Camera, expression -> {// 表情直接塞给 avatar 驱动mCharacterHelper.setExpression(expression);});}// 停止表情检测private void stopExpression() {// 不用的时候记得停止ZegoAvatarService.getInteractEngine().stopDetectExpression();}// 获取到 avatar 纹理后的处理public void onCaptureAvatar(int textureId, int width, int height) {if (mIsStop || mUser == null) { // rtc 的 onStop 是异步的, 可能activity已经运行到onStop了, rtc还没return;}boolean useFBO = true;if (mBgRender == null) {mBgRender = new TextureBgRender(textureId, useFBO, width, height, Texture2dProgram.ProgramType.TEXTURE_2D_BG);}mBgRender.setInputTexture(textureId);float r = Color.red(mUser.bgColor) / 255f;float g = Color.green(mUser.bgColor) / 255f;float b = Color.blue(mUser.bgColor) / 255f;float a = Color.alpha(mUser.bgColor) / 255f;mBgRender.setBgColor(r, g, b, a);mBgRender.draw(useFBO); // 画到 fbo 上需要反向的ZegoExpressEngine.getEngine().sendCustomVideoCaptureTextureData(mBgRender.getOutputTextureID(), width, height, System.currentTimeMillis());}@Overridepublic void onStartCapture() {if (mUser == null) return;
//        // 收到回调后,开发者需要执行启动视频采集相关的业务逻辑,例如开启摄像头等AvatarCaptureConfig config = new AvatarCaptureConfig(mUser.width, mUser.height);
//        // 开始捕获纹理mCharacterHelper.startCaptureAvatar(config, this::onCaptureAvatar);}@Overridepublic void onStopCapture() {mCharacterHelper.stopCaptureAvatar();stopExpression();}private void initRes(Application app) {// 先把资源拷贝到SD卡,注意:线上使用时,需要做一下判断,避免多次拷贝。资源也可以做成从网络下载。if (!FileUtils.checkFile(app, "AIModel.bundle", "assets"))FileUtils.copyAssetsDir2Phone(app, "AIModel.bundle", "assets");if (!FileUtils.checkFile(app, "base.bundle", "assets"))FileUtils.copyAssetsDir2Phone(app, "base.bundle", "assets");if (!FileUtils.checkFile(app, "human.bundle", "assets"))FileUtils.copyAssetsDir2Phone(app, "human.bundle", "assets");if (!FileUtils.checkFile(app, "Packages", "assets"))FileUtils.copyAssetsDir2Phone(app, "Packages", "assets");}@Overridepublic void onError(ZegoAvatarErrorCode code, String desc) {Log.e(TAG, "errorcode : " + code.getErrorCode() + ",desc : " + desc);}@Overridepublic void onStateChange(ZegoAvatarServiceState state) {if (state == ZegoAvatarServiceState.InitSucceed) {Log.i("ZegoAvatar", "Init success");// 要记得及时移除通知ZegoAvatarService.removeServiceObserver(this);if (listener != null) listener.onInitSucced();}}private AvatarMngr(Application app) {mApp = app;initRes(app);}public static AvatarMngr getInstance(Application app) {if (null == mInstance) {synchronized (AvatarMngr.class) {if (null == mInstance) {mInstance = new AvatarMngr(app);}}}return mInstance;}
}

以上代码完成了整个虚拟形象的创建,关键代码全部展示,如果还需要具体全部代码,直接从附件中下载即可。

5 附件

  • 源码:https://github.com/RTCWang/Meta-Avatar
  • 即构元宇宙官网:https://doc-zh.zego.im/article/15302

30分钟快速生成元宇宙专属虚拟人相关推荐

  1. python 30分钟_一张图30分钟快速Python入门

    国外一个大牛使用一张图来讲述Python的基本概念,让你30分钟快速入门.通过该程序,可以了解快速Python语言基本的语法结构和使用方法,它的基本程序如下: 当然看不懂没关系这里还有中文版的 # - ...

  2. 30分钟快速搭建移动应用直传OSS服务

    30分钟快速搭建移动应用直传服务 背景 这是一个移动互联的时代.手机APP上传的数据会越来越多.把数据存储的问题交给OSS, 让开发者能更加专注于自己的应用逻辑. 那么怎么样基于OSS构建一个APP存 ...

  3. python程序员专用壁纸_神级python程序员分享的让小白30分钟快速上手的一张神图,赶快收藏!...

    原标题:神级python程序员分享的让小白30分钟快速上手的一张神图,赶快收藏! 现在很多人学编程都把Python作为入门语言,其实这是个很不错的选择,那么你知道新手如何学Python吗?小编给大家分 ...

  4. 全网最详细中英文ChatGPT接口文档(六)30分钟快速入门ChatGPT——使用策略和API数据使用策略

    30分钟快速入门使用ChatGPT--使用策略和API数据使用策略 Usage policies使用策略 Disallowed usage of our models 禁止使用我们的模型 API da ...

  5. 30分钟快速搭建一套私有云平台

    本文介绍一种基于starvcenter快速搭建私有云平台的方法,实现30分钟内完成一套私有云平台的部署. 整个安装过程真的太简单了,仅需4个步骤.话不多说,上干货~ 1.下载starvcenter的i ...

  6. 软骨鱼颠覆传统软件SaaS化改造之路:30分钟快速SaaS化

    什么是SaaS SaaS就是将应用软件统一部署在云端服务器上,客户可以根据工作实际需求,通过互联网向厂商定购所需的应用软件服务,按定购的服务多少和时间长短向厂商支付费用,并通过互联网获得Saas平台供 ...

  7. 用云存储30分钟快速搭建APP

    背景 不管你承认与否,移动互联的时代已经到来,这是一个移动互联的时代,手机已经是当今世界上引领潮流的趋势,大型的全球化企业和中小企业都把APP程序开发纳入到他们的企业发展策略当中. 但随着手机APP上 ...

  8. 深度学习之30分钟快速入门PyTorch(附学习资源推荐)

    目录 1.Pytorch简介 1.0 如何使用本教程 1.1 PyTorch由来 1.2 Torch简介 1.3 重新认识PyTorch 1.4 PyTorch和Tensorflow的对比 1.5 总 ...

  9. Fuel 30 分钟快速安装OpenStack

    一直以来,对于openstack 的初学者来讲,安装往往是入门的头大难题.在E版本之前,要搭建一个基本能用的openstack 环境那是相当麻烦,自己要装机,自己搞源,自己照着文档敲命令,又没有靠谱的 ...

最新文章

  1. 安卓v7支持包下的ListView替代品————RecyclerView
  2. Java 连接 MS sql Server 2005
  3. python绘制笑脸-用python绘图
  4. 请你简单介绍一下ArrayList和LinkedList的区别及大数据量情况下的插入效率对比
  5. 安卓 图像清晰度识别_智能车牌识别系统的常见故障和解决方法
  6. es6 import 命令
  7. [Ogre] 创建Ogre项目的一劳永逸的简单办法
  8. 其他系统 对外接口设计_外观模式:统一接口 VS.暴露细节
  9. 数字盲打怎么练_键盘上的数字键怎么练才能盲打?
  10. wwwscan目录扫描器的逆向分析
  11. excel批量翻译-excel怎么批量翻译
  12. 解决:ipad QQ可以联网,浏览器不能联网
  13. VMware拟收购云应用管理服务商Wavefront
  14. PhysX官方手册翻译
  15. 爆款短视频封面的三个核心要素
  16. 上海滩玩起了黑科技,苏宁Biu店带你刷脸扫货
  17. Linux服务器操作系统快速删除大量/大文件
  18. 【深度强化学习】6. Q-Learning技巧及其改进方案
  19. C语言华氏温度转换为摄氏温度
  20. 编写一个C 程序,并使用系统调用fork()创建一个子进程

热门文章

  1. vsCode 创建Vue项目
  2. 计算机的硬盘维修,计算机硬盘的维修方法和技巧
  3. 为什么?------”人的天性总是高估自己,而低估别人“
  4. cnpm安装淘宝镜像
  5. 椭圆曲线ECC倍点运算forJava
  6. java 连接wtc_Tuxedo通过WTC调用weblogic配置
  7. android开发 问卷调查案例_安卓 问卷调查Demo 原生代码
  8. 百度地图 pc浏览器获取经纬度
  9. lableme json转化为图片常用的脚本
  10. linux下CURL工具的使用(常用选项)