安卓计步器是如何实现计步的

本文全面解析Android开发,涵盖开发环境搭建、SDK与JDK的区别、四大组件详解、数据存储方式对比,以及计步器等应用的实现原理。深入探讨Activity、Service、Broadcast、ContentProvider的使用场景,为开发者提供实用指南。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

开发环境

由于要用JAVA开发后端因此JDK必不可少,然后下载androd studio即可。SDK
几种APP开发模式
Native App (原生软件)APP应用所有的UI元素、数据内容、逻辑框架均安装在手机终端上。
用安卓做个客户端的壳访问服务器式开发模式

安卓开发必备知识
Android开发目前还是需要使用java语言的,如果不需要很复杂的功能,那么掌握java,懂得xml就可以做一个本地的AndroidApp,如果需要上网功能,可能同时需要HTML,CSS,javascript,SQL。学习 Java 只是基础,学习 SDK的使用方法才是开发安卓应用最关键的

大体来说,开发一个app从设计到最终实现需要你懂得以下几个必要方面:
1、前期需求规划与信息——首先,开发一个Android App软件,你需要制定出一个完整的需求文档,功能文档,流程图,时序图。
2、交互设计、UI设计——手机AndroidApp开发需要设计出基本且完善的原型图和app基础的交互设计效果之后,再根据这些设计出完整的UI界面
3、使用ADT(抽象数据类型)之类的开发环境进行app软件开发,你最基本的也得掌握java语言,熟悉android环境和机制。
4、如果不是单机版的app,需要用到服务器,那你还得掌握WebService相关知识和开发语言,常用的有ASP.Net,PHP,JSP等。
5、熟悉并能开发数据库。
6、对于手机安卓app的开发,某些功能需要做算法,这还需要一定得专业知识尤其是数学基础。
7、熟悉API接口开发,这里包括你自行开发API的能力以及调用第三方API的经验。
8、熟悉TCP/IP,socket等网络协议和相关知识。
9、熟练掌握App发布的流程,真机调试技巧,证书,打包,上架,一个完整的Android 手机App才能安全上架。

SDK:软件开发工具包(Software Development Kit)一般都是一些软件工程师为特定的软件包、软件框架、硬件平台、操作系统等建立应用软件时的开发工具的集合。包括广义上指辅助开发某一类软件的相关文档、范例和工具的集合。

Android SDK 就是 Android 专属的软件开发工具包。
主要作用包括:
1.提供基础类库和官方推荐的附加类库
调用电话/调用相机/相册选择等都是由基础类库android.jar提供的v4包v7包都是官方推荐的附加类库
2.编译工具
java编码文件通过编译工具编译成Darvit虚拟机能够读取的dex文件,Android4.4后使用Art虚拟机,运行效率得到极大的提高
3.调试开发工具
提供了模拟器等调试开发工具
需求案例
2018年平安银行产品经理和开发人员打架事件受到广泛的关注,主要是由于不懂技术的产品经理提出奇葩需求:app的主题颜色能够根据手机壳的颜色变化而变化。我们一个需求的实现高度依赖于SDK,SDK提供了相关API我们就能做,如果没有提供我们就做不了,因此该开发人员在拒绝该需求的时候解释说没有提供接口,这里就是说SDK没有提供相关的API来做这个需求。

Android SDK 中提供的面向硬件的特性
android.hardware.Camera
允许应用程序与相机交互的类,可以截取照片、获取预览屏幕的图像,修改用来治理相机操作的参数。
android.hardware.SensorManager
允许访问 Android 平台传感器的类。并非所有配备 Android 的设备都支持SensorManager 中的所有传感器
android.hardware.SensorListener
在传感器值实时更改时,希望接收更新的类要实现的接口。应用程序实现该接口来监视硬件中一个或多个可用传感器。
android.media.MediaRecorder
用于录制媒体样例的类, 可以分析音频片段以便在访问控件或安全应用程序时进行身份鉴定。例如,它可以帮助您通过声音打开门,以节省时间,不需要从房产经纪人处获取钥匙。
android.FaceDetector
允许对人脸(以位图形式包含)进行基本识别的类。不可能有两张完全一样的脸。可以使用该类作为设备锁定方法,无需记密码 — 这是手机的生物特征识别功能。
android.os.*
包含几个有用类的包,可以与操作环境交互,包括电源管理、文件查看器、处理器和消息类。和许多可移动设备一样,支持 Android 的电话可能会消耗大量电能。让设备在正确的时间 “醒来” 以监视感兴趣的事件是在设计时需要首先关注的方面。
java.util.Date
java.util.Timer
java.util.TimerTask
当测量实际的事件时,数据和时间往往很重要。例如,java.util.Date 类允许您在遇到特定的事件或状况时获取时间戳。您可以使用 java.util.Timer 和java.util.TimerTask 分别执行周期性任务或时间点任务

Android SDK 中提供的面向软件的特性
Android作为一个SDK,就需要提供给开发者在开发产品过程中实现一个基本功能所需的基本开发组件,而这些组件就是Activity,Service,Broadcast和ContentProvider

JDK
JDK全称为Java开发工具包,主要作用包括:
1.提供基础类库
由rt.jar提供了基础类的API,如果基础类没有只能由jar包的扩展类来提供
2.JVM[JAVA虚拟机]
class文件通过JVM"翻译"成机器能够识别的二进制的编码,充当着机器和虚拟机之间的桥梁
3.编译器
将java编码"翻译"成JVM能够识别的成class文件
4.调试开发工具
提供debug调试工具等
跨平台
Java之所以能实现其跨平台的属性其JVM功不可没,不管你是win、mac还是Linux系统,只要安装了JDK,就能跨平台运行class文件。JVM做了中间的桥梁,所以JVM是很牛逼的东西,可惜的是我国真正去研究JVM的人很少。

Android SDK和JDK区别和联系
1.Android SDK的基础类库[android.jar]参考了大部分的JDK基础类库[rt.jar],在此基础上进行的一些修减,增加了Android特有的也删除了JDK的一些基础类,也有改造了一些类。Android各版本对应的SDK和JDK版本
2.Android SDK不包括虚拟机但JDK包括虚拟机,Android的Darvit&ART虚拟机安装在Android设备上,Android是不存在main方法的入口这种说法的。
3.Android SDK编译工具[Gradle]是将java文件编译成Darvit&ART虚拟机能够读取的apk文件,JDK编译工具将java文件编译成JVM虚拟机能够读取的class文件。apk文件本质上就是zip包其中类主要编译在dex文件中,dex文件由class文件转化而来,因此apk的编译程序比war包[将class文件打包,用于部署在服务器]编译程序要多且要慢。

android中JDK和SDK和API的区别
一、 SDK
sdk,就是软件开发包,是一个广义的概念,任何编程工具几乎都可以看成是SDK。单单说SDK,范围太大。如果是Android sdk,就可以理解是安卓机器的操作系统,类似Windows操作系统。没有Android sdk,就无法进行Android开发。简言之,jdk是sdk的一种。
二、SDK和API
SDK和API的区别SDK相当于开发集成工具环境,API就是数据接口。在SDK环境下调用API数据
实际上SDK包含了API的定义,SDK又不完完全全只包含API以及API的实现,它是一个软件工具包,它还有很多其他辅助性的功能。
通俗语言解释API前端调用后端数据的一个通道,就是我们俗说的接口,通过这个通道,可以访问到后端的数据,但是又无需调用源代码。
三、JDK
JDK:开发JAVA程序的开发包,JDK里面有JAVA的运行环境(JRE),包括client和server端,需要配置环境变量

c++与java的几个不同点
1—— c++有头文件 而JAVA中是import
a:Java里有package关键字的用法,在写的程序第一行使用package关键字来声明一个包。包就是一个文件夹,它将源代码文件(.java)、编译后的文件(.class)和其他的一些用到的文件有组织的放在一起编译器会在import导入的包里选择与类名对应的包,而#include则是编译器将其他文件的内容载入进来。
b:头文件就是包含在C++程序中的别人写好的一些东西,里面包含一些函数和一些特定的功能,比如一般使用的cout<< cin>> endl 等这些东西都包含在iostream里,sqrt,sin, cos等数学相关的函数都包含在cmath里。 在c++里有一个经典的命名:using namespace std;这行代码使得我们在使用cin、cout、endl……时不用加std::的前缀常常要用到的一些函数,或定义,或习惯,也可以自已做成一个头文件,在编程中引用,这样可以减少很大的不必要的重复工作量。
c语言里头的头文件里放的 还是源代码,而java引入的包是被编译过的*.class文件并且JAVA的包 绝对是以类为单位的…也就是个类是一个*.class文件多个类 组成一个包而C++是 可以一个头文件中放函数 放常量 放类
![在这里插入图片描述](https://img-blog.csdnimg.cn/20200321235737828.png
2——代码格式
C/C++程序基本上是由n个函数组成,主函数调用其他函数实现所需功能。
Java程序是由n个类、m个方法组成,某个public类的主方法调用当前类的方法,或是调用其他类的公有(public)方法实现所需功能。Java的m个方法类似于C/C++的n个函数,只不过是将这m个方法放到n个类里,目录结构更清晰一些。
3——.录入输出
c++的录入输出靠输入流cin,输出流cout、cerr、clog实现,需要写#include。
Java语言里没有像c++那样一个函数就完成录入,它的录入使用了一些基础类,比如:
首先导包import java.util.Scanner;然后在方法内使用以下语句进行录入
4.——编译结果
a::c/c++编译的最终结果是一个程序生成一个exe文件。
b::Java编译结果是一个程序中有多少类就生成多少个与类名 相同的class文件。
5——指针问题
c/c++的引用,它跟java的引用完全不是一个东西,c/c++的引用是同一块内存的不同名字。而java的引用是指向一个对象,引用本身也占用了内存
a:首先,列举一下能对指针进行的一些常见操作:
指向一个对象,如 Person *p = new Person….;int *iv = new int….
对指针所指的对象进行操作:P->getAge();
b:指向一个对象,如Person p = new Person…
调用对象的方法,p.getAge();
指向另外一个对象,Person p1 = new Person…; p = p1;

弄懂 JRE、JDK、JVM 之间的区别与联系
JDK>JRE>JVM(开发工具包>运行环境>虚拟机)
JVM : (Java Virtual Machine),就是我们耳熟能详的 Java 虚拟机。它只认识 xxx.class 这种类型的文件,它能够将 class 文件中的字节码指令进行识别并调用操作系统向上的 API 完成动作。所以说,jvm 是 Java 能够跨平台的核心,具体的下文会详细说明。
JRE : (Java Runtime Environment), Java 运行时环境。它主要包含两个部分,jvm 的标准实现和 Java 的一些基本类库。它相对于 jvm 来说,多出来的是一部分的 Java 类库。
JDK : (Java Development Kit),Java 开发工具包。jdk 是整个 Java 开发的核心,它集成了 jre 和一些好用的小工具。例如:javac.exe,java.exe,jar.exe 等。
显然,这三者的关系是:一层层的嵌套关系。JDK>JRE>JVM。
jdk 是我们的开发工具包,它集成了 jre ,因此我们在安装 jdk 的时候可以选择不再安装 jre 而直接使用 jdk 中的 jre 运行我们的 Java 程序。(但是大部分人都默认将两个都装上了)。但是如果你的电脑不是用来开发 Java 程序的,而仅仅是用来部署和运行 Java 程序的,那么完全可以不用安装 jdk,只需要安装 jre 即可。

硬件

计步器用的是安卓手机自带的传感器,目前所知有TYPE_STEP_DETECTOR和TYPE_STEP_COUNTER(会自动返回一个从自启动进程以来到目前为止的累计步数 貌似不用启用service),sevice是一种服务,保持进程在后台依然可以运行

Android SDK 中提供的面向软件的特性(生命周期)

activity生命周期介绍(为什么要有生命周期)
Activity生命周期并不仅仅在用户运行应用程序之后才开始生效,事实上它也影响着用户切出以及切回应用时得到的不同反馈。当我们开发一款应用时,首先需要牢记一点:用户会经常在执行过程中、在我们的应用与其它应用之间频繁切换。取决于用户的操作方式,同一款应用程序有时在前台运行、有时则在后台运行。大家必须保证自己的应用能够就会这类情况,并在此类切换过程中及时保存并恢复数据。(用户在一个界面上点击一下,然后程序在后台开启一个线程去加载数据,加载完成后通知界面显示数据,如果这些数据在退出App情况下也需要的话,还要把这些数据保存到本地文件中)
为了控制Activity处于不同状态下时应用程序的运行方式,例如当用户切出或者切回应用,大家可以选择多种处理方法。这类方法也就是Activity生命周期回调方法。Android系统会在我们的Activity进入某种特定状态后调用这些方法,从而通过一系列步骤确保我们的应用程序能够继续起效、不至于丢失数据而且在用户不与之交互时不会使用非必要性资源。每一种回调方法都会让我们的应用进入一种可能的状态。
如果大家之前曾经接触过Java应用程序的编程工作,那么应该已经发现Android应用程序的启动遵循另一种方式。与Java应用直接使用主方法不同,Android在启动后会首先执行主Activity类中的onCreate方法。请记住,我们已经在清单中将该类指定为主启动Activity。Activity会首先回调onCreate方法,相当于重复用户启动应用程序后的流程。这时候onCreate方法会使应用程序进入Created状态。
开发者指南当中通过示意图以直观方式介绍了生命周期、回调方法以及状态的概念。其中onResume方法负责提供Resumed状态,这时我们的应用程序可以接受用户的直接操作。其它各类回调方法都以onResume为核心,即将应用程序引导至Resumed状态或者从该状态脱离、启动该状态或者将其停止。
对于大部分应用程序来说,我们只需要使用一部分回调方法,但最起码要用到onCreate。虽然使用频率不高,但了解全部回调及状态的作用将帮助我们了解自己的应用程序在运行及停止运行时,Android系统会受到怎样的影响。一般情况下,大家需要保证用户能够在任何操作过程切换出去之后、都能顺利恢复到之前的运行状态;如果他们通过导航选择前进或者后退,应用则需保存全部必要数据并释放不必要占用的硬件资源。

基于场景解读Android四大组件
我们知道一个App里面最常见的交互流程是这样的:用户在一个界面上点击一下,然后程序在后台开启一个线程去加载数据,加载完成后通知界面显示数据,如果这些数据在退出App情况下也需要的话,还要把这些数据保存到本地文件中。我们把这个流程分解成下面四个步骤:
1、前台(界面展示);
2、后台(数据加载);
3、通信(前台和后台通信);
4、存储(数据存储);
到这里可能大家会明白我想说的是什么了。这里的每一步其实就对应了一个组件,Activity就代表了前台所要提供的功能,主要负责界面的展示和用户的交互。Service就代表了后台所要提供的功能,主要负责一些耗时工作如网络请求,文件读写的处理。Broadcast就代表了通信所要提供的功能,主要负责Activity和Service之间(当然也可以是Activity和Activity,Service和Service)的通信。ContentProvider就代表了存储所要提供的功能,主要负责数据在磁盘的读写。
这里也许会有很多人不认同我的说法,比方说后台任务不一定要用Service啊,我自己搞个线程池或者开个进程也可以,消息通信也不一定需要Broadcast,我自己用Handler或者AIDL也可以做,存储也不需要ContentProvider,我自己写一套文件读写框架也可以。如果你这样想,也是可以的,但是我这里说了,我只是基于使用场景来谈这四个组件,从App层面考虑,Android作为一个SDK,就需要提供给开发者在开发产品过程中实现一个基本功能所需的基本开发组件,而这些组件就是Activity,Service,Broadcast和ContentProvider,少一个不行,多一个没必要。当然这四大组件其实也是Android为了方便开发者使用而提供的,你可以不用,自己重写一套来实现我上面说的前台,后台,通信和存储这四步(现在很多App都是这么干的)。但终归是要实现这四大功能,一个都不能少。

Activity使用场景解读
这张图大家应该很熟悉了,这里我按使用场景来把它们分成三组:
onCreate和onDestroy
onStart、onRestart和onStop
onResume和onPause
第一组使用频率最高,为什么?因为它的使用场景是最多的,在一个App里面我们会经常需要打开(startActivity)和关闭(finish)一个页面。onCreate是Activity生命周期里面的第一步,当我们进入到这一步时就表示一个Activity实例对象(从Java的角度看)已经产生了,当我们New了一个Java对象之后,首先要做的肯定是对其进行初始化了,那么onCreate就是Android提供给开发者用来对Activity实例对象中的成员做初始化的。Tips:Android为了方便对Activity组件的管理以及开发者使用,对Activity做了封装,开发者不能直接new一个Activity对象(你也可以直接new,但是new出来的对象不会被Android管理,也就失去了界面的展示和交互的功能,跟普通Java对象无异).
在onCreate里面一般我们会做View的初始化操作,比如添加View(setContentView,addView等)和View中数据的填充(setText,setImage等),那么问题来了:为什么对View的初始化要放在这里,可不可以放到其他地方(onStart或者onResume)?答案是肯定的,但是我们不建议这么做,因为放在onCreate里面可以保证初始化操作只做一次,而放到其他地方可能会调用多次,当然你可以添加一个flag来标记是否已经做过初始化,这样就可以保证放在onStart或者onResume里面也只做一次初始化,但是这样不觉得多此一举么?既然onCreate已经帮我们实现了这么一个功能,为啥不用呢,当然如果你有特殊需求,另当别论。
还有就是在onCreate里面有个savedInstanceState参数,这个主要用于你的Activity在非正常情况下被销毁前帮你自动保存的一些数据,这些数据会在这个Activity被重新创建时用到,因此Android将这个参数放在了onCreate里面。注意,我这里说的是非正常情况销毁Activity,这种场景比较多,比如系统内存不够用,系统语言改变,屏幕方向改变等,如果你不清楚哪些是非正常情况,没关系,只要清楚正常情况就行了,其他的自然就都可以认为是非正常场景下的Activity销毁行为。那么正常情况是什么?用户主动意愿想要销毁Activity就是正常情况,这种场景很少,就两种:调用finish和带特殊启动模式的startActivity方法。那么对于非正常情况下的onCreate我们在里面又该如何使用这个savedInstanceState?那么就要搞清楚savedInstanceState会保存到哪些数据,有两种:系统帮你自动保存的和你自己保存的。系统只会保存它认为有必要保存的(比方说EditText里面的内容,CheckBox的Check状态,Fragment实例等),但是很多童鞋不知道Activity会自动保存其中的Fragment实例,onCreate写成这样子:
在这里插入图片描述onStart、onRestart和onStop
说实话这三个回调接口在实际使用场景中并不多,对onStart、onRestart和onStop的使用可以从是否可见这点来找到它的正确使用姿势。这里举几个常用的场景:
对数据的时效性要求较高。以新闻类App为例:Activity A代表新闻列表,点击列表中的一个Item进入到Activity B新闻详情,在从B返回到A的时候为了保证用户能看到最新的新闻,就需要从服务器拉取最新的新闻列表数据并填充到Activity A,那么这个工作就可以放在onStart里面,当然你也可以放在onRestart里面,但是activity首次加载启动的时候不会调用onRestart,所以也就不会去拉取新闻列表数据。
需要显示动画效果。有些activity需要显示一些动画来帮助提升用户体验,但是当我们从该页面进入到一个新页面时,由于该页面已经不可见了,所以就可以把当前页面中的动画给关掉以节省系统资源,而这个工作就可以放在onStop中进行。
onResume和onPause
这两个接口使用的频率比上一组要高,对onResume和onPause的使用可以从可以从是否获得焦点(焦点即代表是是否可交互)这点来找到它的正确使用姿势,这里也举几个常见场景:
结束占用CPU的动画或者其他正在运行任务,这种在使用地图SDK的时候比较常见:

Service使用场景解读
Service是Android提供给开发者的一个组件,主要用于后台一些耗时任务的处理。其实Android系统中已经存在了很多这样在后台执行一些特定任务的系统级Service,比方说与我们开发中打交道最多的ActivityManager,WindowManager,PackageManager和InputManager等等。
![![在这里插入图片描述](https://img-blog.csdnimg.cn/20200322035230310.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3l1bnhpdTk4ODYyMg==,size_16,color_FFFFFF,t_70](https://img-blog.csdnimg.cn/20200322035301538.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3l1bnhpdTk4ODYyMg==,size_16,color_FFFFFF,t_70
这些方法能够让我们的应用程序在可能的状态之间进行切换,而且某些情况下切换速度会很快。通常来说,大家可以认为自己的应用程序始终处于resumed、paused或者stopped这三种状态之下,因为其它状态都是暂时性的。从图中可以看出Service的生命周期会根据启动方式的不同有不同的生命周期回调。其实startService和bindService的区别就是该service是否可以和启动它的组件(比如activity)通信,因为bindService可以拿到Service的binder,binder就是用来实现IPC(进程间通信)的嘛。下面我们具体分析下每个生命周期回调:
onCreate
该接口是在Service实例被创建时调用,这里的Service实例跟Activity实例不一样,我们知道Activity实例根据不同的启动模式可以有一个或者多个实例,但是,Service虽然也有两种启动方式,在整个系统中却只会有一个Service实例。为什么呢?换个角度看,这就好比PC端的C/S模式,使用一个服务端去处理多个客户端的请求,这里就对应一个Service去处理来自多个Activity的请求嘛,没必要搞多个,浪费资源,而且你会发现系统级Service其实也都只有一个实例。那么在onCreate里面我们可以做些什么呢?当然是初始化,比如创建数据缓存,线程池等等。Android系统给我们提供了一个IntentService,我们可以参考它的实现方式来做一些初始化操作,IntentService的onCreate源码如下: 这里为什么要创建新的线程或者线程池呢?因为Service默认是在主线程中执行的,所以我不建议你把一个后台任务放在Service中的主线程执行,因为那样就失去了Service存在的初衷,还不如直接放在Activity里面做,除非你想要提升App进程的优先级,防止App退到后台被杀掉。
在这里插入图片描述onStart
该接口是在调用startService方法时调用的,我们的后台任务一般都会放在这里执行,你可以通过intent获取startService方法传递的参数,这里依然以IntentService为例看下它的实现方式:
在这里插入图片描述onBind
该方法是在调用bindService方法时调用,如果使用bindService方式来启动服务的话,一般发生在Activity需要与Service进行通信的场景(比如说音乐播放器app里面就会用到),而Android的IPC主要是通过binder来实现的(也可以通过socket,在系统服务用的比较多),所以这里方法的返回值就需要一个binder实例。这里简单说下binder的实现机制(后续讲Broadcast的时候我们在具体分析Android的IPC机制具体实现),其实就是一套PC上的C/S模式,用户通过bindService接口获取到Service的代理,然后通过这个代理来跟Service通信。我们这里用一段代码来详细说明下:
这里的代理分两种:本地代理和远程代理,它们是按照service和activity是否在同一个进程中来区分的(这里仅以activity和service通信为例来说明,其他场景原理类似),如果是在同一进程,那么这个代理对象mServiceProxy其实就是mService对象。而当它们不在同一个进程中时,mServiceProxy和mService就属于不同进程空间中的对象,由于不同进程之间的数据不能直接访问,所以这个时候binder driver就来充当一个中间桥梁的作用,来完成参数和返回结果等数据的传递(其实也就是在Linux内核空间开辟了一段共享内存),从而实现通信的目的。当然为了方便开发者使用binder,Android对binder的使用进行了一定的封装,提供了一个AIDL。通过AIDL我们就只需关心service提供的功能接口,而不用去关心这些接口调用的具体细节。所以从这里也可以看出,对于一个好的产品,不管它的用户群是普通用户还是程序员,使用的便捷性都是一个很重要的指标。就好比现在市面上很多做SDK的,往往那些接口简单,文档清晰的SDK,用的人也会多一些。
在这里插入图片描述
onUnbind
该方法是在调用unbindService方法时调用,一般发生在activity中需要断开与service的连接的场景。注意该接口有个返回值,默认为false。如果你想要在onRebind里面做一些事情的话,那么这里需要返回true。
onDestroy
该方法会在Service销毁时调用,一般在这里我们会释放一些在onCreate中进行初始化时所申请的资源,可以参考IntentService的实现方式:
在这里插入图片描述Service使用场景
为了满足开发者处理后台任务的需要,Android提供了Service这个组件,同时为了方便开发者使用Service,又封装了一个IntentService。当然,现在很多App在处理后台任务的时候并没有优先使用Service,而是自己实现了一套线程池机制或者使用Android提供的AsyncTask来执行后台任务,这里我们来分析下他们各自的优劣:
Service的优点是系统原生支持,使用方便;创建进程方便;可以提供给系统内其他App使用;优先级高,当App退到后台后不宜被杀死。缺点是由于启动Service涉及到多次IPC,运行效率不高,而且受限于系统接口,使用不够灵活。
线程池的优点是运行效率高,配置和使用灵活。缺点是多进程实现不方便, 由于Android实现了一套进程托管机制,我们不能直接创建一个新的进程,而只能通过四大组件的形式创建新的进程。
基于以上分析,我们可以看出,一般普通的异步任务,比如网络请求,数据库或者文件相关操作,我们都会使用线程池的方式来做,因为这样使用的系统开销小,运行效率高,而且随着业务逻辑的复杂度增加,扩展性也更强。然而,对于一些特殊场景,比如进程保活,使用第三方SDK服务比如地图,IM等,就需要使用Service来实现,因为这些服务一般与App主进程隔离开,需要运行在新进程中以防止App主进程发生异常崩溃时,牵连第三方服务也挂掉。

如何把传感器的数据传入app

这里使用了android SDK里的一个sensor类

  • 大致思路

    用SensorManager获得该传感器(用sensor_service返回一个sensorManager对象),然后为传感器建立一个SensorEventListener(传感器事件监听器),这个监听器中包含两种需要实现的方法(nAccuracyChanged ,onSensorChanged ),方法规定了当传感器值发生变化时,监听器监听到该事件于是采取操作,返回当前传感器的值

  • 补充下传感器的工作原理: 传感机是靠感知手机振动来实现计步的,电子计步器主要由震动传感器和电子计数器组成。人在步行时重心都要有一点上下移动。 电子计步器的工作核心就是震动传感器,它里面有一个机械的震子,运动时会产生上下震动,机器通过收集震子运动的频率来计算数值 。

数据库

Android 五种数据存储的方式分别为:
作为一个完整的应用程序,数据存储操作是必不可少的。因此,Android系统一共提供了四种数据存储方式。分别是:SharePreference、文件存储、SQLite、 Content Provider。对这几种方式的不同和应用场景整理如下。
第一种: 使用SharedPreferences存储数据
适用范围:保存少量的数据,且这些数据的格式非常简单:字符串型、基本类型的值。比如应用程序的各种配置信息(如是否打开音效、是否使用震动效果、小游戏的玩家积分等),解锁口令密码等
  核心原理:保存基于XML文件存储的key-value键值对数据,通常用来存储一些简单的配置信息。通过DDMS的File Explorer面板,展开文件浏览树,很明显SharedPreferences数据总是存储在/data/data//shared_prefs目录下。SharedPreferences对象本身只能获取数据而不支持存储和修改,存储修改是通过SharedPreferences.edit()获取的内部接口Editor对象实现。 SharedPreferences本身是一 个接口,程序无法直接创建SharedPreferences实例,只能通过Context提供的getSharedPreferences(String name, int mode)方法来获取SharedPreferences实例,该方法中name表示要操作的xml文件名,第二个参数具体如下:  Context.MODE_PRIVATE: 指定该SharedPreferences数据只能被本应用程序读、写。
Context.MODE_WORLD_READABLE: 指定该SharedPreferences数据能被其他应用程序读,但不能写。
Context.MODE_WORLD_WRITEABLE: 指定该SharedPreferences数据能被其他应用程序读,写
SharedPreferences对象与SQLite数据库相比,免去了创建数据库,创建表,写SQL语句等诸多操作,相对而言更加方便,简洁。但是SharedPreferences也有其自身缺陷,比如其职能存储boolean,int,float,long和String五种简单的数据类型,比如其无法进行条件查询等。所以不论SharedPreferences的数据存储操作是如何简单,它也只能是存储方式的一种补充,而无法完全替代如SQLite数据库这样的其他数据存储方式。
第二种: 文件存储数据
  可以在设备本身的存储设备或者外接的存储设备中创建用于保存数据的文件。同样在默认的状态下,文件是不能在不同的程序间共享。
  写文件:调用Context.openFileOutput()方法根据指定的路径和文件名来创建文件,这个方法会返回一个FileOutputStream对象。
  读取文件:调用Context.openFileInput()方法通过制定的路径和文件名来返回一个标准的Java FileInputStream对象。
第三种:SQLite存储数据
  SQLite Database数据库。Android对数据库的支持很好,它本身集成了SQLite数据库,每个应用都可以方便的使用它,或者更确切的说,Android完全依赖于SQLite数据库,它所有的系统数据和用到的结构化数据都存储在数据库中。 它具有以下优点: a. 效率出众,这是无可否认的 b. 十分适合存储结构化数据 c. 方便在不同的Activity,甚至不同的应用之间传递数据。第四种:ContentProvider
  Android系统中能实现所有应用程序共享的一种数据存储方式,由于数据通常在各应用间的是互相私密的,所以此存储方式较少使用,但是其又是必不可少的一种存储方式。例如音频,视频,图片和通讯录,一般都可以采用此种方式进行存储。每个ContentProvider都会对外提供一个公共的URI(包装成Uri对象),如果应用程序有数据需要共享时,就需要使用ContentProvider为这些数据定义一个URI,然后其他的应用程序就通过Content Provider传入这个URI来对数据进行操作。
  总结一下,文件适用于存储一些简单的文本数据或者二进制数据,SharedPreferences适用于存储一些键值对,而数据库则适用于那些复杂的关系型数据。

SharedPreferences类介绍
Android平台给我们提供了一个SharedPreferences类(是一种方法,就像content provider一样是一种方法,具体是怎么存储到硬件上的我们不用关心,只要知道是以xml文件存储的就行了),它是一个轻量级的存储辅助类,特别适合用于保存软件配置参数和应用的一些常用配置。 例如,一些默认欢迎语、登录的用户名和密码等比如Activity状态,Activity暂停时,将此activity的状态保存到SharedPereferences中;当Activity重载,系统回调方法onSaveInstanceState时,再从SharedPreferences中将值取出。SharedPreferences 可以用来进行数据的共享,包括应用程序之间,或者同一个应用程序中的不同组件。比如两个activity除了通过Intent传
使用SharedPreferences保存数据, 其背后是以“键-值”对的方式用xml文件存放数据,文件存放在/data/data//shared_prefs目录下。提示最终是以xml方式来保存,整体效率来看不是特别的高,对于常规的轻量级而言比SQLite要好不少
如何利用xml存储数据的
具体编程实现

ContentProvider使用场景解读
ContentProvider在整个Android系统中扮演了数据管理的角色,负责整个Android系统中App数据的访问和各App之间的数据共享,ContentProvider提供了一些通用的接口来实现对底层数据(其实是数据库中的表结构数据)进行操作。
ContentProvider是什么?
ContentProvider是Android提供给上层的一个组件,主要用于实现数据访问的统一管理和数据共享。这里的数据管理是通过定义统一的访问接口来完成,如增删改查。同时,它采用了类似Internet的URL机制,将数据以URI的形式来标识,这样其他App就可以采用一套标准的URI规范来访问同一处数据,而不用关心具体的实现细节。我们知道在Android系统中可能会涉及到一个App的数据被其他App使用的情况,比如通讯录,日历,短信等,这时就需要一套能实现数据共享的机制,这里的ContentProvider就可以提供该功能,其底层使用了binder来完成App进程之间的通信,同时使用匿名共享内存来作为共享数据的载体。当然为了保证数据访问的安全性,ContentProvider还对每处的数据URI增加了权限管理机制,以控制该数据的访问者及访问方式。
最后再说下为何ContentProvider在我们平时开发App中使用的不多?其实,虽然ContentProvider具有数据管理和数据共享的功能,但是多半的优势还是集中在数据共享上,在数据共享的前提下,这个数据管理的优势也才能更加明显。像一些系统内置应用,联系人,日历和短信,就比较适合使用ContentProvider,因为他们经常需要给其他App提供数据。而对于像普通App,一般出于安全原因,不会把数据提供给第三方App使用,而在App内部访问数据库的话,完全可以自己实现一套数据库访问框架,因为ContentProvider为了屏蔽数据库的访问细节,实质上是在其基础上再封装了一层接口而已,而对于App内部的数据库访问来说,没有这个必要

用service实现计步器的后台运行

这个问题相当于为什么当计步器转入后台程序的时候仍然可以实现计步的功能,这就引入了我本段要讲的组件service。

  • 讲sevice之前先讲一下何为后台
    安卓软件所处的一种生命周期Activity一般有四种状态:
    (1)running:当Activity位于栈顶时,此时正好处于屏幕最前方,此时处于运行状态;
    (2)paused:当Activity失去了焦点但仍然对用于可见(如栈顶的Activity是透明的或者栈顶Activity并不是铺满整个手机屏幕) ;
    (3)stoped:当Activity被其他Activity完全遮挡,此时此Activity对用户不可见
    (4)destoryed:当Activity由于人为或系统原因(如低内存等)被销毁 ;
    后台即paused和stop状态。

  • 具体应用
    一个service是一段长生命周期的,没有用户界面的程序,一个例子就是手机播放音乐时用户可以进行其他应用的活动,或者闹钟关机之后仍然可以响。对service其实就是在主页面基础上(在oncreat中)写一个intent,然后可以把service定义到onclick中(用intent转换),service也有生命周期例如oncreat,onstart,

  • 支付宝和微信的计步原理
    -

  • 是怎么实现交互的
    安卓需要用到一个组件叫activity,一个activity提供一个屏幕,用户可以用来交互已完成某种任务,例如点击、拨号拍照。由于安卓是事件驱动的,因此当你做操作的时候会对应一系列处理,最典型的就是点击事件的设置,为监听事件设置一个监听器,当监听器监听到你的动作就会跳转到该动作触发的程序,这样就完成了一次交互操作。 监听器概念在安卓中非常重要,在上文中提到获取传感器数据时也用到了一个sensorlistener(是监听传感器的值是否发生改变)

  • 补充事件驱动的定义:

    首先我们来给事件驱动编程下一个定义。事件驱动编程是一种编程范式,程序的执行流程是由动作(actions,例如用户交互,其他线程发送的消息等等)触发的事件(event)决定的。在这个意义上,Android是部分事件驱动:我们都知道的onClick监听器或者Activity的生命周期,都是应用中由动作触发的事件。我为什么说它不是纯粹的事件驱动系统呢?默认情况下,每个事件被绑定在特定的controller上面,因此很难在其他地方使用该事件(例如onClick事件是定义为View服务的,因此它的使用范围很有限)。

    是如何获得时间的

    由于安卓中system类中提供获取时间的方法(System.currentTimeMillis()),因此我们要做的就是开启一个线程,无限循环该线程,在线程中用handler的message的通信机制,每隔一秒就会发出一个message,然后就可以主线程中处理消息并更新时间,在通过xml的设计显示到界面上就行了。

计步器的各类性能

准确率方面
android机型众多,传感器的灵敏度不一,因此会出现各种计步数不同的现象
传感器的选择
由于android4.4 就支持stepcount了 因此可以直接接受返回值,省去了对于步数检测的一系列算法
实现隔天清零以及查看历史数据的操作
//emmm这个功能以后再学习吧
隔天清零 参考该篇博文
感觉这个博主的代码很易懂,附传送门~
这个是关于计步器的一篇论文

  • 计步器的问题

安卓的计步传感器,重启数据清零,跨天数据不会清零,所以当天的数据需要自己计算.当天的最后一次步数 - 当天的第一次步数 = 当天的步数, 还要考虑重启传感器清零的情况."还有些手机杀掉进程可以获取数据,但是隔天需要重新开启进程,不然不会计步。"这个就是第一次打开注册了读取到当时的值了
首先抛出问题android4.4之后提供了两个传感器STEP DETECTOR 以及 STEP COUNTER,但是这两个传感器虽然可以很方便的提供给我们步数,但返回的步数是从开机以来的累计步数,隔天数据无法实现清零 ,并且关机之后无法实现
先来看下我们需要解决的点有:

1、步数从开机之后不断累加,关机之后便清零,步数 不能隔天清零

2、不能查看历史数据

数据的存储

web app和原生app有三种区别:

1.开发方面有区别:
(1)原生APP:每一种移动操作系统都需要独立的开发项目,iphone版本、Ipad版本、安卓版本。每种平台都需要独立的开发语言。Java(Android), Objective-C(iOS)等等。需要使用各自的软件开发包,开发工具以及各自的控件。
(2)开发成本高、开发速度慢、维护成本高。三个平台(IOS、安卓、windows)的规则、推广、运营都不相同。官方应用商店对APP上线审核流程比较复杂而且很慢,会严重影响APP的发布上线。
(3)Web App :因为运行在移动设备的浏览器上,所以只需要一个开发项目。可以通过HTML、 CSS或者JavaScript来进行WebAPP的开发。开发成本低、开发速度快。
2.功能有区别:
(1)原生App:原生APP是一个系统性的应用程序,可以类比于电脑上的软件。原生app可以调用移动终端的硬件设备, 比如:麦克风、摄像头、短信、GPS、蓝牙、重力感应等。实现功能丰富
(2)Web App:Web APP可以类比于电脑上的网页。Web APP更多是页面展示类的APP。只能使用有限的移动硬件设备功能。更多用于页面展示,侧重于简单的交互,无法使用很多硬件设备独特的功能。

有关android四大组件引用于https://www.jianshu.com/p/9612c33686b9

https://blog.csdn.net/chaoyu168/article/details/52997036Android SDK上手指南:Activity与生命周期

描述 android计步器实现,自定义的一个弧形进度条,记步通过手机的传感器来实现,也就是说不支持传感器的机子(应该很老的了吧)就没有效果。看看效果图: 这里写图片描述这里写图片描述 自定义View public class StepView extends View { /** * 圆弧的宽度 */ private float borderWidth = dipToPx(10); /** * 画步数的数值的字体大小 */ private float numberTextSize = 0; /** * 步数 */ private String stepNumber = "0"; /** * 开始绘制圆弧的角度 */ private float startAngle = 125; /** * 终点对应的角度和起始点对应的角度的夹角 */ private float angleLength = 290; /** * 所要绘制的当前步数的蓝色圆弧终点到起点的夹角 */ private float currentAngleLength = 0; /** * 动画时长 */ private int animationLength = 3000; /** * 当前运动类型 */ private String type = "Riding"; /** * 当前活跃等级 */ private String level = "等级:轻度活跃"; /** * 步数上方文字 */ private String today = "今日步数"; /** * 单位km是否显示 */ private String unit = "Km"; public StepView(Context context) { super(context); } public StepView(Context context, AttributeSet attrs) { super(context, attrs); } public StepView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); /**中心点的x坐标*/ float centerX = (getWidth()) / 2; /**指定圆弧的外轮廓矩形区域*/ RectF rectF = new RectF(0 + borderWidth, borderWidth, 2 * centerX - borderWidth, 2 * centerX - borderWidth); /**【第一步】绘制整体的灰色圆弧*/ drawArcYellow(canvas, rectF); /**【第二步】绘制当前进度的蓝色圆弧*/ drawArcRed(canvas, rectF); /**【第三步】绘制当前进度的白色数字*/ drawTextNumber(canvas, centerX); /**【第四步】绘制"本次步数"的灰色文字*/ drawTextStepString(canvas, centerX); /**【第五步】绘制当前记步类型*/ drawTextType(canvas, centerX); /**【第六步】绘制当前等级类型*/ drawTextLevel(canvas, centerX); /**【第七步】绘制骑行距离单位*/ drawTextUnit(canvas, centerX); } /** * 1.绘制总步数的灰色圆弧 * * @param canvas 画笔 * @param rectF 参考的矩形 */ private void drawArcYellow(Canvas canvas, RectF rectF) { Paint paint = new Paint(); /** 默认画笔颜色,灰色 */ paint.setColor(getResources().getColor(R.color.near_black)); /** 结合处为圆弧*/ paint.setStrokeJoin(Paint.Join.MITER); /** 设置画笔的样式 Paint.Cap.Round ,Cap.SQUARE等分别为圆形、方形*/ paint.setStrokeCap(Paint.Cap.BUTT); /** 设置画笔的填充样式 Paint.Style.FILL :填充内部;Paint.Style.FILL_AND_STROKE :填充内部和描边; Paint.Style.STROKE :仅描边*/ paint.setStyle(Paint.Style.STROKE); float[] floats = {4,16,4,16}; paint.setPathEffect(new DashPathEffect(floats, 0)); /**抗锯齿功能*/ paint.setAntiAlias(true); /**设置画笔宽度*/ paint.setStrokeWidth(borderWidth); /**绘制圆弧的方法 * drawArc(RectF oval, float startAngle, float sweepAngle, boolean useCenter, Paint paint)//画弧, 参数一是RectF对象,一个矩形区域椭圆形的界限用于定义在形状、大小、电弧, 参数二是起始角(度)在电弧的开始,圆弧起始角度,单位为度。 参数三圆弧扫过的角度,顺时针方向,单位为度,从右中间开始为零度。 参数四是如果这是true(真)的话,在绘制圆弧时将圆心包括在内,通常用来绘制扇形;如果它是false(假)这将是一个弧线, 参数五是Paint对象; */ canvas.drawArc(rectF, startAngle, angleLength, false, paint); } /** * 2.绘制当前步数的蓝色圆弧 */ private void drawArcRed(Canvas canvas, RectF rectF) { Paint paintCurrent = new Paint(); paintCurrent.setStrokeJoin(Paint.Join.MITER); paintCurrent.setStrokeCap(Paint.Cap.BUTT);//圆角弧度 paintCurrent.setStyle(Paint.Style.STROKE);//设置填充样式 paintCurrent.setAntiAlias(true);//抗锯齿功能 paintCurrent.setStrokeWidth(borderWidth);//设置画笔宽度 paintCurrent.setColor(getResources().getColor(R.color.colorPrimary));//设置画笔颜色 float[] floats = {4,16,4,16}; paintCurrent.setPathEffect(new DashPathEffect(floats, 0)); canvas.drawArc(rectF, startAngle, currentAngleLength, false, paintCurrent); } /** * 3.圆环中心的步数 */ private void drawTextNumber(Canvas canvas, float centerX) { Paint vTextPaint = new Paint(); vTextPaint.setTextAlign(Paint.Align.CENTER); vTextPaint.setAntiAlias(true);//抗锯齿功能 vTextPaint.setTextSize(numberTextSize); Typeface font = Typeface.create(Typeface.SANS_SERIF, Typeface.NORMAL); vTextPaint.setTypeface(font);//字体风格 vTextPaint.setColor(getResources().getColor(R.color.white)); Rect bounds_Number = new Rect(); vTextPaint.getTextBounds(stepNumber, 0, stepNumber.length(), bounds_Number); canvas.drawText(stepNumber, centerX, getHeight() / 2 + bounds_Number.height() / 2, vTextPaint); } /** * 4.圆环中心[本次步数]的文字 */ private void drawTextStepString(Canvas canvas, float centerX) { Paint vTextPaint = new Paint(); vTextPaint.setTextSize(dipToPx(16)); vTextPaint.setTextAlign(Paint.Align.CENTER); vTextPaint.setAntiAlias(true);//抗锯齿功能 vTextPaint.setColor(getResources().getColor(R.color.gray)); Rect bounds = new Rect(); vTextPaint.getTextBounds(today, 0, today.length(), bounds); canvas.drawText(today, centerX, getHeight() / 2 + bounds.height() - 2 * getFontHeight(numberTextSize), vTextPaint); } /** * 5.圆环中下[Walking]等文字 */ private void drawTextType(Canvas canvas, float centerX) { Paint mTypePaint = new Paint(); mTypePaint.setTextSize(dipToPx(22)); mTypePaint.setTextAlign(Paint.Align.CENTER); mTypePaint.setAntiAlias(true); mTypePaint.setColor(getResources().getColor(R.color.text_blue)); Rect bounds = new Rect(); mTypePaint.getTextBounds(type, 0, type.length(), bounds); canvas.drawText(type, centerX, getHeight() / 2 + 2 * bounds.height() + getFontHeight(numberTextSize), mTypePaint); } /** * 6.绘制圆环下方等级 */ private void drawTextLevel(Canvas canvas, float centerX) { Paint mLevelPaint = new Paint(); mLevelPaint.setTextSize(dipToPx(12)); mLevelPaint.setTextAlign(Paint.Align.CENTER); mLevelPaint.setAntiAlias(true); mLevelPaint.setColor(getResources().getColor(R.color.input_hint_gray)); Rect bounds = new Rect(); mLevelPaint.getTextBounds(level, 0, level.length(), bounds); canvas.drawText(level, centerX, getHeight() / 2 + 2 * bounds.height() + 2 * getFontHeight(numberTextSize), mLevelPaint); } /** * 7.绘制骑行单位km */ private void drawTextUnit(Canvas canvas, float centerX) { Paint mUnitPaint = new Paint(); mUnitPaint.setTextSize(dipToPx(16)); mUnitPaint.setTextAlign(Paint.Align.CENTER); mUnitPaint.setAntiAlias(true); mUnitPaint.setColor(getResources().getColor(R.color.input_hint_gray)); Rect bounds = new Rect(); mUnitPaint.getTextBounds(unit, 0, unit.length(), bounds); canvas.drawText(unit, centerX+ stepNumber.length()*80, getHeight() / 2 + bounds.height() * 3 / 2, mUnitPaint); } /** * 获取当前步数的数字的高度 * * @param fontSize 字体大小 * @return 字体高度 */ public int getFontHeight(float fontSize) { Paint paint = new Paint(); paint.setTextSize(fontSize); Rect bounds_Number = new Rect(); paint.getTextBounds(stepNumber, 0, stepNumber.length(), bounds_Number); return bounds_Number.height(); } /** * dip 转换成px * * @param dip * @return */ private int dipToPx(float dip) { float density = getContext().getResources().getDisplayMetrics().density; return (int) (dip * density + 0.5f * (dip >= 0 ? 1 : -1)); } /** * 所走的步数进度 * @param totalStepNum 设置的步数 * @param currentCounts 所走步数 */ public void setCurrentCount(int totalStepNum, int currentCounts) { /**如果当前走的步数超过总步数则圆弧还是270度,不能成为园*/ if (currentCounts > totalStepNum) { currentCounts = totalStepNum; } /**上次所走步数占用总共步数的百分比*/ float scalePrevious = (float) Integer.valueOf(stepNumber) / totalStepNum; /**换算成弧度最后要到达的角度的长度-->弧长*/ float previousAngleLength = scalePrevious * angleLength; /**所走步数占用总共步数的百分比*/ float scale = (float) currentCounts / totalStepNum; /**换算成弧度最后要到达的角度的长度-->弧长*/ float currentAngleLength = scale * angleLength; /**开始执行动画*/ setAnimation(previousAngleLength, currentAngleLength, animationLength); stepNumber = String.valueOf(currentCounts); setTextSize(currentCounts); } /** * 设置各个参数 */ public void setParams(String today, String unit, String type, String level) { this.today = today; this.unit = unit; this.type = type; this.level = level; } /** * 为进度设置动画 * ValueAnimator是整个属性动画机制当中最核心的一个类,属性动画的运行机制是通过不断地对值进行操作来实现的, * 而初始值和结束值之间的动画过渡就是由ValueAnimator这个类来负责计算的。 * 它的内部使用一种时间循环的机制来计算值与值之间的动画过渡, * 我们只需要将初始值和结束值提供给ValueAnimator,并且告诉它动画所需运行的时长, * 那么ValueAnimator就会自动帮我们完成从初始值平滑地过渡到结束值这样的效果。 * * @param start 初始值 * @param current 结束值 * @param length 动画时长 */ private void setAnimation(float start, float current, int length) { ValueAnimator progressAnimator = ValueAnimator.ofFloat(start, current); progressAnimator.setDuration(length); progressAnimator.setTarget(currentAngleLength); progressAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { /**每次在初始值和结束值之间产生的一个平滑过渡的值,逐步去更新进度*/ currentAngleLength = (float) animation.getAnimatedValue(); invalidate(); } }); progressAnimator.start(); } /** * 设置文本大小,防止步数特别大之后放不下,将字体大小动态设置 * * @param num */ public void setTextSize(int num) { String s = String.valueOf(num); int length = s.length(); if (length 4 && length 6 && length 8) { numberTextSize = dipToPx(25); } } } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 整个View的代码逻辑还是比较清晰的,自定义的view会首先调用onDraw()方法,获取了整个布局的中心和轮廓,然后开始七步分别绘制控件,这就是大家可以发挥的部分了,想怎么设计怎么设计。 第一步着重看一下,这个虚线的实现: float[] floats = {4,16,4,16}; paint.setPathEffect(new DashPathEffect(floats, 0)); 1 2 floats的四个参数意思是{画宽度,间隔宽度,画宽度,间隔宽度},这里的floats是数组也就是说你可以把每一个宽度和间隔都写出来,当然如果一样的话你可以只列出两个,系统会自动识别来延续后面的宽度和间隔。 因为注释很清楚,其他的这里就不详述了,想改啥基本都能看明白。 记步服务 public class StepService extends Service implements SensorEventListener { private String TAG = "StepService"; /** * 默认为10秒进行一次存储 */ private static int duration = 10 * 1000; /** * 传感器管理对象 */ private SensorManager sensorManager; /** * 保存记步计时器 */ private TimeCount time; /** * 当前所走的步数 */ private int CURRENT_STEP; /** * 计步传感器类型 Sensor.TYPE_STEP_COUNTER或者Sensor.TYPE_STEP_DETECTOR */ private static int stepSensorType = -1; /** * 每次第一次启动记步服务时是否从系统中获取了已有的步数记录 */ private boolean hasRecord = false; /** * 系统中获取到的已有的步数 */ private int hasStepCount = 0; /** * 上一次的步数 */ private int previousStepCount = 0; /** * IBinder对象,向Activity传递数据的桥梁 */ private StepBinder stepBinder = new StepBinder(); @Override public void onCreate() { super.onCreate(); Log.d(TAG, "onCreate()"); new Thread(new Runnable() { public void run() { startStepDetector(); } }).start(); startTimeCount(); } /** * 开始保存记步数据 */ private void startTimeCount() { if (time == null) { time = new TimeCount(duration, 1000); } time.start(); } /** * UI监听器对象 */ private UpdateUiCallBack mCallback; /** * 注册UI更新监听 * * @param paramICallback */ public void registerCallback(UpdateUiCallBack paramICallback) { this.mCallback = paramICallback; } @Override public IBinder onBind(Intent intent) { return stepBinder; } /** * 向Activity传递数据的纽带 */ public class StepBinder extends Binder { /** * 获取当前service对象 * * @return StepService */ public StepService getService() { return StepService.this; } } /** * 获取当前步数 * * @return */ public int getStepCount() { return CURRENT_STEP; } @Override public void onStart(Intent intent, int startId) { super.onStart(intent, startId); } @Override public int onStartCommand(Intent intent, int flags, int startId) { return START_STICKY; } /** * 获取传感器实例 */ private void startStepDetector() { if (sensorManager != null) { sensorManager = null; } // 获取传感器管理器的实例 sensorManager = (SensorManager) this .getSystemService(SENSOR_SERVICE); //android4.4以后可以使用计步传感器 addCountStepListener(); } /** * 添加传感器监听 * 1. TYPE_STEP_COUNTER API的解释说返回从开机被激活后统计的步数,当重启手机后该数据归零, * 该传感器是一个硬件传感器所以它是低功耗的。 * 为了能持续的计步,请不要反注册事件,就算手机处于休眠状态它依然会计步。 * 当激活的时候依然会上报步数。该sensor适合在长时间的计步需求。 * * 2.TYPE_STEP_DETECTOR翻译过来就是走路检测, * API文档也确实是这样说的,该sensor只用来监监测走步,每次返回数字1.0。 * 如果需要长事件的计步请使用TYPE_STEP_COUNTER。 */ private void addCountStepListener() { Sensor countSensor = sensorManager.getDefaultSensor(Sensor.TYPE_STEP_COUNTER); Sensor detectorSensor = sensorManager.getDefaultSensor(Sensor.TYPE_STEP_DETECTOR); if (countSensor != null) { stepSensorType = Sensor.TYPE_STEP_COUNTER; Log.v(TAG, "Sensor.TYPE_STEP_COUNTER"); sensorManager.registerListener(StepService.this, countSensor, SensorManager.SENSOR_DELAY_NORMAL); } else if (detectorSensor != null) { stepSensorType = Sensor.TYPE_STEP_DETECTOR; Log.v(TAG, "Sensor.TYPE_STEP_DETECTOR"); sensorManager.registerListener(StepService.this, detectorSensor, SensorManager.SENSOR_DELAY_NORMAL); } } /** * 传感器监听回调 * @param event */ @Override public void onSensorChanged(SensorEvent event) { if (stepSensorType == Sensor.TYPE_STEP_COUNTER) { //获取当前传感器返回的临时步数 int tempStep = (int) event.values[0]; //首次如果没有获取手机系统中已有的步数则获取一次系统中APP还未开始记步的步数 if (!hasRecord) { hasRecord = true; hasStepCount = tempStep; } else { //获取APP打开到现在的总步数=本次系统回调的总步数-APP打开之前已有的步数 int thisStepCount = tempStep - hasStepCount; //本次有效步数=(APP打开后所记录的总步数-上一次APP打开后所记录的总步数) int thisStep = thisStepCount - previousStepCount; //总步数=现有的步数+本次有效步数 CURRENT_STEP += (thisStep); //记录最后一次APP打开到现在的总步数 previousStepCount = thisStepCount; } } else if (stepSensorType == Sensor.TYPE_STEP_DETECTOR) { if (event.values[0] == 1.0) { CURRENT_STEP++; } } } @Override public void onAccuracyChanged(Sensor sensor, int accuracy) { } /** * 保存记步数据 */ class TimeCount extends CountDownTimer { public TimeCount(long millisInFuture, long countDownInterval) { super(millisInFuture, countDownInterval); } @Override public void onFinish() { // 如果计时器正常结束,则开始计步 time.cancel(); mCallback.updateUi(CURRENT_STEP); startTimeCount(); } @Override public void onTick(long millisUntilFinished) { } } @Override public void onDestroy() { super.onDestroy(); //取消前台进程 stopForeground(true); } @Override public boolean onUnbind(Intent intent) { return super.onUnbind(intent); } } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 因为记步是在后台进行的,开启之后我们可以干其他事情,所以采用服务的方式,而我们需要获取记步的数据,所以要通过绑定的方式来注册服务,这样才能获得通信用的connection。逻辑思路是,通过计步器来获取步数,然后设置监听器来监听步数的改变,如果步数改变更新步数的变量,然后开启一个计时器来定时去记录,设置回调参数来返回步数。回调接口只有一个方法: public interface UpdateUiCallBack { /** * 更新UI步数 * * @param stepCount 步数 */ void updateUi(int stepCount); } 1 2 3 4 5 6 7 8 当然别忘了在AndroidManifest.xml中注册Service: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 还有记步需要用的权限: 1 2 3 调用MainActivity public class MainActivity extends AppCompatActivity { private StepView stepView; private Button button; private boolean isBind = false; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); initView(); initData(); } private void initView() { stepView = (StepView) findViewById(R.id.step_walk_arv); button = (Button) findViewById(R.id.begin_btn); button.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { begin(); } }); } private void initData() { stepView.setParams("今日步数", "", "Walking", "等级:轻度活跃"); stepView.setCurrentCount(1000,0); } private void begin() { if (isBind == false){ //开启记步 Intent intent = new Intent(this, StepService.class); isBind = bindService(intent, conn, Context.BIND_AUTO_CREATE); startService(intent); button.setText("正在记步"); } } /** * 用于查询应用服务(application Service)的状态的一种interface, * 更详细的信息可以参考Service 和 context.bindService()中的描述, * 和许多来自系统的回调方式一样,ServiceConnection的方法都是进程的主线程中调用的。 */ ServiceConnection conn = new ServiceConnection() { /** * 在建立起于Service的连接时会调用该方法,目前Android是通过IBind机制实现与服务的连接。 * @param name 实际所连接到的Service组件名称 * @param service 服务的通信信道的IBind,可以通过Service访问对应服务 */ @Override public void onServiceConnected(ComponentName name, IBinder service) { StepService stepService = ((StepService.StepBinder) service).getService(); //设置初始化数据 stepView.setCurrentCount(1000, stepService.getStepCount()); //设置步数监听回调 stepService.registerCallback(new UpdateUiCallBack() { @Override public void updateUi(int stepCount) { stepView.setCurrentCount(1000, stepCount); } }); } /** * 当与Service之间的连接丢失的时候会调用该方法, * 这种情况经常发生在Service所在的进程崩溃或者被Kill的时候调用, * 此方法不会移除与Service的连接,当服务重新启动的时候仍然会调用 onServiceConnected()。 * @param name 丢失连接的组件名称 */ @Override public void onServiceDisconnected(ComponentName name) { } }; //服务解绑 @Override public void onDestroy() { super.onDestroy(); if (isBind) { unbindService(conn); isBind = false; } } } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 调用也很简单,首先在布局文件中引用StepView,然后初始化,设置圆弧里面的参数,设置总步数和当前步数(开始当然为0): stepView.setParams("今日步数", "", "Walking", "等级:轻度活跃"); stepView.setCurrentCount(1000,0); 1 2 其中还设置了一个boolean值isBind区分是否已经启动服务并绑定到了activity上,以便在onDestroy()方法是解除绑定。 整个计步器实现还是相对简单的,之前还想过要在圆弧圈外面画一个手指来指向当前的进度,不过暂时还没想通怎么实现,有兴趣的小伙伴研究之后告诉一声。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值