以下文章来源于京东零售技术 ,作者侯伟浩 狄彩林
原文链接

京东鸿蒙版来了〜

背景

随着鸿蒙2.0的发布,华为部分手机用户迎来鸿蒙时代,京东作为华为鸿蒙OS的合作APP,首次投入鸿蒙应用商用版开发,目前已上架V10.0.2版本。

鸿蒙OS特性

2021年6月3日,华为举行了鸿蒙OS2.0发布会。鸿蒙OS带来了全新桌面及用户体验,如桌面图标支持上滑呼出快捷卡片,原子化能力能通过鸿蒙设备间流转实现快速分享、显示,以及统一控制中心(手势:右上角下滑)、服务中心(手势:屏幕左下角或右下角向侧上方滑动)等。

Android工程鸿蒙化

01 背景

为了利用上鸿蒙的特性,我们开发者需要尽快的将App鸿蒙化。但是将整个App鸿蒙化的工作量是特别庞大的,那么有没有一种方式既能利用鸿蒙的特性也能快速适配呢,答案是有的,那就是混合包开发模式,整个App基本上没有大的修改,只需要新增鸿蒙相关模块用来实现鸿蒙相关特性即可。京东App-鸿蒙版能够做到快速适配上线,并拥有鸿蒙特性,就是利用了这种开发模式。下面我们将以京东App-鸿蒙版为例,具体介绍下相关流程。

02 Android工程改造

我们需要依赖鸿蒙的一个兼容包(包文件可以联系我们取得),将我们现有的Application继承自HarmonyApplication,仅需编译依赖,不需要真正打进App中。

  compileOnly files('libs/abilityshell_ide_java.jar')
  1. 在AndroidManifest.xml中,向根节点下增加。
 <uses-feature android:name="zidane.software.ability" android:required="false" />
  1. 向application节点下新增子节点。
  <meta-data android:name="permZA" android:value="true" /><meta-data android:name="multiFrameworkBundle" android:value="true" />

自此已经可以构建出鸿蒙需要的apk包了,大家也可以通过配置编译变体等形式,构建鸿蒙版本的apk包。

注意:鸿蒙包中混入的apk必须要是64位的。

03 配置鸿蒙工程

1.在鸿蒙工程中entry module中的build.gradle里,增加混入apk文件配置。

   legacyApkOptions{legacyApk rootProject.file('android_entry.apk').absolutePath //混入apk的存放路径signConfig{storeFile rootProject.file('xxx.keystore')   //混入apk所用签名文件}}

整体配置如下图 :

  1. 签名改造,我们需要根据Android apk的签名来做鸿蒙应用签名的申请,需要将.keystore或.jks格式的签名文件转换成.p12文件,签名秘钥和别名保持不变。具体转换步骤,大家可以自行搜索。

参考:在转换.p12文件时,我们遇到了问题,由于我们Android的签名格式是.keystore,转出来的.p12文件有问题无法申请鸿蒙应用证书,经过和华为方面沟通,我们将鸿蒙应用的签名秘钥和别名保持和Android的一致,解决了打包问题。

  1. 配置文件增加属性,在鸿蒙工程的每个feature module的config.json app节点下,增加originalName,表示混入的apk包名,同时要将bundleName的值也改成一致。

  2. 在entry模块下,新建一个空的Ability类并配置在config.json里作为启动入口,如:

  "abilities": [{"skills": [{"entities": ["entity.system.home"],"actions": ["action.system.home"]}],"orientation": "portrait","visible": true,"name": "com.xxx.xxx.xx.EntryAbility","icon": "$media:icon","description": "$string:mainability_description","label": "$string:app_name","type": "page","launchType": "standard"}],

自此已经可以构建出包含原有Android功能的鸿蒙包了。

Android-鸿蒙互调用

01 从Android启动鸿蒙组件

我们需要集成鸿蒙的一个jar包(可以联系我们获得此文件),来实现从Android启动鸿蒙的组件。如:

Intent intent = new Intent();
ComponentName componentName = new ComponentName("your harmony app's bundleName name","your ability's full name");
intent.setComponent(componentName);
intent.putExtras(bundle);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK|Intent.FLAG_ACTIVITY_CLEAR_TASK);
AbilityUtils.startAbility(context, intent);

02 鸿蒙模块调用Android

  1. 鸿蒙启动Android组件

鸿蒙里本身是支持启动Android组件的,只需要在Intent里增加一个flag

Intent.FLAG_NOT_OHOS_COMPONENT

如:

  Intent intent = new Intent();Operation operation = new Intent.OperationBuilder().withDeviceId("").withBundleName("your android app’s packagename").withAbilityName("your android app’s activity fullname").withFlags(Intent.FLAG_NOT_OHOS_COMPONENT).build();intent.setOperation(operation);startAbility(intent);
  1. 鸿蒙模块调用Android现有能力

在Android包里,已经有了很多现有功能,如埋点收集、用户登录态获取、定位、地址等等,在鸿蒙模块里需要用到这些功能时,我们为了节省时间暂时没有再去开发一遍鸿蒙版,我们利用了Java的反射技术来搞定。经过验证,在Android中反射鸿蒙以及鸿蒙中反射Android都是可以的。

03 获取当前是否为鸿蒙系统

在有些场景下,我们需要知道当前系统的运行环境是不是鸿蒙系统,可以使用以下代码段来实现。

private static final String HARMONY_OS = "harmony";
/**
* check the system is harmony os
*
* @return true if it is harmony os
*/
public static boolean isHarmonyOS() {try {Class clz = Class.forName("com.huawei.system.BuildEx");Method method = clz.getMethod("getOsBrand");return HARMONY_OS.equals(method.invoke(clz));} catch (ClassNotFoundException e) {Log.e(TAG, "occured ClassNotFoundException");} catch (NoSuchMethodException e) {Log.e(TAG, "occured NoSuchMethodException");} catch (Exception e) {Log.e(TAG, "occur other problem");}return false;
}

鸿蒙OS特性+购物应用场景开发

鸿蒙OS打破了设备间的壁垒,对用户及应用开发者来说,形成了超级终端。超级终端包含手机、大屏、平板,未来或许会有更多的设备加入,设备间协同合作让购物体验变得优质。每个设备不再是孤立的个体,而是基于鸿蒙操作系统的智慧终端,即便用户拿着不同的设备,也可以有很好的体验。通过一键流转实现跨设备间的数据传输,从而实现无缝的购物体验。

01 流转:直播间FA

介绍

流转泛指多设备间的分布式操作,打破设备界限,多设备联动,使用户应用程序可分可合、可流转。流转按照体验可分为跨端迁移和多端协同。流转支持免安装运行FA。京东App-鸿蒙版本中的直播FA就利用了流转能力,将当前手机的直播流转至TV端,做到无缝衔接,并支持通过手机端控制TV端直播显示的功能。效果如下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XnVXVpY1-1625060181076)(https## 标题://img-blog.csdnimg.cn/2021063021251885.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzI1NzQ5NzQ5,size_16,color_FFFFFF,t_70)]

开发

我们以京东App-鸿蒙版中直播FA的流转开发经验进行介绍,如何具备流转能力。

1.权限要求

由于使用到了分布式能力,我们需要先把权限配置好,在对应的module的config.json下,增加以下权限:

ohos.permission.GET_DISTRIBUTED_DEVICE_INFO
ohos.permission.DISTRIBUTED_DATASYNC
ohos.permission.DISTRIBUTED_DEVICE_STATE_CHANGE

同时在Ability里,需要增加动态权限申请。

requestPermissionsFromUser(new String[]{SystemPermission.DISTRIBUTED_DATASYNC}, Constants.PermissionCode.PERMISSION_DISTRIBUTED_DATASYNC);

2.关键接口

3.功能实现

a. 通过流转服务注册管理器,将当前FA注册,注册时可以指定流转的过滤条件,如设备类型、目标设备等等:

b. 当需要流转时,我们通过流转服务注册管理器获取当前满足条件的设备列表:

系统会自动查找设备,将满足条件的设备自动展示出来供用户选择,当用户点击某个设备后,就会回调IContinuationDeviceCallback的onDeviceConnectDone方法,获取到目标设备的Id后,就可以启动目标设备的FA。

c. 启动远程FA

需要注意的是,在启动对端设备上FA时,我们要确保对端设备的分布式能力已经被初始化。

02 FA近场分享:商详FA

介绍

FA近场分享能力依赖于华为分享服务,可以快速实现FA分享的功能。较单纯的使用分布式FA流转功能,为开发者免除了设备发现功能,并且没有了同账号同网络等限制条件。在京东App-鸿蒙版中,商详FA就使用了此功能实现了FA的近场分享,并且能够做到免安装打开商详页面。下图分别是A向B发送商详FA 和 B接收商详FA。

开发

我们将以在京东App-鸿蒙版中的相关开发经验介绍下如何进行FA近场分享的开发。

工作原理图示:

由于功能依赖华为分享服务,我们首先要引入IDL文件。

1. 导入IDL文件

在商详FA module中java同级目录,创建idl目录,并创建包名com.huawei.hwshare.third,在此包名下创建IHwShareCallback.idl和 IHwShareService.idl文件,文件具体内容如下:

IHwShareCallback.idl:
interface com.huawei.hwshare.third.IHwShareCallback {[oneway] void notifyState([in] int state);}
IHwShareService.idl:
sequenceable ohos.interwork.utils.PacMapEx;interface com.huawei.hwshare.third.IHwShareCallback;interface com.huawei.hwshare.third.IHwShareService {int startAuth([in] String appId, [in] IHwShareCallback callback); int shareFaInfo([in] PacMapEx pacMapEx);}

2. 对分享能力进行封装

以下是我们在商详FA中封装好的代码,大家可以直接使用。

package com.xxx.xxx.xxx;
import com.huawei.hwshare.third.HwShareCallbackStub;
import com.huawei.hwshare.third.HwShareServiceProxy;
import ohos.aafwk.ability.IAbilityConnection;
import ohos.aafwk.content.Intent;
import ohos.app.Context;
import ohos.bundle.ElementName;
import ohos.eventhandler.EventHandler;
import ohos.eventhandler.EventRunner;
import ohos.interwork.utils.PacMapEx;
import ohos.rpc.IRemoteObject;
import ohos.rpc.RemoteException;
import ohos.hiviewdfx.HiLog;
import ohos.hiviewdfx.HiLogLabel;
public class ShareFaManager {private static final HiLogLabel LABEL_LOG = new HiLogLabel(3, 0xD000F00, "ShareFa"); private static final String LOG_FORMAT = "%{public}s: %{public}s";// FA的图标 byte[] len < 32768 非必须,不传默认取应用图标public static final String HM_FA_ICON = "ohos_fa_icon";// FA的名字 String len < 1024 非必须,不传默认取应用名public static final String HM_FA_NAME = "ohos_fa_name";// ability类名 String len < 1024 必须public static final String HM_ABILITY_NAME = "ohos_ability_name";// 包名 String len < 1024 必须public static final String HM_BUNDLE_NAME = "ohos_bundle_name";// FA类型 int 暂时只有0 非必须,默认为0public static final String SHARING_FA_TYPE = "sharing_fa_type";// FA卡片展示图 byte[] len < 153600 必须public static final String SHARING_THUMB_DATA = "sharing_fa_thumb_data";// FA卡片展示信息 String len < 1024 必须public static final String SHARING_CONTENT_INFO = "sharing_fa_content_info";// 携带的额外信息,可带到被拉起的FA String len < 10240 非必须public static final String SHARING_EXTRA_INFO = "sharing_fa_extra_info";private static final String TAG = "ShareHmFaManager";private static final String SHARE_PKG_NAME = "com.huawei.android.instantshare";private static final String SHARE_ACTION = "com.huawei.instantshare.action.THIRD_SHARE";private static final long UNBIND_TIME = 20*1000L;private Context mContext;private String mAppId;private PacMapEx mSharePacMap;private static ShareFaManager sSingleInstance;private HwShareServiceProxy mShareService;private boolean mHasPermission = false;private EventHandler mHandler = new EventHandler(EventRunner.getMainEventRunner());//服务绑定回调private final IAbilityConnection mConnection = new IAbilityConnection() {@Overridepublic void onAbilityConnectDone(ElementName elementName, IRemoteObject iRemoteObject, int i) {HiLog.error(LABEL_LOG, LOG_FORMAT, TAG, "onAbilityConnectDone success.");mHandler.postTask(()->{mShareService = new HwShareServiceProxy(iRemoteObject);try {//华为分享认证授权mShareService.startAuth(mAppId, mFaCallback);} catch (RemoteException e) {HiLog.error(LABEL_LOG, LOG_FORMAT, TAG, "startAuth error.");}});}@Overridepublic void onAbilityDisconnectDone(ElementName elementName, int i) {HiLog.info(LABEL_LOG, LOG_FORMAT, TAG, "onAbilityDisconnectDone.");mHandler.postTask(()->{mShareService = null;mHasPermission = false;});}};private Runnable mTask = () -> {if (mContext != null && mShareService != null) {mContext.disconnectAbility(mConnection);mHasPermission = false;mShareService = null;}};//华为分享认证授权回调private final HwShareCallbackStub mFaCallback = new HwShareCallbackStub("HwShareCallbackStub") {@Overridepublic void notifyState(int state) throws RemoteException {mHandler.postTask(()->{HiLog.info(LABEL_LOG, LOG_FORMAT, TAG, "notifyState: " + state);if (state == 0) {mHasPermission = true;if (mSharePacMap != null) {shareFaInfo();}}});}};/**\* 单例模式获取ShareFaManager的实例对象*\* @param context 程序Context\* @return ShareFaManager实例对象*/public static synchronized ShareFaManager getInstance(Context context) {if (sSingleInstance == null && context != null) {sSingleInstance = new ShareFaManager(context.getApplicationContext());}return sSingleInstance;}private ShareFaManager(Context context) {mContext = context;}private void shareFaInfo() {if (mShareService == null) {return;}if (mHasPermission) {HiLog.info(LABEL_LOG, LOG_FORMAT, TAG, "start shareFaInfo.");try {mShareService.shareFaInfo(mSharePacMap);mSharePacMap = null;} catch (RemoteException e) {HiLog.error(LABEL_LOG, LOG_FORMAT, TAG, "shareFaInfo error.");}}// 不使用时断开mHandler.postTask(mTask, UNBIND_TIME);}/**\* 开始分享*\* @param appId 开发者联盟网站创建鸿蒙服务/鸿蒙应用时生成的appid\* @param pacMap 服务信息载体*/public void shareFaInfo(String appId, PacMapEx pacMap) {if (mContext == null) {return;}mAppId = appId;mSharePacMap = pacMap;mHandler.removeTask(mTask);shareFaInfo();bindShareService();}/**\* 绑定华为分享服务*/private void bindShareService() {if (mShareService != null) {return;}HiLog.error(LABEL_LOG, LOG_FORMAT, TAG, "start bindShareService.");Intent intent = new Intent();intent.setBundle(SHARE_PKG_NAME);intent.setAction(SHARE_ACTION);intent.setFlags(Intent.FLAG_NOT_OHOS_COMPONENT);mContext.connectAbility(intent, mConnection);}}

3.开始分享

我们将参数进行组装,调用ShareFaManager的shareFaInfo方法即可自动的完成FA分享功能。如我们将商详FA进行分享:

注意:

  1. 使用时要主要传递的数据不要超过限定的大小,否则会分享失败并导致程序崩溃。

  2. 在对端接收到分享后,我们需要将自定义的参数取出来,从Intent中取sharing_fa_extra_info即可。

Ps:针对远距离的场景,华为也给出了解决方案,通过畅连即可分享购物链接。值得注意的是,此时好友还可以通过屏幕共享在商品页面进行涂鸦互动。

03 服务卡片:搜索卡片

用户上滑 App 图标即可生成万能卡片 ,在桌面呈现更丰富的信息,卡片信息支持实时更新,减少了 App 加载的时间,如目前京东app,用户上滑 App 图标可打开快捷搜索入口。

介绍

FA卡片是FeatureAbility的Page模板的一种界面展示形式。FA卡片常用于嵌入到其他应用中作为其界面的一部分显示,并支持基础的交互功能。卡片使用方作为卡片展示的宿主负责显示卡片,卡片使用方的典型应用就是桌面应用。卡片使用方仅限系统应用。

当FA规格小于10M时,可以支持免安装运行。系统最大支持500个卡片,相同名称的卡片实例最大是32个。

通过服务卡片的一些特点,如定时更新、免安装运行等,可以很好的进行快捷入口的引导。如我们可以在卡片上展示活动商品,并定期更新,用户可以免安装的打开活动详情,当用户产生进一步购买欲望时,用户可下载整个App进行下单。

开发

卡片的开发支持JS和Java两种方式。在京东App-鸿蒙版中的搜索FA里,我们加入了FA卡片,可以直达搜索。下面我们将以此为例进行开发步骤的讲解。

1.卡片配置

首先要在搜索FA的config.json中配置forms节点,比如:

我们给SearchAbility节点下添加forms节点,就表示这个卡片的创建及管理由SearchAbility来负责。

注意:必须要设置label属性,必须是资源形式的且不能是包名。

属性解释:

2. 实现卡片相关回调

在SearchAbility中,复写以下几个方法:

创建:在创建卡片时,我们可以从Intent中获取当前要创建卡片的Id,如:

这是一个很简单的卡片,我们没有对卡片中的视图设置任何数据和事件,那么点击卡片后,打开的就是负责管理卡片的Ability。如果需要设置数据和事件,可以使用以下方式

  1. 创建ComponentProvider;

  2. 通过ComponentProvider设置对应View的数据,以及点击事件,目前能够支持的事件有START_ABILITY和START_SERVICE两类;

  3. 将ComponentProvider对象合并入ProviderFormInfo中。

更新:当触发了更新卡片方法时,我们可以进行数据更新,并将最新的数据更新到卡片View上。

删除:当卡片使用方将卡片删除,我们可能需要将对应卡片在App内的相关持久化数据进行删除。

3.配置EntryCard目录

配置EntryCard目录,以便让系统能够识别出服务卡片,并展示在服务中心的推荐里。新建应用时可以勾选自动生成,如果是之前IDE创建的工程,则需要手动补充上。

1)在工程根目录下创建EntryCard目录;

2) EntryCard目录下,创建一个文件夹,取名为拥有卡片的FA工程名,如我们的搜索FA拥有服务卡片,搜索FA的工程名叫searchfeature,那我们就创建一个文件夹,名字就叫做searchfeature;

3)在searchfeature目录下创建base/snapshot两级目录,在其中放置我们的卡片图片,其命名方式为formname-dimensions,如搜索卡片的卡片名称配置的是search_card,尺寸是2*2的,那么这个图片就命名为search_card-2x2.png。

鸿蒙App打包及上架

01 打包构建

通过以上配置,我们已经可以进行鸿蒙App的构建了。目前鸿蒙App分为两种构建形式,debug和release,可以通过DevEco工具自带的编译任务或者使用gradle的assembleDebug signReleaseApp任务进行构建。

其中debug模式构建方式出来的产物是多个目标设备的多个.hap文件,每个FA都会构建出各自的.hap文件;release会构建出一个.app文件,我们需要将此文件进行上架发布。

安装及运行

  1. 开发者无法安装.app安装包,此文件只能用于上架应用市场。

  2. 通过adb shell bm get -udid获取设备UDID后,录入到开发者中心,并生成证书文件,我们就可以安装.hap包。

  3. 安装时可以将文件push到手机某个目录下(如sdcard/hmphone),然后使用adb shell bm install -p /sdcard/hmphone/进行安装,每次安装可以先删除之前文件。

注意:由于我们无法安装验证.app包,我们要保证在debug和release两种构建模式下,我们的代码不会发生改变。

02 应用上架及发布

1.如果还没有在开发者中心创建鸿蒙应用的话,需要先新增一个鸿蒙应用,包名和之前Android的包名保持一致,并关联到同一个项目中。

  1. 选择我们创建的鸿蒙应用,在【应用信息】页面中,将应用安装与升级修改为如下图所示。

  1. 在【版本信息】页面中,点击【版本/升级】创建新版本,在新版本页面中的【软件版本】模块下,上传我们构建的.app软件包后并勾选,在当前页面填入相关信息后即可提交审核,待审核通过后,在应用市场上就会出现了。

京东Android APP HarmonyOS 开发实践!相关推荐

  1. 京东鸿蒙版来了!京东 APP HarmonyOS 开发实践!

    点击"开发者技术前线",选择"星标????" 让一部分开发者看到未来 来自:京东零售技术 京东鸿蒙版来了〜 背景 随着鸿蒙2.0的发布,华为部分手机用户迎来鸿蒙 ...

  2. Android组件化开发实践(九):自定义Gradle插件

    本文紧接着前一章Android组件化开发实践(八):组件生命周期如何实现自动注册管理,主要讲解怎么通过自定义插件来实现组件生命周期的自动注册管理. 1. 采用groovy创建插件 新建一个Java L ...

  3. Android APP 快速开发教程(安卓)

    Android APP 快速开发教程(安卓) 前言 本篇博客从开发的角度来介绍如何开发一个Android App,需要说明一点是,这里只是提供一个如何开发一个app的思路,并不会介绍很多技术上的细节, ...

  4. 掌阅Android App插件补丁实践(ZeusPlugin)

    掌阅Android App插件补丁实践(ZeusPlugin) 遇到问题 65K方法数超限 随着应用不断迭代,业务线的扩展,应用越来越大,那么很不幸,总有一天,当你编译的时候,会遇到一个类似下面的错误 ...

  5. Android app应用开发高级进阶系列专栏解读

    1.前言 在从事android app开发的几年里,最开始接触做android 都是从app开发开始做的,在做app的这几年中把积累下来的做的一些功能,都整理出来了作为自己的技术资料,在以后开发类似的 ...

  6. 谈谈Android App混合开发

    推酷 文章 站点 主题 公开课 活动 客户端 荐 周刊 登录 谈谈Android App混合开发 时间 2015-08-25 20:13:43bxbxbai 原文  http://bxbxbai.gi ...

  7. CameraX:Android 相机库开发实践

    前言 前段时间因为工作的需要对项目中的相机模块进行了优化,我们项目中的相机模块是基于开源库 CameraView 进行开发的.那次优化主要包括两个方面,一个是相机的启动速度,另一个是相机的拍摄的清晰度 ...

  8. Android App Widget 开发

    概述 App Widget是应用程序窗口小部件(Widget),是微型的应用程序视图,它可以被嵌入到其它应用程序中(比如桌面)并接收周期性的更新.你可以通过一个App Widget Provider来 ...

  9. Android—App—必备开发组件—调试工具篇—Stetho[配合OkHttp框架使用]

    一.First and Foremost : 测试同学,在测试Android-App时,所需要的其中一个重要的技能即判断页面数据错误后,能迅速定位是服务器接口问题,还是APP逻辑问题.此时就需要知道服 ...

最新文章

  1. 我与我的专业计算机作文500字,我的好朋友——电脑
  2. android程序在调试时出现了套接字异常“java.net.SocketException: Permission denied”该如何解决...
  3. 【深度学习】利用深度学习进行时间序列预测
  4. HarmonyOS之数据管理·关系型数据库的应用
  5. IntelliJ IDEA 项目结构旁边出现 0%methods,0% lines covered 解决
  6. 教你读懂Ajax的工作原理
  7. java bitset_Java BitSet or()方法与示例
  8. 由windows/linux转向使用Mac的适应期教程
  9. 分布式mysql 不支持存储过程_分布式数据库VoltDB对存储过程的支持
  10. php监测tomcat,java_JAVA实现监测tomcat是否宕机及控制重启的方法,本文实例讲述了JAVA实现监测tom - phpStudy...
  11. 【二分匹配】【匈牙利算法即由增广路求最大匹配模板】
  12. 在LoadRunner中查找和替换字符串
  13. spark分片个数的确定及Spark内存错误(GC error)的迂回解决方式
  14. RIPv1配置(Enabling Rip)
  15. 有限差分法及matlab实现,有限差分法与matlab实现
  16. python中的snip用法_腾讯mac截图软件Snip使用教程
  17. ios VM snapshot invalid and could not be inferred from settings
  18. 程序员的真实工资是多少?
  19. 为什么下载的破解游戏和软件经常会被报毒?
  20. 什么是DFX测试.md

热门文章

  1. 在线书法培训迎来新风口
  2. 站长探讨说说之SEO文章关键词精准优化布局
  3. vlan和vlanif区别
  4. Huawei Libra数据库常用语句
  5. git commit 报错fatal: unable to access
  6. Bad attr `wx... Bad Value with message... uni-app报错合集
  7. 安卓手机来电防火墙_安卓基础知识自动化测试
  8. PHP获取前天和昨天日期
  9. Android 滑动侧边栏(Sliding Menu)第一种实现 - 1 手动滚动+自动滚动
  10. NAVIGATE领航者峰会 | 紫光云 · 服务与运营战略升级