
android
菊厂程序员每天轻松跟练刘畊宏健身操,到底怎么做到的?
陈叔叔 发表了文章 • 0 个评论 • 48 次浏览 • 2022-05-25 09:53
健身房不开,国民“运动量”却爆发式上涨。
私教都拉不回的运动自律,被刘畊宏的一套毽子操拉回来了。最近程序员阿强也在女友的打卡带动下,跟着刘教练开启了在家健身模式,线上与千万网友隔空同频共振,阿强表示,终于找到快乐运动的正确打开方式。
阿强切身体会了居家健身需求的兴起,BUT,其不足之处也渐渐浮现。
线上跟学,一套动作,统一的节奏无法适配每一个人的身体状态…长期不运动或运动量小的同学,习惯了peace模式的身体骤然接受大开大合、耗时不短的健身操,健身热情跟得上,身体和心脏也跟不上,所以,在家健身的男生女生们,需要一个贴身运动健康管家来实时监控运动状况!
收到内心发来的开发需求,阿强说干就干,使用HMS Core的运动健康服务能力,打造了一个运动管家App,开始运动后,实时监控运动时间、消耗的卡路里、心率等健康数据,帮助调整运动状态。
Demo示例
关于华为运动健康服务(HUAWEI Health Kit)
运动健康服务分为基础能力服务和扩展能力服务。
基础能力服务:Health Kit基础能力提供原子化数据开放,在获取用户对数据的授权后,应用通过接口访问运动健康数据,对用户数据进行增、删、改、查等操作,为用户提供运动健康类数据服务。
扩展能力服务: Health Kit扩展能力服务开放更多实时运动和健康数据、运动和健康解决方案场景化数据。
运动管家App相关能力则是基于Health Kit拓展能力中的功能进行开发。
开发流程
1、 开发环境要求
Android 平台的要求
安装Android Studio 3.X及以上
JDK 1.8.211及以上
应用应满足以下条件
minSdkVersion 24
targetSdkVersion 29
compileSdkVersion 29
Gradle 4.6及以上
测试应用的设备要求Android 6.0 及以上版本且装有华为运动健康App的华为手机。
2、开发步骤
完整的开发流程如下。
3、开始运动和停止运动功能实现
控制运动和获取实时运动配合使用顺序一般为:
(1)registerSportData,开始获取实时运动数据。
(2)startSport,开始运动。
(3)stopSport,停止运动。
(4)unregisterSportData,停止获取实时运动数据。
关键代码步骤
(1)开始获取实时运动数据
-
调用 HiHealthDataStore 对象的 registerSportData 方法,开始获取实时运动数据。
-
通过请求参数 HiSportDataCallback 对象,返回查询结果,结果中数据类型参考实时运动 Bundle 对象键值。
HiHealthDataStore.registerSportData(context, new HiSportDataCallback() { @Override public void onResult(int resultCode) { // 接口调用结果 Log.i(TAG, "registerSportData onResult resultCode:" + resultCode); } @Override public void onDataChanged(int state, Bundle bundle) { // 实时数据变化回调 Log.i(TAG, "registerSportData onChange state: " + state); StringBuffer stringBuffer = new StringBuffer(""); if (state == HiHealthKitConstant.SPORT_STATUS_RUNNING) { Log.i(TAG, "heart rate : " + bundle.getInt(HiHealthKitConstant.BUNDLE_KEY_HEARTRATE)); Log.i(TAG, "distance : " + bundle.getInt(HiHealthKitConstant.BUNDLE_KEY_DISTANCE)); Log.i(TAG, "duration : " + bundle.getInt(HiHealthKitConstant.BUNDLE_KEY_DURATION)); Log.i(TAG, "calorie : " + bundle.getInt(HiHealthKitConstant.BUNDLE_KEY_CALORIE)); Log.i(TAG, "totalSteps : " + bundle.getInt(HiHealthKitConstant.BUNDLE_KEY_TOTAL_STEPS)); Log.i(TAG, "totalCreep : " + bundle.getInt(HiHealthKitConstant.BUNDLE_KEY_TOTAL_CREEP)); Log.i(TAG, "totalDescent : " + bundle.getInt(HiHealthKitConstant.BUNDLE_KEY_TOTAL_DESCENT)); } }});
(2)开始运动
开始和结束运动支持的运动类型常量
-
调用 HiHealthDataStore 对象的 startSport 方法,启动相应类型的运动。
-
通过请求参数 ResultCallback 对象,返回查询结果。
// 室外跑步int sportType = HiHealthKitConstant.SPORT_TYPE_RUN;HiHealthDataStore.startSport(context, sportType, new ResultCallback() { @Override public void onResult(int resultCode, Object message) { if (resultCode == HiHealthError.SUCCESS) { Log.i(TAG, "start sport success"); } }});
(3)结束运动:
-
调用 HiHealthDataStore 对象的 stopSport 方法,停止相应类型的运动。
-
通过请求参数 ResultCallback 对象,返回查询结果。
HiHealthDataStore.stopSport(context, new ResultCallback() { @Override public void onResult(int resultCode, Object message) { if (resultCode == HiHealthError.SUCCESS) { Log.i(TAG, "stop sport success"); } }});
(4)停止获取实时运动数据:
-
调用 HiHealthDataStore 对象的 unregisterSportData 方法,停止获取实时运动数据。
-
通过请求参数 HiSportDataCallback 对象,返回查询结果。
HiHealthDataStore.unregisterSportData(context, new HiSportDataCallback() { @Override public void onResult(int resultCode) { // 接口调用结果 Log.i(TAG, "unregisterSportData onResult resultCode:" + resultCode); } @Override public void onDataChanged(int state, Bundle bundle) { // 此时不会被调用 }});
4、今日活动量查询功能实现
查询每日活动量,包含步数统计、步数详情、距离、热量及运动中高强度,这部分数据来源于手机或华为穿戴设备,需先申请开通权限,并获取用户授权,否则接口将调用失败。所需权限点此查询。
(1)使用 execQuery 接口查询用户每日活动量
1、调用 HiHealthDataStore 对象的 execQuery 方法,查询用户每日活动量。
2、通过请求参数 ResultCallback 对象,返回查询结果。
步数统计为例:
int timeout = 0;// 查询当天步数Calendar currentDate = Calendar.getInstance();currentDate.set(Calendar.HOUR_OF_DAY, 0);currentDate.set(Calendar.MINUTE, 0);currentDate.set(Calendar.SECOND, 0);long startTime = currentDate.getTimeInMillis();long endTime = System.currentTimeMillis();// 查询步数HiHealthDataQuery hiHealthDataQuery = new HiHealthDataQuery(HiHealthPointType.DATA_POINT_STEP_SUM, startTime, endTime, new HiHealthDataQueryOption());HiHealthDataStore.execQuery(context, hiHealthDataQuery, timeout, new ResultCallback() { @Override public void onResult(int resultCode, Object data) { Log.i(TAG, "query steps resultCode: " + resultCode); if (resultCode == HiHealthError.SUCCESS && data instanceof List) { List dataList = (ArrayList) data; for (Object obj : dataList) { HiHealthPointData pointData = (HiHealthPointData) obj; Log.i(TAG, "start time : " + pointData.getStartTime()); Log.i(TAG, "query steps : " + String.valueOf(pointData.getValue())); } } }});
查询数据所需参数及查询结果:
5、运动记录查询
查询30天内运动记录代码示例:
1、调用 HiHealthDataStore 对象的 execQuery 方法,查询用户运动记录。
2、通过请求参数 ResultCallback 对象,返回查询结果。
int timeout = 0; long endTime = System.currentTimeMillis(); // 查询时间范围 : 30 天 long startTime = endTime - 1000 * 60 * 60 * 24 * 30L; // 查询跑步记录 HiHealthDataQuery hiHealthDataQuery = new HiHealthDataQuery(HiHealthSetType.DATA_SET_RUN_METADATA, startTime, endTime, new HiHealthDataQueryOption()); HiHealthDataStore.execQuery(context, hiHealthDataQuery, timeout, new ResultCallback() { @Override public void onResult(int resultCode, Object data) {if (resultCode == HiHealthError.SUCCESS && data instanceof List){List dataList = (List) data; for (Object obj : dataList) { HiHealthSetData hiHealthData = (HiHealthSetData) obj; Map map = hiHealthData.getMap(); Log.i(TAG, "start time : " + hiHealthData.getStartTime()); Log.i(TAG, "total_time : " + map.get(HiHealthKitConstant.BUNDLE_KEY_TOTAL_TIME)); Log.i(TAG, "total_distance : " + map.get(HiHealthKitConstant.BUNDLE_KEY_TOTAL_DISTANCE)); Log.i(TAG, "total_calories : " + map.get(HiHealthKitConstant.BUNDLE_KEY_TOTAL_CALORIES)); Log.i(TAG, "step : " + map.get(HiHealthKitConstant.BUNDLE_KEY_STEP)); Log.i(TAG, "average_pace : " + map.get(HiHealthKitConstant.BUNDLE_KEY_AVERAGEPACE)); Log.i(TAG, "average_speed : " + map.get(HiHealthKitConstant.BUNDLE_KEY_AVERAGE_SPEED)); Log.i(TAG, "average_step_rate : " + map.get(HiHealthKitConstant.BUNDLE_KEY_AVERAGE_STEP_RATE)); Log.i(TAG, "step_distance : " + map.get(HiHealthKitConstant.BUNDLE_KEY_STEP_DISTANCE)); Log.i(TAG, "average_heart_rate : " + map.get(HiHealthKitConstant.BUNDLE_KEY_AVERAGE_HEART_RATE)); Log.i(TAG, "total_altitude : " + map.get(HiHealthKitConstant.BUNDLE_KEY_TOTAL_ALTITUDE)); Log.i(TAG, "total_descent : " + map.get(HiHealthKitConstant.BUNDLE_KEY_TOTALDESCENT)); Log.i(TAG, "data source : " + map.get(HiHealthKitConstant.BUNDLE_KEY_DATA_SOURCE)); } } } });
点此查询数据所需参数及查询结果
了解更多详情>>
访问华为开发者联盟官网
获取开发指导文档
华为移动服务开源仓库地址:GitHub、Gitee
关注我们,第一时间了解 HMS Core 最新技术资讯~
Android切换希腊语蓝牙打开失败
陈叔叔 发表了文章 • 0 个评论 • 46 次浏览 • 2022-05-23 14:17
版本:Android 9
平台:RK-PX30
问题描述:在状态栏点击蓝牙图标打开蓝牙时,发现打开失败,进入到设置里打开蓝牙却能打开成功,但是时间会比较长。
分析:
查看日志发现,执行BluetoothHci::initialize()
后,在获取到蓝牙MAC地址,就又把蓝牙关闭了。测试发现如果把蓝牙开启超时时间设置长点就可以正常打开了(由原本的4s改为10s)。
2021-10-27 14:32:30.561 495-522/system_process D/BluetoothManagerService: MESSAGE_BLUETOOTH_STATE_CHANGE: OFF > BLE_TURNING_ON2021-10-27 14:32:30.561 495-522/system_process D/BluetoothManagerService: Sending BLE State Change: OFF > BLE_TURNING_ON2021-10-27 14:32:30.561 19461-19482/com.android.bluetooth D/BluetoothAdapterService: bleOnProcessStart()2021-10-27 14:32:30.564 19461-19482/com.android.bluetooth D/BluetoothAdapterService: bleOnProcessStart() - Make Bond State Machine2021-10-27 14:32:30.565 19461-19482/com.android.bluetooth D/BluetoothBondStateMachine: make2021-10-27 14:32:30.570 19461-19497/com.android.bluetooth I/BluetoothBondStateMachine: StableState(): Entering Off State2021-10-27 14:32:30.595 19461-19461/com.android.bluetooth D/BluetoothAdapterService: getAdapterService() - returning com.android.bluetooth.btservice.AdapterService@e8fb3e72021-10-27 14:32:30.600 19461-19461/com.android.bluetooth D/BluetoothAdapterService: getAdapterService() - returning com.android.bluetooth.btservice.AdapterService@e8fb3e72021-10-27 14:32:30.613 19461-19461/com.android.bluetooth D/BluetoothAdapterService: getAdapterService() - returning com.android.bluetooth.btservice.AdapterService@e8fb3e72021-10-27 14:32:30.615 19461-19461/com.android.bluetooth D/BluetoothAdapterService: handleMessage() - Message: 22021-10-27 14:32:30.615 19461-19461/com.android.bluetooth D/BluetoothAdapterService: handleMessage() - MESSAGE_PROFILE_SERVICE_REGISTERED2021-10-27 14:32:30.616 19461-19461/com.android.bluetooth D/BluetoothAdapterService: handleMessage() - Message: 12021-10-27 14:32:30.616 19461-19461/com.android.bluetooth D/BluetoothAdapterService: handleMessage() - MESSAGE_PROFILE_SERVICE_STATE_CHANGED2021-10-27 14:32:30.628 19461-19502/com.android.bluetooth I/bt_hci: hci_initialize: IBluetoothHci::getService() returned 0x76f4876c60 (remote)2021-10-27 14:32:30.628 260-260/? I/android.hardware.bluetooth@1.0-impl: BluetoothHci::initialize() # 初始化2021-10-27 14:32:34.566 19461-19482/com.android.bluetooth D/BluetoothAdapterService: updateAdapterState() - Broadcasting state BLE_TURNING_OFF to 1 receivers.2021-10-27 14:32:34.567 495-522/system_process D/BluetoothManagerService: MESSAGE_BLUETOOTH_STATE_CHANGE: BLE_TURNING_ON > BLE_TURNING_OFF2021-10-27 14:32:34.567 495-522/system_process D/BluetoothManagerService: Sending BLE State Change: BLE_TURNING_ON > BLE_TURNING_OFF # 从log的时间来看,过了大概4s就把蓝牙状态设置为关闭,代码中超时时间就是4s2021-10-27 14:32:34.742 495-495/system_process D/BluetoothManagerService: Bluetooth Adapter address changed to 0F:DD:CC:AA:EE:0A # 调用的initialize() 4s后才返回结果,但此时状态已经设置为BLE_TURNING_OFF了2021-10-27 14:32:34.743 495-495/system_process D/BluetoothManagerService: Stored Bluetoothaddress: 0F:DD:CC:AA:EE:0A2021-10-27 14:32:34.744 495-495/system_process D/BluetoothManagerService: Bluetooth Adapter name changed to IPPhone2021-10-27 14:32:34.747 495-495/system_process D/BluetoothManagerService: Stored Bluetooth name: IPPhone2021-10-27 14:32:34.759 19461-19461/com.android.bluetooth D/BluetoothAdapterService: handleMessage() - Message: 12021-10-27 14:32:34.759 19461-19461/com.android.bluetooth D/BluetoothAdapterService: handleMessage() - MESSAGE_PROFILE_SERVICE_STATE_CHANGED2021-10-27 14:32:34.760 19461-19461/com.android.bluetooth D/BluetoothAdapterService: handleMessage() - Message: 32021-10-27 14:32:34.760 19461-19461/com.android.bluetooth D/BluetoothAdapterService: handleMessage() - MESSAGE_PROFILE_SERVICE_UNREGISTERED2021-10-27 14:32:34.761 19461-19482/com.android.bluetooth D/BluetoothAdapterService: updateAdapterState() - Broadcasting state OFF to 1 receivers.2021-10-27 14:32:34.761 495-522/system_process D/BluetoothManagerService: MESSAGE_BLUETOOTH_STATE_CHANGE: BLE_TURNING_OFF > OFF
继续分析,其他语言使用都正常,为什么只有希腊语会有出现这种情况呢?
对比正常情况,BluetoothHci::initialize()
调用后在2s内就会返回结果,因此问题就在于希腊语下BluetoothHci::initialize()
的时间过长,这种问题最简单的分析方法就是在可能有耗时较长的地方加log打印时间。
logcat本身会打印时间,如果是没有打印时间的,就需要使用其他方式计算时间了。
最后定位到源码里packages/apps/Bluetooth/src/com/android/bluetooth/Utils.java
的getAddressStringFromByte()
方法。
这个方法里面很简单,就只是调用了format()
方法,但是该方法却卡了至少2~4s的时间,加上其他步骤本身就需要接近2s,而上层BLE开启的超时时间是4s,因此很容易就导致超时而打不开。
继续跟踪format()
方法的实现:
format() # libcore/ojluni/src/main/java/java/util/Formatter.java ` toString() ` toUpperCase() # libcore/libart/src/main/java/java/lang/CaseMapper.java
最终找到根本原因是使用了希腊语的toUpperCase()
方法。
该方法实现如下:
public static String toUpperCase(Locale locale, String s, int count) { String languageCode = locale.getLanguage(); if (languageCode.equals("tr") || languageCode.equals("az") || languageCode.equals("lt")) { return ICU.toUpperCase(s, locale); } if (languageCode.equals("el")) { // 希腊语使用特殊方法处理 return EL_UPPER.get().transliterate(s); }}private static final ThreadLocal<Transliterator> EL_UPPER = new ThreadLocal<Transliterator>() { @Override protected Transliterator initialValue() { return Transliterator.getInstance("el-Upper"); }};
android使用开源转换库icu做处理,在调用希腊语转换模块的时候需要生成大量的对象,因此第一次调用会有很明显的延迟。
经过验证,如果对toUpperCase()
指定其他语言,也不会存在延迟;并且其他APP第一次调用该方法,也会有延迟。
解决方案:我们的解决方案是提前创建该对象,使用的时候就不会有延迟了。
《第一行代码:Android篇》学习笔记(一)
陈叔叔 发表了文章 • 0 个评论 • 43 次浏览 • 2022-05-11 01:47
本文和接下来的几篇文章为阅读郭霖先生所著《第一行代码:Android(篇第2版)》的学习笔记,按照书中的内容顺序进行记录,书中的Demo本人全部都做过了。
每一章节本人都做了详细的记录,以下是我学习记录(包含大量书中内容的整理和自己在学习中遇到的各种bug及解决方案),方便以后阅读和查阅。最后,非常感激郭霖先生提供这么好的书籍。
第一章 开始启程——你的第一行Android代码
欢迎你来到Android世界!
Android发展时间线:
2003年10月,Andy Rubin等人一起创办了Android公司;
2005年8月,谷歌收购了该公司,并让Andy Rubin继续负责Android项目;
2008年,谷歌终推出了Android系统的第一个版本;
Android的发展受到重重阻挠:
乔布斯自始至终认为Android抄袭iPhone的产品,剽窃了诸多iPhone的创意,并声称一定要毁掉Android;
2010年,Linux团队将基于Linux开发的Android操作系统从Linux内核主线中除名;
甲骨文则针对Android侵犯Java知识产权一事对谷歌提起了诉讼……
谷歌的开放政策:
任何手机厂商和个人都能免费获取到Android操作系统的源码,并且可以自由地使用和定制。
三星、HTC、摩托罗拉、索爱等公司都推出了各自系列的Android手机,Android市场上百花齐放。
仅仅推出两年后,Android就超过了已经霸占市场逾十年的诺基亚Symbian,成为了全球第一大智能手机操作系统。
国内的手机厂商,小米、华为、魅族等新兴品牌都推出了相当不错的Android手机,并且也获得了市场的广泛认可.
目前Android已经占据了全球智能手机操作系统70%以上的份额。
1.1 了解全貌——Android王国简介
Android从面世以来到现在已经发布了二十几个版本了。谷歌为Android王国建立了一个完整的生态系统。手机厂商、开发者、用户之间相互依存,共同推进着Android的蓬勃发展。
1.1.1 Android系统架构
Android大致可以分为四层架构:Linux内核层、系统运行库层、应用框架层和应用层。
1.Linux内核层
Android系统是基于Linux内核的,这一层为Android设备的各种硬件提供了底层的驱动,如显示驱动、音频驱动、照相机驱动、蓝牙驱动、Wi-Fi驱动、电源管理等。
2.系统运行库层
这一层通过一些C/C++库来为Android系统提供了主要的特性支持。如SQLite库提供了数据库的支持,OpenGL|ES库提供了3D绘图的支持,Webkit库提供了浏览器内核的支持等。
同样在这一层还有Android运行时库,它主要提供了一些核心库,能够允许开发者使用Java语言来编写Android应用。另外,Android运行时库中还包含了Dalvik虚拟机(5.0系统之后改为ART运行环境),它使得每一个Android应用都能运行在独立的进程当中,并且拥有一个自己的Dalvik虚拟机实例。相较于Java虚拟机,Dalvik是专门为移动设备定制的,它针对手机内存、CPU性能有限等情况做了优化处理。
3.应用框架层
这一层主要提供了构建应用程序时可能用到的各种API, Android自带的一些核心应用就是使用这些API完成的,开发者也可以通过使用这些API来构建自己的应用程序。
4.应用层
所有安装在手机上的应用程序都是属于这一层的,比如系统自带的联系人、短信等程序,或者是你从Google Play上下载的小游戏,当然还包括你自己开发的程序。
1.1.2 Android已发布的版本
2008年9月,谷歌正式发布了Android 1.0系统;
随后的几年,谷歌以惊人的速度不断地更新Android系统,2.1、2.2、2.3系统的推出使Android占据了大量的市场;
2011年2月,谷歌发布了Android 3.0系统,这个系统版本是专门为平板电脑设计的,但也是Android为数不多的比较失败的版本,推出之后一直不见什么起色;
在同年的10月,谷歌又发布了Android 4.0系统,这个版本不再对手机和平板进行差异化区分,既可以应用在手机上,也可以应用在平板上。
2014年Google I/O大会上,谷歌推出了号称史上版本改动最大的Android 5.0系统,其中使用ART运行环境替代了Dalvik虚拟机,大大提升了应用的运行速度,还提出了Material Design的概念来优化应用的界面设计。除此之外,还推出了Android Wear、Android Auto、Android TV系统,从而进军可穿戴设备、汽车、电视等全新领域。
2015年GoogleI/O大会上推出了Android 6.0系统,加入运行时权限功能;
2016年Google I/O大会上推出了Android 7.0系统,加入多窗口模式功能,这也是目前最新的Android系统版本;
查看最新的数据访问:https://developer.android.google.cn/about/dashboards/
1.1.3 Android应用开发特色
Android系统到底提供了哪些东西,可供我们开发出优秀的应用程序。
1.Android系统四大组件
(1)活动(Activity):是所有Android应用程序的门面,凡是在应用中你看得到的东西,都是放在活动中的。
(2)服务(Service):你无法看到它,但它会一直在后台默默地运行,即使用户退出了应用,服务仍然是可以继续运行的。
(3)广播接收器(Broadcast Receiver):广播接收器允许你的应用接收来自各处的广播消息,比如电话、短信等,当然你的应用同样也可以向外发出广播消息。
(4)内容提供器(Content Provider):为应用程序之间共享数据提供了可能,比如你想要读取系统电话簿中的联系人,就需要通过内容提供器来实现。
2.丰富的系统控件
Android系统为开发者提供了丰富的系统控件,使得我们可以很轻松地编写出漂亮的界面。
3.SQLite数据库
Android系统还自带了这种轻量级、运算速度极快的嵌入式关系型数据库。它不仅支持标准的SQL语法,还可以通过Android封装好的API进行操作,让存储和读取数据变得非常方便。
4.强大的多媒体
Android系统还提供了丰富的多媒体服务,如音乐、视频、录音、拍照、闹铃,等等,这一切你都可以在程序中通过代码进行控制,让你的应用变得更加丰富多彩。
5.地理位置定位
现在的Android手机都内置有GPS,走到哪儿都可以定位到自己的位置,发挥你的想象就可以做出创意十足的应用,如果再结合功能强大的地图功能,LBS这一领域潜力无限。
1.2 手把手带你搭建开发环境
1.2.1 准备所需要的工具
-
JDK
JDK是Java语言的软件开发工具包,它包含了Java的运行环境、工具集合、基础类库等内容。
-
Android SDK
Android SDK是谷歌提供的Android开发工具包,在开发Android程序时,我们需要通过引入该工具包,来使用Android相关的API。
-
Android Studio
在很早之前,Android项目都是用Eclipse来开发的,安装ADT插件后就可以用来开发Android程序了。
在2013年的时候,谷歌推出了一款官方的IDE工具Android Studio,由于不再是以插件的形式存在,Android Studio在开发Android程序方面要远比Eclipse强大和方便得多。
1.2.2 搭建开发环境
Android官网下载最新的开发工具,下载地址是:https://developer.android.google.cn/studio/index.html
百度网盘去下载,下载地址是:https://pan.baidu.com/s/1nuABMDb
注意:现在点击Finish按钮来启动Android Studio,一开始会让你选择是否导入之前Android Studio版本的配置,由于这是我们首次安装,这里选择不导入就可以了。
在点击Finish按钮,配置工作就全部完成了。然后Android Studio会尝试联网下载一些更新,等待更新完成后再点击Finish按钮就会进入Android Studio的欢迎界面
目前为止,Android开发环境就已经全部搭建完成了。
1.3 创建你的第一个Android项目
1.3.1 创建HelloWorld项目(注意:Language 选择Java)
-
在Android Studio的欢迎界面点击Start a newAndroid Studio project;
-
pplication name表示应用名称,此应用安装到手机之后会在手机上显示该名称;
-
Company Domain表示公司域名,如果是个人开发者;
-
Package name表示项目的包名,Android系统就是通过包名来区分不同应用程序的,因此包名一定要具有唯一性;
-
AndroidStudio会根据应用名称和公司域名来自动帮我们生成合适的包名,如果你不想使用默认生成的包名,也可以点击右侧的Edit按钮自行修改;
-
Project location表示项目代码存放的位置,如果没有特殊要求的话,这里也保持默认就可以了;
-
Android 4.0以上的系统已经占据了超过98%的Android市场份额,因此这里我们将Minimum SDK指定成API 15就可以了;
-
Wear、TV和Android Auto这几个选项分别是用于开发可穿戴设备、电视和汽车程序的;
-
Android Studio提供了很多种内置模板,刚开始学习,这里直接选择Empty Activity来创建一个空的活动就可以了;
记得:配置环境变量,
ANDROID_HONE
C:用户xxxxAppDataLocalAndroidSdkplatform-tools
1.3.2 启动模拟器
由于Android Studio自动为我们生成了很多东西,你现在不需要编写任何代码,HelloWorld项目就已经可以运行了。但是在此之前还必须要有一个运行的载体,可以是一部Android手机,也可以是Android模拟器。
暂时先使用模拟器来运行程序:
- 创建一个Android模拟器,观察Android Studio顶部工具栏中的图标
- Virtual Device Configuration
- 选择创建Nexus 5X这台设备的模拟器
- 选择模拟器所使用的操作系统版本,Download Android 11.0系统。继续点击Next
-
确认模拟器配置
以对模拟器的一些配置进行确认,比如说指定模拟器的名字、分辨率、横竖屏等信息,如果没有特殊需求的话,全部保持默认就可以了。
- 完成模拟器的创建,然后会弹出如图
- 点击Actions栏目中最左边的三角形按钮即可启动模拟器
1.3.3 运行HelloWorld
Android Studio顶部工具栏中的图标,三角形按钮是用来运行项目的。
运行结果:(像使用手机一样,按住鼠标向上划、向下划),Android Studio太智能了
1.3.4 分析你的第一个Android程序
- HelloWorld项目(Android模式的项目结构)
任何一个新建的项目都会默认使用Android模式的项目结构,但这并不是项目真实的目录结构,而是被AndroidStudio转换过的。这种项目结构简洁明了,适合进行快速开发,但是对于新手来说可能并不易于理解。
- 点击图中的Android区域可以切换项目结构模式
- 将项目结构模式切换成Project,这就是项目真实的目录结构
(1).gradle和.idea
这两个目录下放置的都是AndroidStudio自动生成的一些文件,我们无须关心,也不要去手动编辑。
(2)app
项目中的代码、资源等内容几乎都是放置在这个目录下的,我们后面的开发工作也基本都是在这个目录下进行的。
(3)build
它主要包含了一些在编译时自动生成的文件。
(4)gradle
包含了gradle wrapper的配置文件,使用gradle wrapper的方式不需要提前将gradle下载好,而是会自动根据本地的缓存情况决定是否需要联网下载gradle。
Android Studio默认没有启用gradle wrapper的方式,如果需要打开,可以点击Android Studio导航栏→File→Settings→Build, Execution,Deployment→Gradle,进行配置更改。
(5).gitignore
这个文件是用来将指定的目录或文件排除在版本控制之外的。
(6)build.gradle
项目全局的gradle构建脚本,通常这个文件中的内容是不需要修改的。
(7)gradle.properties
这个文件是全局的gradle配置文件,在这里配置的属性将会影响到项目中所有的gradle编译脚本。
(8)gradlew和gradlew.bat
这两个文件是用来在命令行界面中执行gradle命令的,其中gradlew是在Linux或Mac系统中使用的,gradlew.bat是在Windows系统中使用的。
(9)HelloWorld.iml
iml文件是所有IntelliJ IDEA项目都会自动生成的一个文件(Android Studio是基于IntelliJ IDEA开发的),用于标识这是一个IntelliJ IDEA项目,我们不需要修改这个文件中的任何内容。
(10)local.properties
这个文件用于指定本机中的AndroidSDK路径,通常内容都是自动生成的,我们并不需要修改。
除非你本机中的Android SDK位置发生了变化,那么就将这个文件中的路径改成新的位置即可。
(11)settings.gradle
这个文件用于指定项目中所有引入的模块。由于HelloWorld项目中就只有一个app模块,因此该文件中也就只引入了app这一个模块。
通常情况下模块的引入都是自动完成的,需要我们手动去修改这个文件的场景可能比较少。
整个项目的外层目录结构,除了app目录之外,大多数的文件和目录都是自动生成的,我们并不需要进行修改。下面我们就来对app目录下的内容进行更为详细的分析。
(1)build
这个目录和外层的build目录类似,主要也是包含了一些在编译时自动生成的文件,不过它里面的内容会更加更杂,我们不需要过多关心。
(2)libs
项目中使用到了第三方jar包,就需要把这些jar包都放在libs目录下,放在这个目录下的jar包都会被自动添加到构建路径里去。
(3)androidTest
用来编写Android Test测试用例的,可以对项目进行一些自动化测试。
(4)java
所有Java代码的地方,展开该目录,你将看到我们刚才创建的HelloWorldActivity文件就在里面。
(5)res
你在项目中使用到的所有图片、布局、字符串等资源都要存放在这个目录下。这个目录下还有很多子目录,图片放在drawable目录下,布局放在layout目录下,字符串放在values目录下,所以你不用担心会把整个res目录弄得乱糟糟的。
(6)AndroidManifest.xml
整个Android项目的配置文件,程序中定义的所有四大组件都需要在这个文件里注册,另外还可以在这个文件中给应用程序添加权限声明。
<?xml version="1.0" encoding="utf-8"?><manifest xmlns:android="http://schemas.android.com/apkes/android" package="com.zhouzhou"> <application android:allowBackup="true" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:roundIcon="@mipmap/ic_launcher_round" android:supportsRtl="true" android:theme="@style/Theme.HelloWorld"> <activity android:name=".MainActivity" android:exported="true"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> </application></manifest>
(7)test
用来编写Unit Test测试用例的,是对项目进行自动化测试的另一种方式。
(8).gitignore
用于将app模块内的指定的目录或文件排除在版本控制之外,作用和外层的.gitignore文件类似。
(9)app.iml
IntelliJ IDEA项目自动生成的文件,我们不需要关心或修改这个文件中的内容。
(10)build.gradle
app模块的gradle构建脚本,这个文件中会指定很多项目构建相关的配置。
(11)proguard-rules.pro
用于指定项目代码的混淆规则,当代码开发完成后打成安装包文件,如果不希望代码被别人破解,通常会将代码进行混淆,从而让破解者难以阅读。
详细解析:
<activity android:name=".MainActivity" android:exported="true"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter></activity>
这段代码表示对MainActivity这个活动进行注册,没有在AndroidManifest.xml里注册的活动是不能使用的。
intent-filter里的两行代码非常重要:
表示MainActivity是这个项目的主活动,在手机上点击应用图标,首先启动的就是这个活动。
而在Android四大组件的时候说过,活动是Android应用程序的门面,凡是在应用中你看得到的东西,都是放在活动中的。
打开MainActivity
代码如下所示:
package com.zhouzhou;import androidx.appcompat.app.AppCompatActivity;import android.os.Bundle;//MainActivity是继承自AppCompatActivity的,这是一种向下兼容的Activity,可以将Activity在各个系统版本中增加的特性和功能最低兼容到Android 2.1系统。public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); }}
- Activity是Android系统提供的一个活动基类,我们项目中所有的活动都必须继承它或者它的子类才能拥有活动的特性(AppCompatActivity是Activity的子类)。
- MainActivity中有一个onCreate()方法,这个方法是一个活动被创建时必定要执行的方法,其中只有两行代码,并且没有Hello World!的字样。
- Android程序的设计讲究逻辑和视图分离,因此是不推荐在活动中直接编写界面的,更加通用的一种做法是,在布局文件中编写界面,然后在活动中引入进来。
- 在onCreate()方法的第二行调用了setContentView()方法,就是这个方法给当前的活动引入了一个activity_main布局,那HelloWorld!一定就是在这里定义的了!
布局文件都是定义在res/layout目录下的,当你展开layout目录,你会看到activity_main.xml这个文件。(我是直接在代码上按住Ctrl键,鼠标定位到setContentView(R.layout.activity_main)中的activity_main
,点击即可进入)
注意:遇到一个小插曲,打开activity_main.xml显示的是视图,不是代码?
快捷键:Alt+Shift+左右箭头(第一个图标是显示代码)
activity_main.xml代码如下:
<?xml version="1.0" encoding="utf-8"?><androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apkes/android" xmlns:app="http://schemas.android.com/apkes-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".MainActivity"> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Hello World!" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintRight_toRightOf="parent" app:layout_constraintTop_toTopOf="parent" /></androidx.constraintlayout.widget.ConstraintLayout>
终于找到了,原来就是通过android:text="Hello World!"
这句代码定义的Hello World!的字样。
1.3.5 详解项目中的资源
(1)res目录
归纳一下:
- 所有以drawable开头的文件夹都是用来放图片的
- 所有以mipmap开头的文件夹都是用来放应用图标的
- 所有以values开头的文件夹都是用来放字符串、样式、颜色等配置的
- layout文件夹是用来放布局文件的
注:之所以有这么多mipmap开头的文件夹,是为了让程序能够更好地兼容各种设备。
drawable文件夹也是相同的道理。Android Studio没有帮我们自动生成,但是我们应该自己创建drawable-hdpi、drawable-xhdpi、drawable-xxhdpi等文件夹。
在制作程序的时候最好能够给同一张图片提供几个不同分辨率的版本,分别放在这些文件夹下,然后当程序运行的时候,会自动根据当前运行设备分辨率的高低选择加载哪个文件夹下的图片。
当然这只是理想情况,更多的时候美工只会提供给我们一份图片,这时你就把所有图片都放在drawable-xxhdpi文件夹下就好了。
(2)strings.xml文件
打开res/values/strings.xml文件:
<resources> <string name="app_name">HelloWorld</string><esources>
这里定义了一个应用程序名的字符串,我们有以下两种方式来引用它:
- 在代码中通过R.string.app_name可以获得该字符串的引用;
- 在XML中通过@string/app_name可以获得该字符串的引用
其中string部分是可以替换的,如果是引用的图片资源就可以替换成drawable,如果是引用的应用图标就可以替换成mipmap,如果是引用的布局文件就可以替换成layout,以此类推。
语法练习:打开AndroidManifest.xml
......<application android:allowBackup="true" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:roundIcon="@mipmap/ic_launcher_round" android:supportsRtl="true" android:theme="@style/Theme.HelloWorld"> ...... </application>
1.3.6 详解build.gradle文件
不同于Eclipse, Android Studio是采用Gradle来构建项目的。Gradle是一个非常先进的项目构建工具,它使用了一种基于Groovy的领域特定语言(DSL)来声明项目设置,摒弃了传统基于XML(如Ant和Maven)的各种烦琐配置。
HelloWorld项目中有两个build.gradle文件,一个是在最外层目录下的,一个是在app目录下的。这两个文件对构建AndroidStudio项目都起到了至关重要的作用。
- 最外层目录下的build.gradle文件:
// Top-level build file where you can add configuration options common to all sub-projects/modules.顶层构建文件,您可以在其中添加所有子项目/模块通用的配置选项。plugins { id 'com.android.application' version '7.1.0' apply false id 'com.android.library' version '7.1.0' apply false}task clean(type: Delete) { delete rootProject.buildDir}
- app目录下的build.gradle文件:
plugins { id 'com.android.application' /** *com.android.application表示这是一个应用程序模块 *com.android.library表示这是一个库模块 *应用程序模块和库模块的最大区别在于,一个是可以直接运行的,一个只能作为代码库依附于别的应用程序模块来运行。 **/}//下面是一个大的android闭包,可以配置项目构建的各种属性。android { //compileSdk用于指定项目的编译版本,32表示使用Android 11.0系统的SDK编译(24表示使用Android 7.0系统的SDK编译)。 compileSdk 32 //在android闭包中又嵌套了一个defaultConfig闭包 defaultConfig { //包名,后期想改可以在这里更改 applicationId "com.zhouzhou" //minSdk用于指定项目最低兼容的Android系统版本 minSdk 21 //targetSdk指定的值表示你在该目标版本上已经做过了充分的测试,系统将会为你的应用程序启用一些最新的功能和特性。 targetSdk 32 //versionCode用于指定项目的版本号 versionCode 1 //versionName用于指定项目的版本名,和versionCode一样,在生成安装文件的时候非常重要 versionName "1.0" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" } //buildTypes闭包,用于指定生成安装文件的相关配置,通常只会有两个子闭包,一个是debug,一个是release。release闭包用于指定生成正式版安装文件的配置。另外,debug闭包是可以忽略不写的,因此我们看到上面的代码中就只有一个release闭包。 buildTypes { release { //minifyEnabled用于指定是否对项目的代码进行混淆,true表示混淆,false表示不混淆。 minifyEnabled false /** *第一个proguard-android.txt是在Android SDK目录下的,里面是所有项目通用的混淆规则。 *第二个proguard-rules.pro是在当前项目的根目录下的,里面可以编写当前项目特有的混淆规则。 *需要注意的是,通过Android Studio直接运行项目生成的都是测试版安装文件,关于如何生成正式版安装文件我们将会在第15章中学习。 **/ proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' } } compileOptions { sourceCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8 }}//dependencies闭包,它可以指定当前项目所有的依赖关系。通常Android Studio项目一共有3种依赖方式:本地依赖、库依赖和远程依赖。//本地依赖可以对本地的Jar包或目录添加依赖关系,库依赖可以对项目中的库模块添加依赖关系,远程依赖则可以对jcenter库上的开源项目添加依赖关系。dependencies { implementation 'androidx.appcompat:appcompat:1.3.0' implementation 'com.google.android.material:material:1.4.0' implementation 'androidx.constraintlayout:constraintlayout:2.0.4' testImplementation 'junit:junit:4.13.2' androidTestImplementation 'androidx.test.ext:junit:1.1.3' androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'}
1.4 前行必备——掌握日志工具的使用
Android中日志工具的使用方法,这对以后的Android开发之旅会有极大的帮助。
1.4.1 使用Android的日志工具Log
Android中的日志工具类是Log(android.util.Log),这个类中提供了如下5个方法来供我们打印日志。
-
Log.v()
用于打印那些最为琐碎的、意义最小的日志信息。对应级别verbose,是Android日志里面级别最低的一种。
-
Log.d()
用于打印一些调试信息,这些信息对你调试程序和分析问题应该是有帮助的。对应级别debug,比verbose高一级。
-
Log.i()
用于打印一些比较重要的数据,这些数据应该是你非常想看到的、可以帮你分析用户行为数据。对应级别info,比debug高一级。
-
Log.w()
用于打印一些警告信息,提示程序在这个地方可能会有潜在的风险,最好去修复一下这些出现警告的地方。对应级别warn,比info高一级。
-
Log.e()
用于打印程序中的错误信息,比如程序进入到了catch语句当中。当有错误信息打印出来的时候,一般都代表你的程序出现严重问题了,必须尽快修复。对应级别error,比warn高一级。
练习:打开MainActivity,在onCreate()方法中添加一行打印日志的语句,如下所示:
package com.zhouzhou;import androidx.appcompat.app.AppCompatActivity;import android.os.Bundle;import android.util.Log;public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); Log.d("MainActivity","onCreate execute"); }}
小插曲:No connected device 解决:以管理员的方式运行AS
解决问题的博客地址:https://blog.csdn.net/qq_35605213/article/details/93891176
Log.d("MainActivity","onCreate execute");传入两个参数:第一个参数是tag,一般传入当前的类名就好,主要用于对打印信息进行过滤;第二个参数是msg,即想要打印的具体的内容。
1.4.2 为什么使用Log而不使用System.out
在真正的项目开发中,是极度不建议使用System.out.println()方法的!如果你在公司的项目中经常使用这个方法,就很有可能要挨骂了。
这个方法除了使用方便一点之外,其他就一无是处了。缺点太多了,比如日志打印不可控制、打印时间无法确定、不能添加过滤器、日志没有级别区分……Log对以上谈不上绝对好,但是已经做的相当不错了。
除了Log.v()、Log.d()、Log.i()、Log.w()、Log.w()之外,logcat中还能很轻松地添加过滤器:
目前只有3个过滤器:
- Show only selected application表示只显示当前选中程序的日志;
- Firebase是谷歌提供的一个分析工具,我们可以不用管它;
- NoFilters相当于没有过滤器,会把所有的日志都显示出来;
我们也可以自己添加一个过滤器试试:
点击图Edit Filter Configuration,会弹出一个过滤器配置界面。给过滤器起名叫Myfilter,并且让它对名为data的tag进行过滤,尝试在onCreate()方法中把打印日志的语句改成Log.d("data", "onCreate execute"),然后再次运行程序,你就会在data过滤器下看到这行日志了。
logcat中的日志级别控制,有5个方法:
日志级别控制的好处就是,你可以很快地找到你所关心的那些日志。最后,看一下关键字过滤。如果使用过滤器加日志级别控制还是不能锁定到你想查看的日志内容的话,那么还可以通过关键字进行进一步的过滤。
输入框里输入关键字的内容,这样只有符合关键字条件的日志才会显示出来,从而能够快速定位到任何你想查看的日志。另外还有一点需要注意,关键字过滤是支持正则表达式的,有了这个特性,我们就可以构建出更加丰富的过滤条件。
日调用量超600亿次,HMS Core HiAI Foundation助力AI应用高效开发
陈叔叔 发表了文章 • 0 个评论 • 42 次浏览 • 2022-05-09 09:32
随着新技术的不断演进,人工智能已经广泛地应用到教育、金融、物流、零售、交通、医疗等各个领域。而在AI高速发展的当下,高效开发变得更为重要,如何将创意想法与AI技术深度融合,迅速转化为可落地的AI应用,是开发者在激烈竞争中制胜的关键。
为了助力开发者快速上线AI业务,为消费者提供创新AI体验,HMS Core HiAI Foundation将HiAI 生态中的底层硬件能力开放,为开发者提供了300多个具备业界优质模型兼容性的AI算子,让开发者更简单快捷地应用华为AI能力,快速构建智能AI应用。
此外,HUAWEI HiAI Foundation还推出端云协同、Model Zoo、自主调优工具包、多IP深度协同等多种解决方案和能力,为众多业务场景打造更高性能更低功耗的计算环境,助力开发者快速开发和部署AI应用。
五大性能优势,助力开发者高效灵活开发
• 端云协同:针对新业务场景算子以及已有典型业务场景算子,提供性能优化、快速升级平台能力的解决方案
当前,AI业务和算法模型都在持续快速演进,AI计算平台适配新业务和新算法快速升级成为难题。端云协同具备可变的计算框架、广适的模型结构,助力开发者快速支持新模型、上线新业务,让消费者快速获得端侧AI带来的体验提升。
• Model Zoo:辅助开发者优化模型结构,更好地利用NPU加速优势
开发者在业务开发过程中,为了更大程度利用底层算力,需要针对底层硬件结构做一些模型调整,这个过程可能会出现效率低,资源利用不足的情况。HiAI Foundation打造Model Zoo,将NPU友好的模型结构、Backbone、算子放进去,供开发者自行挑选,辅助其优化模型结构,更好地发挥麒麟芯片NPU的加速优势。
• 模型量化工具包:让开发者的App更快、更小
正常情况下,开发者训练的模型是32bit,这样的模型计算精度固然高,但对手机功耗和内存的要求也比较高。HiAI Foundation为开发者提供模型量化工具包,在满足计算精度的前提下,可以把原始模型直接量化为更小、更轻便且更适合NPU结构的低比特模型,无需开发者做二次调整,从而节省手机空间,降低计算资源的消耗。
HiAI Foundation模型量化工具包
• 网络结构搜索工具包:让网络设计更简单、更有效
网络结构搜索工具包支持多种类型的网络结构搜索任务,包含分类,检测和分割。通过精度,性能目标牵引,协同硬件信息通过最优化搜索算法获得最优的网络结构,得到最佳的性能提升。网络结构搜索工具包支持多种主流训练框架使能,包含:caffe,tensorflow,pytorch。同时针对多种主流硬件平台具有算力和时延建模的能力。
HiAI Foundation网络结构搜索工具包
• 多IP深度协同:各计算单元共用DDR内存,提升性能降低功耗
HiAI Foundation在硬件裸算力开放的同时,还具备硬件底层多IP深度协同的优势。多IP深度协同的好处在于,CPU、NPU、ISP、GPU等各计算IP可以共用DDR内存,最大程度减少IP之间拷贝数据流转的开销,既可提升性能又能降低功耗。
从平台架构来看,HMS Core HiAI Foundation衔接智慧业务和底层硬件能力,上层支持MNN、TNN、华为MindSpore Lite、Paddle Lite、KwaiNN等合作伙伴的框架对接,利用推理加速平台(Foundation DDK)和异构计算平台(Foundation HCL)将AI任务调入NPU/CPU/GPU/DSP等IP中进行计算,赋能手机、平板、智慧屏、车机、手表等终端设备,将更多好玩酷炫的AI应用带到消费者面前。
HiAI Foundation 开放架构
日调用量突破600亿,引领端侧AI行业标准构筑
如今,AI技术在端侧的应用越来越广泛,语音识别、图像识别、图像分割、图像超分、人脸识别、文字识别等已经成为全民通用的技术,消费者期待更好玩的AI应用出现,希望获得更优质的AI应用体验。HiAI Foundation为AI应用开发提供了基础保障,让开发者能够突破性能瓶颈,提高开发效率,节省计算资源,更好地投入到AI玩法的研究与落地中,满足消费者对智慧生活的美好期待。
据统计, HiAI Foundation自2018年开放以来,以高性能、低功耗、高易用性收获越来越多的开发者青睐,短短几年间日调用量从100万+增长到600亿+(数据来自参与体验改进计划的用户)。爱奇艺、美颜相机、抖音、剪映、快手、优酷等头部应用已先后接入HiAI Foundation,利用端侧AI强大的计算能力,为用户打造智能新体验。
HiAI Foundation日调用量突破600亿+
为了助力端侧AI行业繁荣发展,HiAI Foundation在优化平台性能的同时,已加入AI标准化组织AITISA(人工智能产业技术创新战略联盟),并参与端侧AI标准草案撰写,共同构筑AI行业标准,目前该草案已进入标准定稿审核阶段。
更多HiAI Foundation相关信息,请关注5月24日HUAWEI Developer Day(简称HDD)-HiAI Foundation主题演讲,你也可以登录华为开发者联盟官网>HMS Core>HiAI Foundation查看。
了解更多详情>>
访问华为开发者联盟官网
获取开发指导文档
华为移动服务开源仓库地址:GitHub、Gitee
关注我们,第一时间了解 HMS Core 最新技术资讯~
RxJava + Retrofit源码解析
陈叔叔 发表了文章 • 0 个评论 • 40 次浏览 • 2022-04-24 21:20
RxJava + Retrofit怎么请求网络,具体的用法这里就不讲了,本文只讲一些重点源码。
版本如下:
okhttp : "com.squareup.okhttp3:okhttp:3.10.0",okhttp3_integration : "com.github.bumptech.glide:okhttp3-integration:1.4.0@aar",retrofit : "com.squareup.retrofit2:retrofit:2.4.0",converter_gson : "com.squareup.retrofit2:converter-gson:2.3.0",converter_scalars : "com.squareup.retrofit2:converter-scalars:2.3.0",converter_protobuf: "com.squareup.retrofit2:converter-protobuf:2.3.0",adapter_rxjava2 : "com.squareup.retrofit2:adapter-rxjava2:2.2.0",logging_interceptor : "com.squareup.okhttp3:logging-interceptor:3.10.0",rxjava : "io.reactivex.rxjava2:rxjava:2.1.12",rxandroid : "io.reactivex.rxjava2:rxandroid:2.0.2",
一、首先关于Retrofit的初始化:
private void initRetrofit() { ExtensionRegistry extensionRegistry = ExtensionRegistry.newInstance(); retrofit = new Retrofit.Builder() .baseUrl(baseUrl) //设置地址 .client(client.build()) //设置自定义的OkHttpClient .addConverterFactory(ProtoConverterFactory.createWithRegistry(extensionRegistry)) .addConverterFactory(StringConverterFactory.create()) .addConverterFactory(GsonConverterFactory.create(buildGson())) .addCallAdapterFactory(RxJava2CallAdapterFactory.create()) .build(); service = retrofit.create(ApiService.class);}
.addConverterFactory(ProtoConverterFactory.createWithRegistry(extensionRegistry))
.addConverterFactory(StringConverterFactory.create())
.addConverterFactory(GsonConverterFactory.create(buildGson()))
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
添加了数据转换器与请求适配器。
Retrofit的初始化采用了Builder模式。
Retrofit.Builder()这一步,获取了一个平台,肯定就是Android()了,后面有地方会用到。
Builder(Platform platform) { this.platform = platform;}public Builder() { this(Platform.get());}class Platform { private static final Platform PLATFORM = findPlatform(); static Platform get() { return PLATFORM; } private static Platform findPlatform() { try { Class.forName("android.os.Build"); if (Build.VERSION.SDK_INT != 0) { return new Android(); } } catch (ClassNotFoundException ignored) { } try { Class.forName("java.util.Optional"); return new Java8(); } catch (ClassNotFoundException ignored) { } return new Platform(); }}
在看最后的build();方法:
public Retrofit build() { if (baseUrl == null) { throw new IllegalStateException("Base URL required."); } okhttp3.Call.Factory callFactory = this.callFactory; if (callFactory == null) { callFactory = new OkHttpClient(); } Executor callbackExecutor = this.callbackExecutor; if (callbackExecutor == null) { callbackExecutor = platform.defaultCallbackExecutor(); } // Make a defensive copy of the adapters and add the default Call adapter. List<CallAdapter.Factory> callAdapterFactories = new ArrayList<>(this.callAdapterFactories); callAdapterFactories.add(platform.defaultCallAdapterFactory(callbackExecutor)); // Make a defensive copy of the converters. List<Converter.Factory> converterFactories = new ArrayList<>(1 + this.converterFactories.size()); // Add the built-in converter factory first. This prevents overriding its behavior but also // ensures correct behavior when using converters that consume all types. converterFactories.add(new BuiltInConverters()); converterFactories.addAll(this.converterFactories); return new Retrofit(callFactory, baseUrl, unmodifiableList(converterFactories), unmodifiableList(callAdapterFactories), callbackExecutor, validateEagerly);}
1、如果没有传入我们自定义的OkHttpClient,那么便会使用默认的。
2、如果没有设置自定义的回调执行器,那么便会是用默认的platform.defaultCallbackExecutor();点进入可以发现回调是默认在主线程中的:
static class Android extends Platform { @Override public Executor defaultCallbackExecutor() { return new MainThreadExecutor(); } @Override CallAdapter.Factory defaultCallAdapterFactory(@Nullable Executor callbackExecutor) { if (callbackExecutor == null) throw new AssertionError(); return new ExecutorCallAdapterFactory(callbackExecutor); } static class MainThreadExecutor implements Executor { private final Handler handler = new Handler(Looper.getMainLooper()); @Override public void execute(Runnable r) { handler.post(r); } }}
3、把我们设置的请求适配器添加进入,然后再添加一个默认的请求适配器。
4、添加进入一个默认的数据转换器,然后再被我们设置的数据转换器添加进去。
二、初始化好Retrofit后,再来看这一句:
service = retrofit.create(ApiService.class);
ApiService是一个接口,里面方法如下:
@GETObservable<ResponseBody> doGet(@Url String url, @HeaderMap Map<String, String> headers, @QueryMap Map<String, String> map);
这个create方法可以说是核心,它运用的是动态代理。
@SuppressWarnings("unchecked") // Single-interface proxy creation guarded by parameter safety.public <T> T create(final Class<T> service) { Utils.validateServiceInterface(service); if (validateEagerly) { eagerlyValidateMethods(service); } return (T) Proxy.newProxyInstance(service.getClassLoader(), new Class<?>[] { service }, new InvocationHandler() { private final Platform platform = Platform.get(); @Override public Object invoke(Object proxy, Method method, @Nullable Object[] args) throws Throwable { // If the method is a method from Object then defer to normal invocation. if (method.getDeclaringClass() == Object.class) { return method.invoke(this, args); } if (platform.isDefaultMethod(method)) { return platform.invokeDefaultMethod(method, service, proxy, args); } ServiceMethod<Object, Object> serviceMethod = (ServiceMethod<Object, Object>) loadServiceMethod(method); OkHttpCall<Object> okHttpCall = new OkHttpCall<>(serviceMethod, args); return serviceMethod.adapt(okHttpCall); } });}
1、首先检测这是否是一个接口,只有接口才能对它进行动态代理。
2、是否需要对接口里面的方法进行初始化预加载,是的话便进行,这个与下面的有点重复,直接讲下面的。
3、return后面的语句便是动态代理的地方,它会代理接口的所有方法,也就是说,当我们调用ApiService的方法的时候,会被拦截,然后走到inoke这个方法做我们自己的操作。
关于动态代理,后面会单独讲。
4、接下来边看invoke方法:
(1)、首先判断该方法是否为Object这个类的方法,如果是,不拦截它,让他走原来的方法。
(2)、platform为Android,platform.isDefaultMethod(method)返回false,不用管它。
(3)、ServiceMethod<Object, Object> serviceMethod =(ServiceMethod<Object, Object>) loadServiceMethod(method);拿到接口的方法,对接口的方法进行解析,比如获取注解,参数之类,构造自己的serviceMethod
(4)、初始化OkHttpCall
(5)、调用serviceMethod.adapt(okHttpCall)进行请求(因为采用的是RxJava,所以这里并不会立即请求,只有被订阅的时候才会,等会会讲)
三、loadServiceMethod(method)方法:
构造自己的serviceMethod 也采用了Builder模式。
进入这个方法后,重点的一句:
result = new ServiceMethod.Builder<>(this, method).build();
先看:
Builder(Retrofit retrofit, Method method) { this.retrofit = retrofit; this.method = method; this.methodAnnotations = method.getAnnotations(); this.parameterTypes = method.getGenericParameterTypes(); this.parameterAnnotationsArray = method.getParameterAnnotations();}
注:我们这里以前面定义的方法来讲解:
@GETObservable<ResponseBody> doGet(@Url String url, @HeaderMap Map<String, String> headers, @QueryMap Map<String, String> map);
1、持有retrofit与原始的method对象。
2、获取方法上的注解,获取到的为:
3、获取参数类型,获取到的为:
4、获取参数上面的的注解,获取到的为:
再看build()方法:
public ServiceMethod build() { callAdapter = createCallAdapter(); responseType = callAdapter.responseType(); if (responseType == Response.class || responseType == okhttp3.Response.class) { throw methodError("'" + Utils.getRawType(responseType).getName() + "' is not a valid response body type. Did you mean ResponseBody?"); } responseConverter = createResponseConverter(); for (Annotation annotation : methodAnnotations) { parseMethodAnnotation(annotation); } if (httpMethod == null) { throw methodError("HTTP method annotation is required (e.g., @GET, @POST, etc.)."); } if (!hasBody) { if (isMultipart) { throw methodError( "Multipart can only be specified on HTTP methods with request body (e.g., @POST)."); } if (isFormEncoded) { throw methodError("FormUrlEncoded can only be specified on HTTP methods with " + "request body (e.g., @POST)."); } } int parameterCount = parameterAnnotationsArray.length; parameterHandlers = new ParameterHandler<?>[parameterCount]; for (int p = 0; p < parameterCount; p++) { Type parameterType = parameterTypes[p]; if (Utils.hasUnresolvableType(parameterType)) { throw parameterError(p, "Parameter type must not include a type variable or wildcard: %s", parameterType); } Annotation[] parameterAnnotations = parameterAnnotationsArray[p]; if (parameterAnnotations == null) { throw parameterError(p, "No Retrofit annotation found."); } parameterHandlers[p] = parseParameter(p, parameterType, parameterAnnotations); } if (relativeUrl == null && !gotUrl) { throw methodError("Missing either @%s URL or @Url parameter.", httpMethod); } if (!isFormEncoded && !isMultipart && !hasBody && gotBody) { throw methodError("Non-body HTTP method cannot contain @Body."); } if (isFormEncoded && !gotField) { throw methodError("Form-encoded method must contain at least one @Field."); } if (isMultipart && !gotPart) { throw methodError("Multipart method must contain at least one @Part."); } return new ServiceMethod<>(this);}
1、首先获取请求适配器。
2、创建请求结果的转换器。
3、对方法上的注解进行解析。
4、构造ParameterHandler数组。
5、对一些异常的判断。
四、我们接下来对每一步进行讲解。
1、首先获取请求适配器:
private CallAdapter<T, R> createCallAdapter() { Type returnType = method.getGenericReturnType(); if (Utils.hasUnresolvableType(returnType)) { throw methodError( "Method return type must not include a type variable or wildcard: %s", returnType); } if (returnType == void.class) { throw methodError("Service methods cannot return void."); } Annotation[] annotations = method.getAnnotations(); try { //noinspection unchecked return (CallAdapter<T, R>) retrofit.callAdapter(returnType, annotations); } catch (RuntimeException e) { // Wide exception range because factories are user code. throw methodError(e, "Unable to create call adapter for %s", returnType); }}
(1)、获取方法的返回类型,返回类型不能是void
(2)、获取方法上的注解。
(3)、调用retrofit.callAdapter(returnType, annotations)方法获取请求的适配器。(我们之前设置的请求适配器都在retrofit对象中)
里面关键的一步为:
int start = callAdapterFactories.indexOf(skipPast) + 1;for (int i = start, count = callAdapterFactories.size(); i < count; i++) { CallAdapter<?, ?> adapter = callAdapterFactories.get(i).get(returnType, annotations, this); if (adapter != null) { return adapter; }}
skipPast为null,所以start为0;
遍历我们之前设置给它的请求适配器,根据返回类型与方法上的注解去找,找到了便返回。(我们这里获取到的callAdapter为RxJava2CallAdapter)
2、创建请求结果的转换器:
responseConverter = createResponseConverter()
这个与获取请求的适配器的过程是类似的,因此这里就略过了。
3、解析方法上的注解:parseMethodAnnotation(annotation),我们用的是GET,所以下面会调用:
parseHttpMethodAndPath("GET", ((GET) annotation).value(), false);
我们这里value是空的,所以它只走了下面这些就返回了。
if (this.httpMethod != null) { throw methodError("Only one HTTP method is allowed. Found: %s and %s.", this.httpMethod, httpMethod); } this.httpMethod = httpMethod; this.hasBody = hasBody; if (value.isEmpty()) { return; }
4、构造ParameterHandler数组
int parameterCount = parameterAnnotationsArray.length;parameterHandlers = new ParameterHandler<?>[parameterCount];for (int p = 0; p < parameterCount; p++) { Type parameterType = parameterTypes[p]; if (Utils.hasUnresolvableType(parameterType)) { throw parameterError(p, "Parameter type must not include a type variable or wildcard: %s", parameterType); } Annotation[] parameterAnnotations = parameterAnnotationsArray[p]; if (parameterAnnotations == null) { throw parameterError(p, "No Retrofit annotation found."); } parameterHandlers[p] = parseParameter(p, parameterType, parameterAnnotations);}
主要是这一个方法:
parameterHandlers[p] = parseParameter(p, parameterType, parameterAnnotations);
p为序号,parameterType为方法的参数类型,parameterAnnotations为参数的注解。
里面就不细讲了,这里最终得到的是:
对于一些异常的判断就不多讲了,比如:
不能有多个带@Url注解的参数。
不能同时使用@Path与@Url注解。
被@QueryMap标注的参数类型必须是Map
@QueryMap注解的参数的key必须是String
至此,我们的ServiceMethod便构造完了。
五、我们回到代理的那个方法里面,还差两句没有解析:
OkHttpCall<Object> okHttpCall = new OkHttpCall<>(serviceMethod, args);
return serviceMethod.adapt(okHttpCall);
主要看serviceMethod.adapt(okHttpCall)
T adapt(Call<R> call) { return callAdapter.adapt(call);}
这里的callAdapter是RxJava2CallAdapter。
于是我们来到它的adapter方法:
@Override public Object adapt(Call<R> call) { Observable<Response<R>> responseObservable = isAsync ? new CallEnqueueObservable<>(call) : new CallExecuteObservable<>(call); Observable<?> observable; if (isResult) { observable = new ResultObservable<>(responseObservable); } else if (isBody) { observable = new BodyObservable<>(responseObservable); } else { observable = responseObservable; } if (scheduler != null) { observable = observable.subscribeOn(scheduler); } if (isFlowable) { return observable.toFlowable(BackpressureStrategy.LATEST); } if (isSingle) { return observable.singleOrError(); } if (isMaybe) { return observable.singleElement(); } if (isCompletable) { return observable.ignoreElements(); } return observable;}
首先我们看isAsync,这里为false,为什么呢?我们创建adapter的时候是这样的:
RxJava2CallAdapterFactory.create()
public static RxJava2CallAdapterFactory create() { return new RxJava2CallAdapterFactory(null, false);}
第二个参数便是isAsync
1、所以我们创建的responseObservable为CallExecuteObservable<>(call),(同步执行的类)
2、我们创建一个Observable<?> observable,这里创建的是BodyObservable<>(responseObservable),将刚刚创建的responseObservable
传进去。
3、最终将该observable传出去。
service = retrofit.create(ApiService.class);public interface ApiService { @GET Observable<ResponseBody> doGet(@Url String url, @HeaderMap Map<String, String> headers, @QueryMap Map<String, String> map);}service.doGet(url, header, params?.params)
也就是说,当我们调用service.doGet的时候,会走到代理的invoke方法,然后返回一个Observable
而该Observable只有在被订阅的时候才会执行,而且我们用的是同步,所以还需要在外面自己切换到子线程执行。
当被订阅的时候,该BodyObservable会调用subscribeActual:
BodyObservable(Observable<Response<T>> upstream) { this.upstream = upstream;}@Override protected void subscribeActual(Observer<? super T> observer) { upstream.subscribe(new BodyObserver<T>(observer));}
而这个upstream便是刚刚传进去的responseObservable,调用subscribe方法,最终会执行到responseObservable的subscribeActual方法。
@Override protected void subscribeActual(Observer<? super Response<T>> observer) { // Since Call is a one-shot type, clone it for each new observer. Call<T> call = originalCall.clone(); observer.onSubscribe(new CallDisposable(call)); boolean terminated = false; try { Response<T> response = call.execute(); if (!call.isCanceled()) { observer.onNext(response); } if (!call.isCanceled()) { terminated = true; observer.onComplete(); } } catch (Throwable t) { Exceptions.throwIfFatal(t); if (terminated) { RxJavaPlugins.onError(t); } else if (!call.isCanceled()) { try { observer.onError(t); } catch (Throwable inner) { Exceptions.throwIfFatal(inner); RxJavaPlugins.onError(new CompositeException(t, inner)); } } }}
我们主要看Response<T> response = call.execute();call便是我们传进来的自定义的OkHttpCall
在call.execute()里面:
...call = rawCall;if (call == null) { try { call = rawCall = createRawCall(); } catch (IOException | RuntimeException | Error e) { throwIfFatal(e); // Do not assign a fatal error to creationFailure. creationFailure = e; throw e; }}...return parseResponse(call.execute());
createRawCall()获取okhttp3.Call,call.execute()便是okhttp的网络请求了。
我们主要看怎么获取okhttp3.Call,以及对请求结果的解析parseResponse方法。
private okhttp3.Call createRawCall() throws IOException { okhttp3.Call call = serviceMethod.toCall(args); if (call == null) { throw new NullPointerException("Call.Factory returned null."); } return call;}
ServiceMethod里面:
/** Builds an HTTP request from method arguments. */okhttp3.Call toCall(@Nullable Object... args) throws IOException { RequestBuilder requestBuilder = new RequestBuilder(httpMethod, baseUrl, relativeUrl, headers, contentType, hasBody, isFormEncoded, isMultipart); @SuppressWarnings("unchecked") // It is an error to invoke a method with the wrong arg types. ParameterHandler<Object>[] handlers = (ParameterHandler<Object>[]) parameterHandlers; int argumentCount = args != null ? args.length : 0; if (argumentCount != handlers.length) { throw new IllegalArgumentException("Argument count (" + argumentCount + ") doesn't match expected count (" + handlers.length + ")"); } for (int p = 0; p < argumentCount; p++) { handlers[p].apply(requestBuilder, args[p]); } return callFactory.newCall(requestBuilder.build());}
方法主要是构造了request然后使用okhttp3.Call.Factory创建okhttp3.Call,而我们之前在构建ServiceMothod的构造的ParameterHandler<Object>[] handlers便参与了request的构建,主要是将之前解析到的参数,比如路径,头部信息等添加到request里面。
再看一下请求结果的解析parseResponse方法:
重点语句:
T body = serviceMethod.toResponse(catchingBody);
在看serviceMethod里面的toResponse方法:
/** Builds a method return value from an HTTP response body. */R toResponse(ResponseBody body) throws IOException { return responseConverter.convert(body);}
这里便用到了我们之前设置的数据转换器,对结果进行转换。
以上便是大概的过程了。
菊厂程序员每天轻松跟练刘畊宏健身操,到底怎么做到的?
陈叔叔 发表了文章 • 0 个评论 • 48 次浏览 • 2022-05-25 09:53
健身房不开,国民“运动量”却爆发式上涨。
私教都拉不回的运动自律,被刘畊宏的一套毽子操拉回来了。最近程序员阿强也在女友的打卡带动下,跟着刘教练开启了在家健身模式,线上与千万网友隔空同频共振,阿强表示,终于找到快乐运动的正确打开方式。
阿强切身体会了居家健身需求的兴起,BUT,其不足之处也渐渐浮现。
线上跟学,一套动作,统一的节奏无法适配每一个人的身体状态…长期不运动或运动量小的同学,习惯了peace模式的身体骤然接受大开大合、耗时不短的健身操,健身热情跟得上,身体和心脏也跟不上,所以,在家健身的男生女生们,需要一个贴身运动健康管家来实时监控运动状况!
收到内心发来的开发需求,阿强说干就干,使用HMS Core的运动健康服务能力,打造了一个运动管家App,开始运动后,实时监控运动时间、消耗的卡路里、心率等健康数据,帮助调整运动状态。
Demo示例
关于华为运动健康服务(HUAWEI Health Kit)
运动健康服务分为基础能力服务和扩展能力服务。
基础能力服务:Health Kit基础能力提供原子化数据开放,在获取用户对数据的授权后,应用通过接口访问运动健康数据,对用户数据进行增、删、改、查等操作,为用户提供运动健康类数据服务。
扩展能力服务: Health Kit扩展能力服务开放更多实时运动和健康数据、运动和健康解决方案场景化数据。
运动管家App相关能力则是基于Health Kit拓展能力中的功能进行开发。
开发流程
1、 开发环境要求
Android 平台的要求
安装Android Studio 3.X及以上
JDK 1.8.211及以上
应用应满足以下条件
minSdkVersion 24
targetSdkVersion 29
compileSdkVersion 29
Gradle 4.6及以上
测试应用的设备要求Android 6.0 及以上版本且装有华为运动健康App的华为手机。
2、开发步骤
完整的开发流程如下。
3、开始运动和停止运动功能实现
控制运动和获取实时运动配合使用顺序一般为:
(1)registerSportData,开始获取实时运动数据。
(2)startSport,开始运动。
(3)stopSport,停止运动。
(4)unregisterSportData,停止获取实时运动数据。
关键代码步骤
(1)开始获取实时运动数据
-
调用 HiHealthDataStore 对象的 registerSportData 方法,开始获取实时运动数据。
-
通过请求参数 HiSportDataCallback 对象,返回查询结果,结果中数据类型参考实时运动 Bundle 对象键值。
HiHealthDataStore.registerSportData(context, new HiSportDataCallback() { @Override public void onResult(int resultCode) { // 接口调用结果 Log.i(TAG, "registerSportData onResult resultCode:" + resultCode); } @Override public void onDataChanged(int state, Bundle bundle) { // 实时数据变化回调 Log.i(TAG, "registerSportData onChange state: " + state); StringBuffer stringBuffer = new StringBuffer(""); if (state == HiHealthKitConstant.SPORT_STATUS_RUNNING) { Log.i(TAG, "heart rate : " + bundle.getInt(HiHealthKitConstant.BUNDLE_KEY_HEARTRATE)); Log.i(TAG, "distance : " + bundle.getInt(HiHealthKitConstant.BUNDLE_KEY_DISTANCE)); Log.i(TAG, "duration : " + bundle.getInt(HiHealthKitConstant.BUNDLE_KEY_DURATION)); Log.i(TAG, "calorie : " + bundle.getInt(HiHealthKitConstant.BUNDLE_KEY_CALORIE)); Log.i(TAG, "totalSteps : " + bundle.getInt(HiHealthKitConstant.BUNDLE_KEY_TOTAL_STEPS)); Log.i(TAG, "totalCreep : " + bundle.getInt(HiHealthKitConstant.BUNDLE_KEY_TOTAL_CREEP)); Log.i(TAG, "totalDescent : " + bundle.getInt(HiHealthKitConstant.BUNDLE_KEY_TOTAL_DESCENT)); } }});
(2)开始运动
开始和结束运动支持的运动类型常量
-
调用 HiHealthDataStore 对象的 startSport 方法,启动相应类型的运动。
-
通过请求参数 ResultCallback 对象,返回查询结果。
// 室外跑步int sportType = HiHealthKitConstant.SPORT_TYPE_RUN;HiHealthDataStore.startSport(context, sportType, new ResultCallback() { @Override public void onResult(int resultCode, Object message) { if (resultCode == HiHealthError.SUCCESS) { Log.i(TAG, "start sport success"); } }});
(3)结束运动:
-
调用 HiHealthDataStore 对象的 stopSport 方法,停止相应类型的运动。
-
通过请求参数 ResultCallback 对象,返回查询结果。
HiHealthDataStore.stopSport(context, new ResultCallback() { @Override public void onResult(int resultCode, Object message) { if (resultCode == HiHealthError.SUCCESS) { Log.i(TAG, "stop sport success"); } }});
(4)停止获取实时运动数据:
-
调用 HiHealthDataStore 对象的 unregisterSportData 方法,停止获取实时运动数据。
-
通过请求参数 HiSportDataCallback 对象,返回查询结果。
HiHealthDataStore.unregisterSportData(context, new HiSportDataCallback() { @Override public void onResult(int resultCode) { // 接口调用结果 Log.i(TAG, "unregisterSportData onResult resultCode:" + resultCode); } @Override public void onDataChanged(int state, Bundle bundle) { // 此时不会被调用 }});
4、今日活动量查询功能实现
查询每日活动量,包含步数统计、步数详情、距离、热量及运动中高强度,这部分数据来源于手机或华为穿戴设备,需先申请开通权限,并获取用户授权,否则接口将调用失败。所需权限点此查询。
(1)使用 execQuery 接口查询用户每日活动量
1、调用 HiHealthDataStore 对象的 execQuery 方法,查询用户每日活动量。
2、通过请求参数 ResultCallback 对象,返回查询结果。
步数统计为例:
int timeout = 0;// 查询当天步数Calendar currentDate = Calendar.getInstance();currentDate.set(Calendar.HOUR_OF_DAY, 0);currentDate.set(Calendar.MINUTE, 0);currentDate.set(Calendar.SECOND, 0);long startTime = currentDate.getTimeInMillis();long endTime = System.currentTimeMillis();// 查询步数HiHealthDataQuery hiHealthDataQuery = new HiHealthDataQuery(HiHealthPointType.DATA_POINT_STEP_SUM, startTime, endTime, new HiHealthDataQueryOption());HiHealthDataStore.execQuery(context, hiHealthDataQuery, timeout, new ResultCallback() { @Override public void onResult(int resultCode, Object data) { Log.i(TAG, "query steps resultCode: " + resultCode); if (resultCode == HiHealthError.SUCCESS && data instanceof List) { List dataList = (ArrayList) data; for (Object obj : dataList) { HiHealthPointData pointData = (HiHealthPointData) obj; Log.i(TAG, "start time : " + pointData.getStartTime()); Log.i(TAG, "query steps : " + String.valueOf(pointData.getValue())); } } }});
查询数据所需参数及查询结果:
5、运动记录查询
查询30天内运动记录代码示例:
1、调用 HiHealthDataStore 对象的 execQuery 方法,查询用户运动记录。
2、通过请求参数 ResultCallback 对象,返回查询结果。
int timeout = 0; long endTime = System.currentTimeMillis(); // 查询时间范围 : 30 天 long startTime = endTime - 1000 * 60 * 60 * 24 * 30L; // 查询跑步记录 HiHealthDataQuery hiHealthDataQuery = new HiHealthDataQuery(HiHealthSetType.DATA_SET_RUN_METADATA, startTime, endTime, new HiHealthDataQueryOption()); HiHealthDataStore.execQuery(context, hiHealthDataQuery, timeout, new ResultCallback() { @Override public void onResult(int resultCode, Object data) {if (resultCode == HiHealthError.SUCCESS && data instanceof List){List dataList = (List) data; for (Object obj : dataList) { HiHealthSetData hiHealthData = (HiHealthSetData) obj; Map map = hiHealthData.getMap(); Log.i(TAG, "start time : " + hiHealthData.getStartTime()); Log.i(TAG, "total_time : " + map.get(HiHealthKitConstant.BUNDLE_KEY_TOTAL_TIME)); Log.i(TAG, "total_distance : " + map.get(HiHealthKitConstant.BUNDLE_KEY_TOTAL_DISTANCE)); Log.i(TAG, "total_calories : " + map.get(HiHealthKitConstant.BUNDLE_KEY_TOTAL_CALORIES)); Log.i(TAG, "step : " + map.get(HiHealthKitConstant.BUNDLE_KEY_STEP)); Log.i(TAG, "average_pace : " + map.get(HiHealthKitConstant.BUNDLE_KEY_AVERAGEPACE)); Log.i(TAG, "average_speed : " + map.get(HiHealthKitConstant.BUNDLE_KEY_AVERAGE_SPEED)); Log.i(TAG, "average_step_rate : " + map.get(HiHealthKitConstant.BUNDLE_KEY_AVERAGE_STEP_RATE)); Log.i(TAG, "step_distance : " + map.get(HiHealthKitConstant.BUNDLE_KEY_STEP_DISTANCE)); Log.i(TAG, "average_heart_rate : " + map.get(HiHealthKitConstant.BUNDLE_KEY_AVERAGE_HEART_RATE)); Log.i(TAG, "total_altitude : " + map.get(HiHealthKitConstant.BUNDLE_KEY_TOTAL_ALTITUDE)); Log.i(TAG, "total_descent : " + map.get(HiHealthKitConstant.BUNDLE_KEY_TOTALDESCENT)); Log.i(TAG, "data source : " + map.get(HiHealthKitConstant.BUNDLE_KEY_DATA_SOURCE)); } } } });
点此查询数据所需参数及查询结果
了解更多详情>>
访问华为开发者联盟官网
获取开发指导文档
华为移动服务开源仓库地址:GitHub、Gitee
关注我们,第一时间了解 HMS Core 最新技术资讯~
Android切换希腊语蓝牙打开失败
陈叔叔 发表了文章 • 0 个评论 • 46 次浏览 • 2022-05-23 14:17
版本:Android 9
平台:RK-PX30
问题描述:在状态栏点击蓝牙图标打开蓝牙时,发现打开失败,进入到设置里打开蓝牙却能打开成功,但是时间会比较长。
分析:
查看日志发现,执行BluetoothHci::initialize()
后,在获取到蓝牙MAC地址,就又把蓝牙关闭了。测试发现如果把蓝牙开启超时时间设置长点就可以正常打开了(由原本的4s改为10s)。
2021-10-27 14:32:30.561 495-522/system_process D/BluetoothManagerService: MESSAGE_BLUETOOTH_STATE_CHANGE: OFF > BLE_TURNING_ON2021-10-27 14:32:30.561 495-522/system_process D/BluetoothManagerService: Sending BLE State Change: OFF > BLE_TURNING_ON2021-10-27 14:32:30.561 19461-19482/com.android.bluetooth D/BluetoothAdapterService: bleOnProcessStart()2021-10-27 14:32:30.564 19461-19482/com.android.bluetooth D/BluetoothAdapterService: bleOnProcessStart() - Make Bond State Machine2021-10-27 14:32:30.565 19461-19482/com.android.bluetooth D/BluetoothBondStateMachine: make2021-10-27 14:32:30.570 19461-19497/com.android.bluetooth I/BluetoothBondStateMachine: StableState(): Entering Off State2021-10-27 14:32:30.595 19461-19461/com.android.bluetooth D/BluetoothAdapterService: getAdapterService() - returning com.android.bluetooth.btservice.AdapterService@e8fb3e72021-10-27 14:32:30.600 19461-19461/com.android.bluetooth D/BluetoothAdapterService: getAdapterService() - returning com.android.bluetooth.btservice.AdapterService@e8fb3e72021-10-27 14:32:30.613 19461-19461/com.android.bluetooth D/BluetoothAdapterService: getAdapterService() - returning com.android.bluetooth.btservice.AdapterService@e8fb3e72021-10-27 14:32:30.615 19461-19461/com.android.bluetooth D/BluetoothAdapterService: handleMessage() - Message: 22021-10-27 14:32:30.615 19461-19461/com.android.bluetooth D/BluetoothAdapterService: handleMessage() - MESSAGE_PROFILE_SERVICE_REGISTERED2021-10-27 14:32:30.616 19461-19461/com.android.bluetooth D/BluetoothAdapterService: handleMessage() - Message: 12021-10-27 14:32:30.616 19461-19461/com.android.bluetooth D/BluetoothAdapterService: handleMessage() - MESSAGE_PROFILE_SERVICE_STATE_CHANGED2021-10-27 14:32:30.628 19461-19502/com.android.bluetooth I/bt_hci: hci_initialize: IBluetoothHci::getService() returned 0x76f4876c60 (remote)2021-10-27 14:32:30.628 260-260/? I/android.hardware.bluetooth@1.0-impl: BluetoothHci::initialize() # 初始化2021-10-27 14:32:34.566 19461-19482/com.android.bluetooth D/BluetoothAdapterService: updateAdapterState() - Broadcasting state BLE_TURNING_OFF to 1 receivers.2021-10-27 14:32:34.567 495-522/system_process D/BluetoothManagerService: MESSAGE_BLUETOOTH_STATE_CHANGE: BLE_TURNING_ON > BLE_TURNING_OFF2021-10-27 14:32:34.567 495-522/system_process D/BluetoothManagerService: Sending BLE State Change: BLE_TURNING_ON > BLE_TURNING_OFF # 从log的时间来看,过了大概4s就把蓝牙状态设置为关闭,代码中超时时间就是4s2021-10-27 14:32:34.742 495-495/system_process D/BluetoothManagerService: Bluetooth Adapter address changed to 0F:DD:CC:AA:EE:0A # 调用的initialize() 4s后才返回结果,但此时状态已经设置为BLE_TURNING_OFF了2021-10-27 14:32:34.743 495-495/system_process D/BluetoothManagerService: Stored Bluetoothaddress: 0F:DD:CC:AA:EE:0A2021-10-27 14:32:34.744 495-495/system_process D/BluetoothManagerService: Bluetooth Adapter name changed to IPPhone2021-10-27 14:32:34.747 495-495/system_process D/BluetoothManagerService: Stored Bluetooth name: IPPhone2021-10-27 14:32:34.759 19461-19461/com.android.bluetooth D/BluetoothAdapterService: handleMessage() - Message: 12021-10-27 14:32:34.759 19461-19461/com.android.bluetooth D/BluetoothAdapterService: handleMessage() - MESSAGE_PROFILE_SERVICE_STATE_CHANGED2021-10-27 14:32:34.760 19461-19461/com.android.bluetooth D/BluetoothAdapterService: handleMessage() - Message: 32021-10-27 14:32:34.760 19461-19461/com.android.bluetooth D/BluetoothAdapterService: handleMessage() - MESSAGE_PROFILE_SERVICE_UNREGISTERED2021-10-27 14:32:34.761 19461-19482/com.android.bluetooth D/BluetoothAdapterService: updateAdapterState() - Broadcasting state OFF to 1 receivers.2021-10-27 14:32:34.761 495-522/system_process D/BluetoothManagerService: MESSAGE_BLUETOOTH_STATE_CHANGE: BLE_TURNING_OFF > OFF
继续分析,其他语言使用都正常,为什么只有希腊语会有出现这种情况呢?
对比正常情况,BluetoothHci::initialize()
调用后在2s内就会返回结果,因此问题就在于希腊语下BluetoothHci::initialize()
的时间过长,这种问题最简单的分析方法就是在可能有耗时较长的地方加log打印时间。
logcat本身会打印时间,如果是没有打印时间的,就需要使用其他方式计算时间了。
最后定位到源码里packages/apps/Bluetooth/src/com/android/bluetooth/Utils.java
的getAddressStringFromByte()
方法。
这个方法里面很简单,就只是调用了format()
方法,但是该方法却卡了至少2~4s的时间,加上其他步骤本身就需要接近2s,而上层BLE开启的超时时间是4s,因此很容易就导致超时而打不开。
继续跟踪format()
方法的实现:
format() # libcore/ojluni/src/main/java/java/util/Formatter.java ` toString() ` toUpperCase() # libcore/libart/src/main/java/java/lang/CaseMapper.java
最终找到根本原因是使用了希腊语的toUpperCase()
方法。
该方法实现如下:
public static String toUpperCase(Locale locale, String s, int count) { String languageCode = locale.getLanguage(); if (languageCode.equals("tr") || languageCode.equals("az") || languageCode.equals("lt")) { return ICU.toUpperCase(s, locale); } if (languageCode.equals("el")) { // 希腊语使用特殊方法处理 return EL_UPPER.get().transliterate(s); }}private static final ThreadLocal<Transliterator> EL_UPPER = new ThreadLocal<Transliterator>() { @Override protected Transliterator initialValue() { return Transliterator.getInstance("el-Upper"); }};
android使用开源转换库icu做处理,在调用希腊语转换模块的时候需要生成大量的对象,因此第一次调用会有很明显的延迟。
经过验证,如果对toUpperCase()
指定其他语言,也不会存在延迟;并且其他APP第一次调用该方法,也会有延迟。
解决方案:我们的解决方案是提前创建该对象,使用的时候就不会有延迟了。
《第一行代码:Android篇》学习笔记(一)
陈叔叔 发表了文章 • 0 个评论 • 43 次浏览 • 2022-05-11 01:47
本文和接下来的几篇文章为阅读郭霖先生所著《第一行代码:Android(篇第2版)》的学习笔记,按照书中的内容顺序进行记录,书中的Demo本人全部都做过了。
每一章节本人都做了详细的记录,以下是我学习记录(包含大量书中内容的整理和自己在学习中遇到的各种bug及解决方案),方便以后阅读和查阅。最后,非常感激郭霖先生提供这么好的书籍。
第一章 开始启程——你的第一行Android代码
欢迎你来到Android世界!
Android发展时间线:
2003年10月,Andy Rubin等人一起创办了Android公司;
2005年8月,谷歌收购了该公司,并让Andy Rubin继续负责Android项目;
2008年,谷歌终推出了Android系统的第一个版本;
Android的发展受到重重阻挠:
乔布斯自始至终认为Android抄袭iPhone的产品,剽窃了诸多iPhone的创意,并声称一定要毁掉Android;
2010年,Linux团队将基于Linux开发的Android操作系统从Linux内核主线中除名;
甲骨文则针对Android侵犯Java知识产权一事对谷歌提起了诉讼……
谷歌的开放政策:
任何手机厂商和个人都能免费获取到Android操作系统的源码,并且可以自由地使用和定制。
三星、HTC、摩托罗拉、索爱等公司都推出了各自系列的Android手机,Android市场上百花齐放。
仅仅推出两年后,Android就超过了已经霸占市场逾十年的诺基亚Symbian,成为了全球第一大智能手机操作系统。
国内的手机厂商,小米、华为、魅族等新兴品牌都推出了相当不错的Android手机,并且也获得了市场的广泛认可.
目前Android已经占据了全球智能手机操作系统70%以上的份额。
1.1 了解全貌——Android王国简介
Android从面世以来到现在已经发布了二十几个版本了。谷歌为Android王国建立了一个完整的生态系统。手机厂商、开发者、用户之间相互依存,共同推进着Android的蓬勃发展。
1.1.1 Android系统架构
Android大致可以分为四层架构:Linux内核层、系统运行库层、应用框架层和应用层。
1.Linux内核层
Android系统是基于Linux内核的,这一层为Android设备的各种硬件提供了底层的驱动,如显示驱动、音频驱动、照相机驱动、蓝牙驱动、Wi-Fi驱动、电源管理等。
2.系统运行库层
这一层通过一些C/C++库来为Android系统提供了主要的特性支持。如SQLite库提供了数据库的支持,OpenGL|ES库提供了3D绘图的支持,Webkit库提供了浏览器内核的支持等。
同样在这一层还有Android运行时库,它主要提供了一些核心库,能够允许开发者使用Java语言来编写Android应用。另外,Android运行时库中还包含了Dalvik虚拟机(5.0系统之后改为ART运行环境),它使得每一个Android应用都能运行在独立的进程当中,并且拥有一个自己的Dalvik虚拟机实例。相较于Java虚拟机,Dalvik是专门为移动设备定制的,它针对手机内存、CPU性能有限等情况做了优化处理。
3.应用框架层
这一层主要提供了构建应用程序时可能用到的各种API, Android自带的一些核心应用就是使用这些API完成的,开发者也可以通过使用这些API来构建自己的应用程序。
4.应用层
所有安装在手机上的应用程序都是属于这一层的,比如系统自带的联系人、短信等程序,或者是你从Google Play上下载的小游戏,当然还包括你自己开发的程序。
1.1.2 Android已发布的版本
2008年9月,谷歌正式发布了Android 1.0系统;
随后的几年,谷歌以惊人的速度不断地更新Android系统,2.1、2.2、2.3系统的推出使Android占据了大量的市场;
2011年2月,谷歌发布了Android 3.0系统,这个系统版本是专门为平板电脑设计的,但也是Android为数不多的比较失败的版本,推出之后一直不见什么起色;
在同年的10月,谷歌又发布了Android 4.0系统,这个版本不再对手机和平板进行差异化区分,既可以应用在手机上,也可以应用在平板上。
2014年Google I/O大会上,谷歌推出了号称史上版本改动最大的Android 5.0系统,其中使用ART运行环境替代了Dalvik虚拟机,大大提升了应用的运行速度,还提出了Material Design的概念来优化应用的界面设计。除此之外,还推出了Android Wear、Android Auto、Android TV系统,从而进军可穿戴设备、汽车、电视等全新领域。
2015年GoogleI/O大会上推出了Android 6.0系统,加入运行时权限功能;
2016年Google I/O大会上推出了Android 7.0系统,加入多窗口模式功能,这也是目前最新的Android系统版本;
查看最新的数据访问:https://developer.android.google.cn/about/dashboards/
1.1.3 Android应用开发特色
Android系统到底提供了哪些东西,可供我们开发出优秀的应用程序。
1.Android系统四大组件
(1)活动(Activity):是所有Android应用程序的门面,凡是在应用中你看得到的东西,都是放在活动中的。
(2)服务(Service):你无法看到它,但它会一直在后台默默地运行,即使用户退出了应用,服务仍然是可以继续运行的。
(3)广播接收器(Broadcast Receiver):广播接收器允许你的应用接收来自各处的广播消息,比如电话、短信等,当然你的应用同样也可以向外发出广播消息。
(4)内容提供器(Content Provider):为应用程序之间共享数据提供了可能,比如你想要读取系统电话簿中的联系人,就需要通过内容提供器来实现。
2.丰富的系统控件
Android系统为开发者提供了丰富的系统控件,使得我们可以很轻松地编写出漂亮的界面。
3.SQLite数据库
Android系统还自带了这种轻量级、运算速度极快的嵌入式关系型数据库。它不仅支持标准的SQL语法,还可以通过Android封装好的API进行操作,让存储和读取数据变得非常方便。
4.强大的多媒体
Android系统还提供了丰富的多媒体服务,如音乐、视频、录音、拍照、闹铃,等等,这一切你都可以在程序中通过代码进行控制,让你的应用变得更加丰富多彩。
5.地理位置定位
现在的Android手机都内置有GPS,走到哪儿都可以定位到自己的位置,发挥你的想象就可以做出创意十足的应用,如果再结合功能强大的地图功能,LBS这一领域潜力无限。
1.2 手把手带你搭建开发环境
1.2.1 准备所需要的工具
-
JDK
JDK是Java语言的软件开发工具包,它包含了Java的运行环境、工具集合、基础类库等内容。
-
Android SDK
Android SDK是谷歌提供的Android开发工具包,在开发Android程序时,我们需要通过引入该工具包,来使用Android相关的API。
-
Android Studio
在很早之前,Android项目都是用Eclipse来开发的,安装ADT插件后就可以用来开发Android程序了。
在2013年的时候,谷歌推出了一款官方的IDE工具Android Studio,由于不再是以插件的形式存在,Android Studio在开发Android程序方面要远比Eclipse强大和方便得多。
1.2.2 搭建开发环境
Android官网下载最新的开发工具,下载地址是:https://developer.android.google.cn/studio/index.html
百度网盘去下载,下载地址是:https://pan.baidu.com/s/1nuABMDb
注意:现在点击Finish按钮来启动Android Studio,一开始会让你选择是否导入之前Android Studio版本的配置,由于这是我们首次安装,这里选择不导入就可以了。
在点击Finish按钮,配置工作就全部完成了。然后Android Studio会尝试联网下载一些更新,等待更新完成后再点击Finish按钮就会进入Android Studio的欢迎界面
目前为止,Android开发环境就已经全部搭建完成了。
1.3 创建你的第一个Android项目
1.3.1 创建HelloWorld项目(注意:Language 选择Java)
-
在Android Studio的欢迎界面点击Start a newAndroid Studio project;
-
pplication name表示应用名称,此应用安装到手机之后会在手机上显示该名称;
-
Company Domain表示公司域名,如果是个人开发者;
-
Package name表示项目的包名,Android系统就是通过包名来区分不同应用程序的,因此包名一定要具有唯一性;
-
AndroidStudio会根据应用名称和公司域名来自动帮我们生成合适的包名,如果你不想使用默认生成的包名,也可以点击右侧的Edit按钮自行修改;
-
Project location表示项目代码存放的位置,如果没有特殊要求的话,这里也保持默认就可以了;
-
Android 4.0以上的系统已经占据了超过98%的Android市场份额,因此这里我们将Minimum SDK指定成API 15就可以了;
-
Wear、TV和Android Auto这几个选项分别是用于开发可穿戴设备、电视和汽车程序的;
-
Android Studio提供了很多种内置模板,刚开始学习,这里直接选择Empty Activity来创建一个空的活动就可以了;
记得:配置环境变量,
ANDROID_HONE
C:用户xxxxAppDataLocalAndroidSdkplatform-tools
1.3.2 启动模拟器
由于Android Studio自动为我们生成了很多东西,你现在不需要编写任何代码,HelloWorld项目就已经可以运行了。但是在此之前还必须要有一个运行的载体,可以是一部Android手机,也可以是Android模拟器。
暂时先使用模拟器来运行程序:
- 创建一个Android模拟器,观察Android Studio顶部工具栏中的图标
- Virtual Device Configuration
- 选择创建Nexus 5X这台设备的模拟器
- 选择模拟器所使用的操作系统版本,Download Android 11.0系统。继续点击Next
-
确认模拟器配置
以对模拟器的一些配置进行确认,比如说指定模拟器的名字、分辨率、横竖屏等信息,如果没有特殊需求的话,全部保持默认就可以了。
- 完成模拟器的创建,然后会弹出如图
- 点击Actions栏目中最左边的三角形按钮即可启动模拟器
1.3.3 运行HelloWorld
Android Studio顶部工具栏中的图标,三角形按钮是用来运行项目的。
运行结果:(像使用手机一样,按住鼠标向上划、向下划),Android Studio太智能了
1.3.4 分析你的第一个Android程序
- HelloWorld项目(Android模式的项目结构)
任何一个新建的项目都会默认使用Android模式的项目结构,但这并不是项目真实的目录结构,而是被AndroidStudio转换过的。这种项目结构简洁明了,适合进行快速开发,但是对于新手来说可能并不易于理解。
- 点击图中的Android区域可以切换项目结构模式
- 将项目结构模式切换成Project,这就是项目真实的目录结构
(1).gradle和.idea
这两个目录下放置的都是AndroidStudio自动生成的一些文件,我们无须关心,也不要去手动编辑。
(2)app
项目中的代码、资源等内容几乎都是放置在这个目录下的,我们后面的开发工作也基本都是在这个目录下进行的。
(3)build
它主要包含了一些在编译时自动生成的文件。
(4)gradle
包含了gradle wrapper的配置文件,使用gradle wrapper的方式不需要提前将gradle下载好,而是会自动根据本地的缓存情况决定是否需要联网下载gradle。
Android Studio默认没有启用gradle wrapper的方式,如果需要打开,可以点击Android Studio导航栏→File→Settings→Build, Execution,Deployment→Gradle,进行配置更改。
(5).gitignore
这个文件是用来将指定的目录或文件排除在版本控制之外的。
(6)build.gradle
项目全局的gradle构建脚本,通常这个文件中的内容是不需要修改的。
(7)gradle.properties
这个文件是全局的gradle配置文件,在这里配置的属性将会影响到项目中所有的gradle编译脚本。
(8)gradlew和gradlew.bat
这两个文件是用来在命令行界面中执行gradle命令的,其中gradlew是在Linux或Mac系统中使用的,gradlew.bat是在Windows系统中使用的。
(9)HelloWorld.iml
iml文件是所有IntelliJ IDEA项目都会自动生成的一个文件(Android Studio是基于IntelliJ IDEA开发的),用于标识这是一个IntelliJ IDEA项目,我们不需要修改这个文件中的任何内容。
(10)local.properties
这个文件用于指定本机中的AndroidSDK路径,通常内容都是自动生成的,我们并不需要修改。
除非你本机中的Android SDK位置发生了变化,那么就将这个文件中的路径改成新的位置即可。
(11)settings.gradle
这个文件用于指定项目中所有引入的模块。由于HelloWorld项目中就只有一个app模块,因此该文件中也就只引入了app这一个模块。
通常情况下模块的引入都是自动完成的,需要我们手动去修改这个文件的场景可能比较少。
整个项目的外层目录结构,除了app目录之外,大多数的文件和目录都是自动生成的,我们并不需要进行修改。下面我们就来对app目录下的内容进行更为详细的分析。
(1)build
这个目录和外层的build目录类似,主要也是包含了一些在编译时自动生成的文件,不过它里面的内容会更加更杂,我们不需要过多关心。
(2)libs
项目中使用到了第三方jar包,就需要把这些jar包都放在libs目录下,放在这个目录下的jar包都会被自动添加到构建路径里去。
(3)androidTest
用来编写Android Test测试用例的,可以对项目进行一些自动化测试。
(4)java
所有Java代码的地方,展开该目录,你将看到我们刚才创建的HelloWorldActivity文件就在里面。
(5)res
你在项目中使用到的所有图片、布局、字符串等资源都要存放在这个目录下。这个目录下还有很多子目录,图片放在drawable目录下,布局放在layout目录下,字符串放在values目录下,所以你不用担心会把整个res目录弄得乱糟糟的。
(6)AndroidManifest.xml
整个Android项目的配置文件,程序中定义的所有四大组件都需要在这个文件里注册,另外还可以在这个文件中给应用程序添加权限声明。
<?xml version="1.0" encoding="utf-8"?><manifest xmlns:android="http://schemas.android.com/apkes/android" package="com.zhouzhou"> <application android:allowBackup="true" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:roundIcon="@mipmap/ic_launcher_round" android:supportsRtl="true" android:theme="@style/Theme.HelloWorld"> <activity android:name=".MainActivity" android:exported="true"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> </application></manifest>
(7)test
用来编写Unit Test测试用例的,是对项目进行自动化测试的另一种方式。
(8).gitignore
用于将app模块内的指定的目录或文件排除在版本控制之外,作用和外层的.gitignore文件类似。
(9)app.iml
IntelliJ IDEA项目自动生成的文件,我们不需要关心或修改这个文件中的内容。
(10)build.gradle
app模块的gradle构建脚本,这个文件中会指定很多项目构建相关的配置。
(11)proguard-rules.pro
用于指定项目代码的混淆规则,当代码开发完成后打成安装包文件,如果不希望代码被别人破解,通常会将代码进行混淆,从而让破解者难以阅读。
详细解析:
<activity android:name=".MainActivity" android:exported="true"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter></activity>
这段代码表示对MainActivity这个活动进行注册,没有在AndroidManifest.xml里注册的活动是不能使用的。
intent-filter里的两行代码非常重要:
表示MainActivity是这个项目的主活动,在手机上点击应用图标,首先启动的就是这个活动。
而在Android四大组件的时候说过,活动是Android应用程序的门面,凡是在应用中你看得到的东西,都是放在活动中的。
打开MainActivity
代码如下所示:
package com.zhouzhou;import androidx.appcompat.app.AppCompatActivity;import android.os.Bundle;//MainActivity是继承自AppCompatActivity的,这是一种向下兼容的Activity,可以将Activity在各个系统版本中增加的特性和功能最低兼容到Android 2.1系统。public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); }}
- Activity是Android系统提供的一个活动基类,我们项目中所有的活动都必须继承它或者它的子类才能拥有活动的特性(AppCompatActivity是Activity的子类)。
- MainActivity中有一个onCreate()方法,这个方法是一个活动被创建时必定要执行的方法,其中只有两行代码,并且没有Hello World!的字样。
- Android程序的设计讲究逻辑和视图分离,因此是不推荐在活动中直接编写界面的,更加通用的一种做法是,在布局文件中编写界面,然后在活动中引入进来。
- 在onCreate()方法的第二行调用了setContentView()方法,就是这个方法给当前的活动引入了一个activity_main布局,那HelloWorld!一定就是在这里定义的了!
布局文件都是定义在res/layout目录下的,当你展开layout目录,你会看到activity_main.xml这个文件。(我是直接在代码上按住Ctrl键,鼠标定位到setContentView(R.layout.activity_main)中的activity_main
,点击即可进入)
注意:遇到一个小插曲,打开activity_main.xml显示的是视图,不是代码?
快捷键:Alt+Shift+左右箭头(第一个图标是显示代码)
activity_main.xml代码如下:
<?xml version="1.0" encoding="utf-8"?><androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apkes/android" xmlns:app="http://schemas.android.com/apkes-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".MainActivity"> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Hello World!" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintRight_toRightOf="parent" app:layout_constraintTop_toTopOf="parent" /></androidx.constraintlayout.widget.ConstraintLayout>
终于找到了,原来就是通过android:text="Hello World!"
这句代码定义的Hello World!的字样。
1.3.5 详解项目中的资源
(1)res目录
归纳一下:
- 所有以drawable开头的文件夹都是用来放图片的
- 所有以mipmap开头的文件夹都是用来放应用图标的
- 所有以values开头的文件夹都是用来放字符串、样式、颜色等配置的
- layout文件夹是用来放布局文件的
注:之所以有这么多mipmap开头的文件夹,是为了让程序能够更好地兼容各种设备。
drawable文件夹也是相同的道理。Android Studio没有帮我们自动生成,但是我们应该自己创建drawable-hdpi、drawable-xhdpi、drawable-xxhdpi等文件夹。
在制作程序的时候最好能够给同一张图片提供几个不同分辨率的版本,分别放在这些文件夹下,然后当程序运行的时候,会自动根据当前运行设备分辨率的高低选择加载哪个文件夹下的图片。
当然这只是理想情况,更多的时候美工只会提供给我们一份图片,这时你就把所有图片都放在drawable-xxhdpi文件夹下就好了。
(2)strings.xml文件
打开res/values/strings.xml文件:
<resources> <string name="app_name">HelloWorld</string><esources>
这里定义了一个应用程序名的字符串,我们有以下两种方式来引用它:
- 在代码中通过R.string.app_name可以获得该字符串的引用;
- 在XML中通过@string/app_name可以获得该字符串的引用
其中string部分是可以替换的,如果是引用的图片资源就可以替换成drawable,如果是引用的应用图标就可以替换成mipmap,如果是引用的布局文件就可以替换成layout,以此类推。
语法练习:打开AndroidManifest.xml
......<application android:allowBackup="true" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:roundIcon="@mipmap/ic_launcher_round" android:supportsRtl="true" android:theme="@style/Theme.HelloWorld"> ...... </application>
1.3.6 详解build.gradle文件
不同于Eclipse, Android Studio是采用Gradle来构建项目的。Gradle是一个非常先进的项目构建工具,它使用了一种基于Groovy的领域特定语言(DSL)来声明项目设置,摒弃了传统基于XML(如Ant和Maven)的各种烦琐配置。
HelloWorld项目中有两个build.gradle文件,一个是在最外层目录下的,一个是在app目录下的。这两个文件对构建AndroidStudio项目都起到了至关重要的作用。
- 最外层目录下的build.gradle文件:
// Top-level build file where you can add configuration options common to all sub-projects/modules.顶层构建文件,您可以在其中添加所有子项目/模块通用的配置选项。plugins { id 'com.android.application' version '7.1.0' apply false id 'com.android.library' version '7.1.0' apply false}task clean(type: Delete) { delete rootProject.buildDir}
- app目录下的build.gradle文件:
plugins { id 'com.android.application' /** *com.android.application表示这是一个应用程序模块 *com.android.library表示这是一个库模块 *应用程序模块和库模块的最大区别在于,一个是可以直接运行的,一个只能作为代码库依附于别的应用程序模块来运行。 **/}//下面是一个大的android闭包,可以配置项目构建的各种属性。android { //compileSdk用于指定项目的编译版本,32表示使用Android 11.0系统的SDK编译(24表示使用Android 7.0系统的SDK编译)。 compileSdk 32 //在android闭包中又嵌套了一个defaultConfig闭包 defaultConfig { //包名,后期想改可以在这里更改 applicationId "com.zhouzhou" //minSdk用于指定项目最低兼容的Android系统版本 minSdk 21 //targetSdk指定的值表示你在该目标版本上已经做过了充分的测试,系统将会为你的应用程序启用一些最新的功能和特性。 targetSdk 32 //versionCode用于指定项目的版本号 versionCode 1 //versionName用于指定项目的版本名,和versionCode一样,在生成安装文件的时候非常重要 versionName "1.0" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" } //buildTypes闭包,用于指定生成安装文件的相关配置,通常只会有两个子闭包,一个是debug,一个是release。release闭包用于指定生成正式版安装文件的配置。另外,debug闭包是可以忽略不写的,因此我们看到上面的代码中就只有一个release闭包。 buildTypes { release { //minifyEnabled用于指定是否对项目的代码进行混淆,true表示混淆,false表示不混淆。 minifyEnabled false /** *第一个proguard-android.txt是在Android SDK目录下的,里面是所有项目通用的混淆规则。 *第二个proguard-rules.pro是在当前项目的根目录下的,里面可以编写当前项目特有的混淆规则。 *需要注意的是,通过Android Studio直接运行项目生成的都是测试版安装文件,关于如何生成正式版安装文件我们将会在第15章中学习。 **/ proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' } } compileOptions { sourceCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8 }}//dependencies闭包,它可以指定当前项目所有的依赖关系。通常Android Studio项目一共有3种依赖方式:本地依赖、库依赖和远程依赖。//本地依赖可以对本地的Jar包或目录添加依赖关系,库依赖可以对项目中的库模块添加依赖关系,远程依赖则可以对jcenter库上的开源项目添加依赖关系。dependencies { implementation 'androidx.appcompat:appcompat:1.3.0' implementation 'com.google.android.material:material:1.4.0' implementation 'androidx.constraintlayout:constraintlayout:2.0.4' testImplementation 'junit:junit:4.13.2' androidTestImplementation 'androidx.test.ext:junit:1.1.3' androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'}
1.4 前行必备——掌握日志工具的使用
Android中日志工具的使用方法,这对以后的Android开发之旅会有极大的帮助。
1.4.1 使用Android的日志工具Log
Android中的日志工具类是Log(android.util.Log),这个类中提供了如下5个方法来供我们打印日志。
-
Log.v()
用于打印那些最为琐碎的、意义最小的日志信息。对应级别verbose,是Android日志里面级别最低的一种。
-
Log.d()
用于打印一些调试信息,这些信息对你调试程序和分析问题应该是有帮助的。对应级别debug,比verbose高一级。
-
Log.i()
用于打印一些比较重要的数据,这些数据应该是你非常想看到的、可以帮你分析用户行为数据。对应级别info,比debug高一级。
-
Log.w()
用于打印一些警告信息,提示程序在这个地方可能会有潜在的风险,最好去修复一下这些出现警告的地方。对应级别warn,比info高一级。
-
Log.e()
用于打印程序中的错误信息,比如程序进入到了catch语句当中。当有错误信息打印出来的时候,一般都代表你的程序出现严重问题了,必须尽快修复。对应级别error,比warn高一级。
练习:打开MainActivity,在onCreate()方法中添加一行打印日志的语句,如下所示:
package com.zhouzhou;import androidx.appcompat.app.AppCompatActivity;import android.os.Bundle;import android.util.Log;public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); Log.d("MainActivity","onCreate execute"); }}
小插曲:No connected device 解决:以管理员的方式运行AS
解决问题的博客地址:https://blog.csdn.net/qq_35605213/article/details/93891176
Log.d("MainActivity","onCreate execute");传入两个参数:第一个参数是tag,一般传入当前的类名就好,主要用于对打印信息进行过滤;第二个参数是msg,即想要打印的具体的内容。
1.4.2 为什么使用Log而不使用System.out
在真正的项目开发中,是极度不建议使用System.out.println()方法的!如果你在公司的项目中经常使用这个方法,就很有可能要挨骂了。
这个方法除了使用方便一点之外,其他就一无是处了。缺点太多了,比如日志打印不可控制、打印时间无法确定、不能添加过滤器、日志没有级别区分……Log对以上谈不上绝对好,但是已经做的相当不错了。
除了Log.v()、Log.d()、Log.i()、Log.w()、Log.w()之外,logcat中还能很轻松地添加过滤器:
目前只有3个过滤器:
- Show only selected application表示只显示当前选中程序的日志;
- Firebase是谷歌提供的一个分析工具,我们可以不用管它;
- NoFilters相当于没有过滤器,会把所有的日志都显示出来;
我们也可以自己添加一个过滤器试试:
点击图Edit Filter Configuration,会弹出一个过滤器配置界面。给过滤器起名叫Myfilter,并且让它对名为data的tag进行过滤,尝试在onCreate()方法中把打印日志的语句改成Log.d("data", "onCreate execute"),然后再次运行程序,你就会在data过滤器下看到这行日志了。
logcat中的日志级别控制,有5个方法:
日志级别控制的好处就是,你可以很快地找到你所关心的那些日志。最后,看一下关键字过滤。如果使用过滤器加日志级别控制还是不能锁定到你想查看的日志内容的话,那么还可以通过关键字进行进一步的过滤。
输入框里输入关键字的内容,这样只有符合关键字条件的日志才会显示出来,从而能够快速定位到任何你想查看的日志。另外还有一点需要注意,关键字过滤是支持正则表达式的,有了这个特性,我们就可以构建出更加丰富的过滤条件。
日调用量超600亿次,HMS Core HiAI Foundation助力AI应用高效开发
陈叔叔 发表了文章 • 0 个评论 • 42 次浏览 • 2022-05-09 09:32
随着新技术的不断演进,人工智能已经广泛地应用到教育、金融、物流、零售、交通、医疗等各个领域。而在AI高速发展的当下,高效开发变得更为重要,如何将创意想法与AI技术深度融合,迅速转化为可落地的AI应用,是开发者在激烈竞争中制胜的关键。
为了助力开发者快速上线AI业务,为消费者提供创新AI体验,HMS Core HiAI Foundation将HiAI 生态中的底层硬件能力开放,为开发者提供了300多个具备业界优质模型兼容性的AI算子,让开发者更简单快捷地应用华为AI能力,快速构建智能AI应用。
此外,HUAWEI HiAI Foundation还推出端云协同、Model Zoo、自主调优工具包、多IP深度协同等多种解决方案和能力,为众多业务场景打造更高性能更低功耗的计算环境,助力开发者快速开发和部署AI应用。
五大性能优势,助力开发者高效灵活开发
• 端云协同:针对新业务场景算子以及已有典型业务场景算子,提供性能优化、快速升级平台能力的解决方案
当前,AI业务和算法模型都在持续快速演进,AI计算平台适配新业务和新算法快速升级成为难题。端云协同具备可变的计算框架、广适的模型结构,助力开发者快速支持新模型、上线新业务,让消费者快速获得端侧AI带来的体验提升。
• Model Zoo:辅助开发者优化模型结构,更好地利用NPU加速优势
开发者在业务开发过程中,为了更大程度利用底层算力,需要针对底层硬件结构做一些模型调整,这个过程可能会出现效率低,资源利用不足的情况。HiAI Foundation打造Model Zoo,将NPU友好的模型结构、Backbone、算子放进去,供开发者自行挑选,辅助其优化模型结构,更好地发挥麒麟芯片NPU的加速优势。
• 模型量化工具包:让开发者的App更快、更小
正常情况下,开发者训练的模型是32bit,这样的模型计算精度固然高,但对手机功耗和内存的要求也比较高。HiAI Foundation为开发者提供模型量化工具包,在满足计算精度的前提下,可以把原始模型直接量化为更小、更轻便且更适合NPU结构的低比特模型,无需开发者做二次调整,从而节省手机空间,降低计算资源的消耗。
HiAI Foundation模型量化工具包
• 网络结构搜索工具包:让网络设计更简单、更有效
网络结构搜索工具包支持多种类型的网络结构搜索任务,包含分类,检测和分割。通过精度,性能目标牵引,协同硬件信息通过最优化搜索算法获得最优的网络结构,得到最佳的性能提升。网络结构搜索工具包支持多种主流训练框架使能,包含:caffe,tensorflow,pytorch。同时针对多种主流硬件平台具有算力和时延建模的能力。
HiAI Foundation网络结构搜索工具包
• 多IP深度协同:各计算单元共用DDR内存,提升性能降低功耗
HiAI Foundation在硬件裸算力开放的同时,还具备硬件底层多IP深度协同的优势。多IP深度协同的好处在于,CPU、NPU、ISP、GPU等各计算IP可以共用DDR内存,最大程度减少IP之间拷贝数据流转的开销,既可提升性能又能降低功耗。
从平台架构来看,HMS Core HiAI Foundation衔接智慧业务和底层硬件能力,上层支持MNN、TNN、华为MindSpore Lite、Paddle Lite、KwaiNN等合作伙伴的框架对接,利用推理加速平台(Foundation DDK)和异构计算平台(Foundation HCL)将AI任务调入NPU/CPU/GPU/DSP等IP中进行计算,赋能手机、平板、智慧屏、车机、手表等终端设备,将更多好玩酷炫的AI应用带到消费者面前。
HiAI Foundation 开放架构
日调用量突破600亿,引领端侧AI行业标准构筑
如今,AI技术在端侧的应用越来越广泛,语音识别、图像识别、图像分割、图像超分、人脸识别、文字识别等已经成为全民通用的技术,消费者期待更好玩的AI应用出现,希望获得更优质的AI应用体验。HiAI Foundation为AI应用开发提供了基础保障,让开发者能够突破性能瓶颈,提高开发效率,节省计算资源,更好地投入到AI玩法的研究与落地中,满足消费者对智慧生活的美好期待。
据统计, HiAI Foundation自2018年开放以来,以高性能、低功耗、高易用性收获越来越多的开发者青睐,短短几年间日调用量从100万+增长到600亿+(数据来自参与体验改进计划的用户)。爱奇艺、美颜相机、抖音、剪映、快手、优酷等头部应用已先后接入HiAI Foundation,利用端侧AI强大的计算能力,为用户打造智能新体验。
HiAI Foundation日调用量突破600亿+
为了助力端侧AI行业繁荣发展,HiAI Foundation在优化平台性能的同时,已加入AI标准化组织AITISA(人工智能产业技术创新战略联盟),并参与端侧AI标准草案撰写,共同构筑AI行业标准,目前该草案已进入标准定稿审核阶段。
更多HiAI Foundation相关信息,请关注5月24日HUAWEI Developer Day(简称HDD)-HiAI Foundation主题演讲,你也可以登录华为开发者联盟官网>HMS Core>HiAI Foundation查看。
了解更多详情>>
访问华为开发者联盟官网
获取开发指导文档
华为移动服务开源仓库地址:GitHub、Gitee
关注我们,第一时间了解 HMS Core 最新技术资讯~
RxJava + Retrofit源码解析
陈叔叔 发表了文章 • 0 个评论 • 40 次浏览 • 2022-04-24 21:20
RxJava + Retrofit怎么请求网络,具体的用法这里就不讲了,本文只讲一些重点源码。
版本如下:
okhttp : "com.squareup.okhttp3:okhttp:3.10.0",okhttp3_integration : "com.github.bumptech.glide:okhttp3-integration:1.4.0@aar",retrofit : "com.squareup.retrofit2:retrofit:2.4.0",converter_gson : "com.squareup.retrofit2:converter-gson:2.3.0",converter_scalars : "com.squareup.retrofit2:converter-scalars:2.3.0",converter_protobuf: "com.squareup.retrofit2:converter-protobuf:2.3.0",adapter_rxjava2 : "com.squareup.retrofit2:adapter-rxjava2:2.2.0",logging_interceptor : "com.squareup.okhttp3:logging-interceptor:3.10.0",rxjava : "io.reactivex.rxjava2:rxjava:2.1.12",rxandroid : "io.reactivex.rxjava2:rxandroid:2.0.2",
一、首先关于Retrofit的初始化:
private void initRetrofit() { ExtensionRegistry extensionRegistry = ExtensionRegistry.newInstance(); retrofit = new Retrofit.Builder() .baseUrl(baseUrl) //设置地址 .client(client.build()) //设置自定义的OkHttpClient .addConverterFactory(ProtoConverterFactory.createWithRegistry(extensionRegistry)) .addConverterFactory(StringConverterFactory.create()) .addConverterFactory(GsonConverterFactory.create(buildGson())) .addCallAdapterFactory(RxJava2CallAdapterFactory.create()) .build(); service = retrofit.create(ApiService.class);}
.addConverterFactory(ProtoConverterFactory.createWithRegistry(extensionRegistry))
.addConverterFactory(StringConverterFactory.create())
.addConverterFactory(GsonConverterFactory.create(buildGson()))
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
添加了数据转换器与请求适配器。
Retrofit的初始化采用了Builder模式。
Retrofit.Builder()这一步,获取了一个平台,肯定就是Android()了,后面有地方会用到。
Builder(Platform platform) { this.platform = platform;}public Builder() { this(Platform.get());}class Platform { private static final Platform PLATFORM = findPlatform(); static Platform get() { return PLATFORM; } private static Platform findPlatform() { try { Class.forName("android.os.Build"); if (Build.VERSION.SDK_INT != 0) { return new Android(); } } catch (ClassNotFoundException ignored) { } try { Class.forName("java.util.Optional"); return new Java8(); } catch (ClassNotFoundException ignored) { } return new Platform(); }}
在看最后的build();方法:
public Retrofit build() { if (baseUrl == null) { throw new IllegalStateException("Base URL required."); } okhttp3.Call.Factory callFactory = this.callFactory; if (callFactory == null) { callFactory = new OkHttpClient(); } Executor callbackExecutor = this.callbackExecutor; if (callbackExecutor == null) { callbackExecutor = platform.defaultCallbackExecutor(); } // Make a defensive copy of the adapters and add the default Call adapter. List<CallAdapter.Factory> callAdapterFactories = new ArrayList<>(this.callAdapterFactories); callAdapterFactories.add(platform.defaultCallAdapterFactory(callbackExecutor)); // Make a defensive copy of the converters. List<Converter.Factory> converterFactories = new ArrayList<>(1 + this.converterFactories.size()); // Add the built-in converter factory first. This prevents overriding its behavior but also // ensures correct behavior when using converters that consume all types. converterFactories.add(new BuiltInConverters()); converterFactories.addAll(this.converterFactories); return new Retrofit(callFactory, baseUrl, unmodifiableList(converterFactories), unmodifiableList(callAdapterFactories), callbackExecutor, validateEagerly);}
1、如果没有传入我们自定义的OkHttpClient,那么便会使用默认的。
2、如果没有设置自定义的回调执行器,那么便会是用默认的platform.defaultCallbackExecutor();点进入可以发现回调是默认在主线程中的:
static class Android extends Platform { @Override public Executor defaultCallbackExecutor() { return new MainThreadExecutor(); } @Override CallAdapter.Factory defaultCallAdapterFactory(@Nullable Executor callbackExecutor) { if (callbackExecutor == null) throw new AssertionError(); return new ExecutorCallAdapterFactory(callbackExecutor); } static class MainThreadExecutor implements Executor { private final Handler handler = new Handler(Looper.getMainLooper()); @Override public void execute(Runnable r) { handler.post(r); } }}
3、把我们设置的请求适配器添加进入,然后再添加一个默认的请求适配器。
4、添加进入一个默认的数据转换器,然后再被我们设置的数据转换器添加进去。
二、初始化好Retrofit后,再来看这一句:
service = retrofit.create(ApiService.class);
ApiService是一个接口,里面方法如下:
@GETObservable<ResponseBody> doGet(@Url String url, @HeaderMap Map<String, String> headers, @QueryMap Map<String, String> map);
这个create方法可以说是核心,它运用的是动态代理。
@SuppressWarnings("unchecked") // Single-interface proxy creation guarded by parameter safety.public <T> T create(final Class<T> service) { Utils.validateServiceInterface(service); if (validateEagerly) { eagerlyValidateMethods(service); } return (T) Proxy.newProxyInstance(service.getClassLoader(), new Class<?>[] { service }, new InvocationHandler() { private final Platform platform = Platform.get(); @Override public Object invoke(Object proxy, Method method, @Nullable Object[] args) throws Throwable { // If the method is a method from Object then defer to normal invocation. if (method.getDeclaringClass() == Object.class) { return method.invoke(this, args); } if (platform.isDefaultMethod(method)) { return platform.invokeDefaultMethod(method, service, proxy, args); } ServiceMethod<Object, Object> serviceMethod = (ServiceMethod<Object, Object>) loadServiceMethod(method); OkHttpCall<Object> okHttpCall = new OkHttpCall<>(serviceMethod, args); return serviceMethod.adapt(okHttpCall); } });}
1、首先检测这是否是一个接口,只有接口才能对它进行动态代理。
2、是否需要对接口里面的方法进行初始化预加载,是的话便进行,这个与下面的有点重复,直接讲下面的。
3、return后面的语句便是动态代理的地方,它会代理接口的所有方法,也就是说,当我们调用ApiService的方法的时候,会被拦截,然后走到inoke这个方法做我们自己的操作。
关于动态代理,后面会单独讲。
4、接下来边看invoke方法:
(1)、首先判断该方法是否为Object这个类的方法,如果是,不拦截它,让他走原来的方法。
(2)、platform为Android,platform.isDefaultMethod(method)返回false,不用管它。
(3)、ServiceMethod<Object, Object> serviceMethod =(ServiceMethod<Object, Object>) loadServiceMethod(method);拿到接口的方法,对接口的方法进行解析,比如获取注解,参数之类,构造自己的serviceMethod
(4)、初始化OkHttpCall
(5)、调用serviceMethod.adapt(okHttpCall)进行请求(因为采用的是RxJava,所以这里并不会立即请求,只有被订阅的时候才会,等会会讲)
三、loadServiceMethod(method)方法:
构造自己的serviceMethod 也采用了Builder模式。
进入这个方法后,重点的一句:
result = new ServiceMethod.Builder<>(this, method).build();
先看:
Builder(Retrofit retrofit, Method method) { this.retrofit = retrofit; this.method = method; this.methodAnnotations = method.getAnnotations(); this.parameterTypes = method.getGenericParameterTypes(); this.parameterAnnotationsArray = method.getParameterAnnotations();}
注:我们这里以前面定义的方法来讲解:
@GETObservable<ResponseBody> doGet(@Url String url, @HeaderMap Map<String, String> headers, @QueryMap Map<String, String> map);
1、持有retrofit与原始的method对象。
2、获取方法上的注解,获取到的为:
3、获取参数类型,获取到的为:
4、获取参数上面的的注解,获取到的为:
再看build()方法:
public ServiceMethod build() { callAdapter = createCallAdapter(); responseType = callAdapter.responseType(); if (responseType == Response.class || responseType == okhttp3.Response.class) { throw methodError("'" + Utils.getRawType(responseType).getName() + "' is not a valid response body type. Did you mean ResponseBody?"); } responseConverter = createResponseConverter(); for (Annotation annotation : methodAnnotations) { parseMethodAnnotation(annotation); } if (httpMethod == null) { throw methodError("HTTP method annotation is required (e.g., @GET, @POST, etc.)."); } if (!hasBody) { if (isMultipart) { throw methodError( "Multipart can only be specified on HTTP methods with request body (e.g., @POST)."); } if (isFormEncoded) { throw methodError("FormUrlEncoded can only be specified on HTTP methods with " + "request body (e.g., @POST)."); } } int parameterCount = parameterAnnotationsArray.length; parameterHandlers = new ParameterHandler<?>[parameterCount]; for (int p = 0; p < parameterCount; p++) { Type parameterType = parameterTypes[p]; if (Utils.hasUnresolvableType(parameterType)) { throw parameterError(p, "Parameter type must not include a type variable or wildcard: %s", parameterType); } Annotation[] parameterAnnotations = parameterAnnotationsArray[p]; if (parameterAnnotations == null) { throw parameterError(p, "No Retrofit annotation found."); } parameterHandlers[p] = parseParameter(p, parameterType, parameterAnnotations); } if (relativeUrl == null && !gotUrl) { throw methodError("Missing either @%s URL or @Url parameter.", httpMethod); } if (!isFormEncoded && !isMultipart && !hasBody && gotBody) { throw methodError("Non-body HTTP method cannot contain @Body."); } if (isFormEncoded && !gotField) { throw methodError("Form-encoded method must contain at least one @Field."); } if (isMultipart && !gotPart) { throw methodError("Multipart method must contain at least one @Part."); } return new ServiceMethod<>(this);}
1、首先获取请求适配器。
2、创建请求结果的转换器。
3、对方法上的注解进行解析。
4、构造ParameterHandler数组。
5、对一些异常的判断。
四、我们接下来对每一步进行讲解。
1、首先获取请求适配器:
private CallAdapter<T, R> createCallAdapter() { Type returnType = method.getGenericReturnType(); if (Utils.hasUnresolvableType(returnType)) { throw methodError( "Method return type must not include a type variable or wildcard: %s", returnType); } if (returnType == void.class) { throw methodError("Service methods cannot return void."); } Annotation[] annotations = method.getAnnotations(); try { //noinspection unchecked return (CallAdapter<T, R>) retrofit.callAdapter(returnType, annotations); } catch (RuntimeException e) { // Wide exception range because factories are user code. throw methodError(e, "Unable to create call adapter for %s", returnType); }}
(1)、获取方法的返回类型,返回类型不能是void
(2)、获取方法上的注解。
(3)、调用retrofit.callAdapter(returnType, annotations)方法获取请求的适配器。(我们之前设置的请求适配器都在retrofit对象中)
里面关键的一步为:
int start = callAdapterFactories.indexOf(skipPast) + 1;for (int i = start, count = callAdapterFactories.size(); i < count; i++) { CallAdapter<?, ?> adapter = callAdapterFactories.get(i).get(returnType, annotations, this); if (adapter != null) { return adapter; }}
skipPast为null,所以start为0;
遍历我们之前设置给它的请求适配器,根据返回类型与方法上的注解去找,找到了便返回。(我们这里获取到的callAdapter为RxJava2CallAdapter)
2、创建请求结果的转换器:
responseConverter = createResponseConverter()
这个与获取请求的适配器的过程是类似的,因此这里就略过了。
3、解析方法上的注解:parseMethodAnnotation(annotation),我们用的是GET,所以下面会调用:
parseHttpMethodAndPath("GET", ((GET) annotation).value(), false);
我们这里value是空的,所以它只走了下面这些就返回了。
if (this.httpMethod != null) { throw methodError("Only one HTTP method is allowed. Found: %s and %s.", this.httpMethod, httpMethod); } this.httpMethod = httpMethod; this.hasBody = hasBody; if (value.isEmpty()) { return; }
4、构造ParameterHandler数组
int parameterCount = parameterAnnotationsArray.length;parameterHandlers = new ParameterHandler<?>[parameterCount];for (int p = 0; p < parameterCount; p++) { Type parameterType = parameterTypes[p]; if (Utils.hasUnresolvableType(parameterType)) { throw parameterError(p, "Parameter type must not include a type variable or wildcard: %s", parameterType); } Annotation[] parameterAnnotations = parameterAnnotationsArray[p]; if (parameterAnnotations == null) { throw parameterError(p, "No Retrofit annotation found."); } parameterHandlers[p] = parseParameter(p, parameterType, parameterAnnotations);}
主要是这一个方法:
parameterHandlers[p] = parseParameter(p, parameterType, parameterAnnotations);
p为序号,parameterType为方法的参数类型,parameterAnnotations为参数的注解。
里面就不细讲了,这里最终得到的是:
对于一些异常的判断就不多讲了,比如:
不能有多个带@Url注解的参数。
不能同时使用@Path与@Url注解。
被@QueryMap标注的参数类型必须是Map
@QueryMap注解的参数的key必须是String
至此,我们的ServiceMethod便构造完了。
五、我们回到代理的那个方法里面,还差两句没有解析:
OkHttpCall<Object> okHttpCall = new OkHttpCall<>(serviceMethod, args);
return serviceMethod.adapt(okHttpCall);
主要看serviceMethod.adapt(okHttpCall)
T adapt(Call<R> call) { return callAdapter.adapt(call);}
这里的callAdapter是RxJava2CallAdapter。
于是我们来到它的adapter方法:
@Override public Object adapt(Call<R> call) { Observable<Response<R>> responseObservable = isAsync ? new CallEnqueueObservable<>(call) : new CallExecuteObservable<>(call); Observable<?> observable; if (isResult) { observable = new ResultObservable<>(responseObservable); } else if (isBody) { observable = new BodyObservable<>(responseObservable); } else { observable = responseObservable; } if (scheduler != null) { observable = observable.subscribeOn(scheduler); } if (isFlowable) { return observable.toFlowable(BackpressureStrategy.LATEST); } if (isSingle) { return observable.singleOrError(); } if (isMaybe) { return observable.singleElement(); } if (isCompletable) { return observable.ignoreElements(); } return observable;}
首先我们看isAsync,这里为false,为什么呢?我们创建adapter的时候是这样的:
RxJava2CallAdapterFactory.create()
public static RxJava2CallAdapterFactory create() { return new RxJava2CallAdapterFactory(null, false);}
第二个参数便是isAsync
1、所以我们创建的responseObservable为CallExecuteObservable<>(call),(同步执行的类)
2、我们创建一个Observable<?> observable,这里创建的是BodyObservable<>(responseObservable),将刚刚创建的responseObservable
传进去。
3、最终将该observable传出去。
service = retrofit.create(ApiService.class);public interface ApiService { @GET Observable<ResponseBody> doGet(@Url String url, @HeaderMap Map<String, String> headers, @QueryMap Map<String, String> map);}service.doGet(url, header, params?.params)
也就是说,当我们调用service.doGet的时候,会走到代理的invoke方法,然后返回一个Observable
而该Observable只有在被订阅的时候才会执行,而且我们用的是同步,所以还需要在外面自己切换到子线程执行。
当被订阅的时候,该BodyObservable会调用subscribeActual:
BodyObservable(Observable<Response<T>> upstream) { this.upstream = upstream;}@Override protected void subscribeActual(Observer<? super T> observer) { upstream.subscribe(new BodyObserver<T>(observer));}
而这个upstream便是刚刚传进去的responseObservable,调用subscribe方法,最终会执行到responseObservable的subscribeActual方法。
@Override protected void subscribeActual(Observer<? super Response<T>> observer) { // Since Call is a one-shot type, clone it for each new observer. Call<T> call = originalCall.clone(); observer.onSubscribe(new CallDisposable(call)); boolean terminated = false; try { Response<T> response = call.execute(); if (!call.isCanceled()) { observer.onNext(response); } if (!call.isCanceled()) { terminated = true; observer.onComplete(); } } catch (Throwable t) { Exceptions.throwIfFatal(t); if (terminated) { RxJavaPlugins.onError(t); } else if (!call.isCanceled()) { try { observer.onError(t); } catch (Throwable inner) { Exceptions.throwIfFatal(inner); RxJavaPlugins.onError(new CompositeException(t, inner)); } } }}
我们主要看Response<T> response = call.execute();call便是我们传进来的自定义的OkHttpCall
在call.execute()里面:
...call = rawCall;if (call == null) { try { call = rawCall = createRawCall(); } catch (IOException | RuntimeException | Error e) { throwIfFatal(e); // Do not assign a fatal error to creationFailure. creationFailure = e; throw e; }}...return parseResponse(call.execute());
createRawCall()获取okhttp3.Call,call.execute()便是okhttp的网络请求了。
我们主要看怎么获取okhttp3.Call,以及对请求结果的解析parseResponse方法。
private okhttp3.Call createRawCall() throws IOException { okhttp3.Call call = serviceMethod.toCall(args); if (call == null) { throw new NullPointerException("Call.Factory returned null."); } return call;}
ServiceMethod里面:
/** Builds an HTTP request from method arguments. */okhttp3.Call toCall(@Nullable Object... args) throws IOException { RequestBuilder requestBuilder = new RequestBuilder(httpMethod, baseUrl, relativeUrl, headers, contentType, hasBody, isFormEncoded, isMultipart); @SuppressWarnings("unchecked") // It is an error to invoke a method with the wrong arg types. ParameterHandler<Object>[] handlers = (ParameterHandler<Object>[]) parameterHandlers; int argumentCount = args != null ? args.length : 0; if (argumentCount != handlers.length) { throw new IllegalArgumentException("Argument count (" + argumentCount + ") doesn't match expected count (" + handlers.length + ")"); } for (int p = 0; p < argumentCount; p++) { handlers[p].apply(requestBuilder, args[p]); } return callFactory.newCall(requestBuilder.build());}
方法主要是构造了request然后使用okhttp3.Call.Factory创建okhttp3.Call,而我们之前在构建ServiceMothod的构造的ParameterHandler<Object>[] handlers便参与了request的构建,主要是将之前解析到的参数,比如路径,头部信息等添加到request里面。
再看一下请求结果的解析parseResponse方法:
重点语句:
T body = serviceMethod.toResponse(catchingBody);
在看serviceMethod里面的toResponse方法:
/** Builds a method return value from an HTTP response body. */R toResponse(ResponseBody body) throws IOException { return responseConverter.convert(body);}
这里便用到了我们之前设置的数据转换器,对结果进行转换。
以上便是大概的过程了。