├── .gitignore ├── README.md ├── app ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ └── main │ ├── AndroidManifest.xml │ ├── java │ └── com │ │ └── today │ │ └── step │ │ ├── MainActivity.java │ │ ├── MyReceiver.java │ │ └── TSApplication.java │ └── res │ ├── layout │ └── activity_main.xml │ ├── mipmap-hdpi │ ├── ic_launcher.png │ └── ic_launcher_round.png │ ├── mipmap-mdpi │ ├── ic_launcher.png │ └── ic_launcher_round.png │ ├── mipmap-xhdpi │ ├── ic_launcher.png │ └── ic_launcher_round.png │ ├── mipmap-xxhdpi │ ├── ic_launcher.png │ └── ic_launcher_round.png │ ├── mipmap-xxxhdpi │ ├── ic_launcher.png │ └── ic_launcher_round.png │ └── values │ ├── colors.xml │ ├── strings.xml │ └── styles.xml ├── base-lib-notification ├── build.gradle ├── proguard-rules.pro └── src │ └── main │ ├── AndroidManifest.xml │ ├── java │ └── com │ │ └── andrjhf │ │ └── notification │ │ └── api │ │ └── compat │ │ └── NotificationApiCompat.java │ └── res │ └── values │ ├── colors.xml │ ├── strings.xml │ └── styles.xml ├── build.gradle ├── dependencies.gradle ├── gradle.properties ├── lib-jlogger ├── build.gradle ├── proguard-rules.pro └── src │ └── main │ ├── AndroidManifest.xml │ ├── java │ └── com │ │ └── andrjhf │ │ └── lib │ │ └── jlogger │ │ ├── JLoggerConstant.java │ │ ├── JLoggerWraper.java │ │ └── Utils.java │ └── res │ └── values │ └── strings.xml ├── lib-todaystepcounter ├── build.gradle ├── proguard-rules.pro └── src │ └── main │ ├── AndroidManifest.xml │ ├── aidl │ └── com │ │ └── today │ │ └── step │ │ └── lib │ │ └── ISportStepInterface.aidl │ ├── java │ └── com │ │ └── today │ │ └── step │ │ └── lib │ │ ├── BaseClickBroadcast.java │ │ ├── ConstantDef.java │ │ ├── DateUtils.java │ │ ├── ITodayStepDBHelper.java │ │ ├── OnStepCounterListener.java │ │ ├── PreferencesHelper.java │ │ ├── SportStepJsonUtils.java │ │ ├── StepUtil.java │ │ ├── TodayStepBootCompleteReceiver.java │ │ ├── TodayStepCounter.java │ │ ├── TodayStepDBHelper.java │ │ ├── TodayStepData.java │ │ ├── TodayStepDetector.java │ │ ├── TodayStepManager.java │ │ ├── TodayStepService.java │ │ ├── TodayStepShutdownReceiver.java │ │ └── WakeLockUtils.java │ └── res │ ├── mipmap-xxhdpi │ ├── ic_launcher.png │ └── ic_launcher_round.png │ └── values │ └── strings.xml ├── screenshots └── screenshots.png └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/workspace.xml 5 | /.idea/libraries 6 | .DS_Store 7 | /build 8 | /captures 9 | .externalNativeBuild 10 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Android计步模块(类似微信运动,支付宝计步,今日步数) 2 | 3 | [Android计步模块优化(今日步数)](http://www.jianshu.com/p/cfc2a200e46d) 4 | 5 | [Android计步模块优化(今日步数)V2.0.0](https://www.jianshu.com/p/1b53937150ad) 6 | 7 | ![图片源于网络.png](http://upload-images.jianshu.io/upload_images/4158487-ef235914605842d1.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 8 | 9 | ### 功能 10 | 1. 返回当天运动步数 11 | 2. 内部自动切换计步算法,适配所有手机 12 | 3. 通过AIDL对外暴露接口 13 | 4. 采用单独进程计步 14 | 15 | ### 优化点 16 | 1. 适配Android8.0系统 17 | 3. TYPE_ACCELEROMETER和TYPE_STEP_COUNTER传感器自动切换 18 | 4. 只提供当天的步数数据 19 | 5. 解决一些bug 20 | 6. 对关键位置增加日志信息(日志系统底层需要自己实现) 21 | 22 | [开源算法](https://github.com/finnfu/stepcount)这个是源码,如果有大神对他进行优化,非常欢迎和我进行讨论。 23 | 24 | #### 问题 25 | 1. 用户后台保活(对于加速度传感器必须后台保活),每个手机都不一样无法提供通用的标准操作 26 | 2. 早上打开一次,计步器会开始计步 27 | 3. 重启手机需要打开app,否则步数丢失 28 | 4. 如果遇到当天步数不准,或者不记步,需要重启手机,android计步协处理器会出现bug 29 | 5. 会有部分清零和极大值出现,这也是由于android计步协处理器出现问题导致的 30 | 6. 卸载app步数会清空,归零。 31 | 32 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | compileSdkVersion versions.compileSdk 5 | buildToolsVersion '28.0.3' 6 | 7 | defaultConfig { 8 | applicationId versions.applicationId 9 | minSdkVersion versions.minSdk 10 | targetSdkVersion versions.targetSdk 11 | versionCode change.code 12 | versionName change.name 13 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 14 | } 15 | buildTypes { 16 | debug{ 17 | } 18 | release { 19 | minifyEnabled false 20 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 21 | } 22 | } 23 | } 24 | 25 | dependencies { 26 | implementation fileTree(include: ['*.jar'], dir: 'libs') 27 | implementation libraries.supportAppCompat 28 | implementation project(':lib-todaystepcounter') 29 | } 30 | -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in /Users/jiahongfei/Documents/DeveloperSoftware/adt-bundle-mac-x86_64-20140702/sdk/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | 19 | # Uncomment this to preserve the line number information for 20 | # debugging stack traces. 21 | #-keepattributes SourceFile,LineNumberTable 22 | 23 | # If you keep the line number information, uncomment this to 24 | # hide the original source file name. 25 | #-renamesourcefileattribute SourceFile 26 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 13 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /app/src/main/java/com/today/step/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.today.step; 2 | 3 | import android.content.ComponentName; 4 | import android.content.Context; 5 | import android.content.Intent; 6 | import android.content.ServiceConnection; 7 | import android.os.Handler; 8 | import android.os.IBinder; 9 | import android.os.Message; 10 | import android.os.RemoteException; 11 | import android.support.v7.app.AppCompatActivity; 12 | import android.os.Bundle; 13 | import android.util.Log; 14 | import android.view.View; 15 | import android.widget.TextView; 16 | 17 | import com.today.step.lib.ISportStepInterface; 18 | import com.today.step.lib.TodayStepManager; 19 | import com.today.step.lib.TodayStepService; 20 | 21 | public class MainActivity extends AppCompatActivity { 22 | 23 | private static String TAG = "MainActivity"; 24 | 25 | private static final int REFRESH_STEP_WHAT = 0; 26 | 27 | //循环取当前时刻的步数中间的间隔时间 28 | private long TIME_INTERVAL_REFRESH = 3000; 29 | 30 | private Handler mDelayHandler = new Handler(new TodayStepCounterCall()); 31 | private int mStepSum; 32 | 33 | private ISportStepInterface iSportStepInterface; 34 | 35 | private TextView mStepArrayTextView; 36 | 37 | private TextView timeTextView; 38 | 39 | @Override 40 | protected void onCreate(Bundle savedInstanceState) { 41 | super.onCreate(savedInstanceState); 42 | setContentView(R.layout.activity_main); 43 | 44 | //初始化计步模块 45 | TodayStepManager.startTodayStepService(getApplication()); 46 | 47 | timeTextView = (TextView) findViewById(R.id.timeTextView); 48 | mStepArrayTextView = (TextView) findViewById(R.id.stepArrayTextView); 49 | 50 | //开启计步Service,同时绑定Activity进行aidl通信 51 | Intent intent = new Intent(this, TodayStepService.class); 52 | startService(intent); 53 | bindService(intent, new ServiceConnection() { 54 | @Override 55 | public void onServiceConnected(ComponentName name, IBinder service) { 56 | //Activity和Service通过aidl进行通信 57 | iSportStepInterface = ISportStepInterface.Stub.asInterface(service); 58 | try { 59 | mStepSum = iSportStepInterface.getCurrentTimeSportStep(); 60 | updateStepCount(); 61 | } catch (RemoteException e) { 62 | e.printStackTrace(); 63 | } 64 | mDelayHandler.sendEmptyMessageDelayed(REFRESH_STEP_WHAT, TIME_INTERVAL_REFRESH); 65 | 66 | } 67 | 68 | @Override 69 | public void onServiceDisconnected(ComponentName name) { 70 | 71 | } 72 | }, Context.BIND_AUTO_CREATE); 73 | 74 | //计时器 75 | mhandmhandlele.post(timeRunable); 76 | 77 | } 78 | 79 | class TodayStepCounterCall implements Handler.Callback { 80 | 81 | @Override 82 | public boolean handleMessage(Message msg) { 83 | switch (msg.what) { 84 | case REFRESH_STEP_WHAT: { 85 | //每隔500毫秒获取一次计步数据刷新UI 86 | if (null != iSportStepInterface) { 87 | int step = 0; 88 | try { 89 | step = iSportStepInterface.getCurrentTimeSportStep(); 90 | } catch (RemoteException e) { 91 | e.printStackTrace(); 92 | } 93 | if (mStepSum != step) { 94 | mStepSum = step; 95 | updateStepCount(); 96 | } 97 | } 98 | mDelayHandler.sendEmptyMessageDelayed(REFRESH_STEP_WHAT, TIME_INTERVAL_REFRESH); 99 | 100 | break; 101 | } 102 | } 103 | return false; 104 | } 105 | } 106 | 107 | private void updateStepCount() { 108 | Log.e(TAG, "updateStepCount : " + mStepSum); 109 | TextView stepTextView = (TextView) findViewById(R.id.stepTextView); 110 | stepTextView.setText(mStepSum + "步"); 111 | 112 | } 113 | 114 | public void onClick(View view) { 115 | switch (view.getId()) { 116 | case R.id.stepArrayButton: { 117 | //获取所有步数列表 118 | if (null != iSportStepInterface) { 119 | try { 120 | String stepArray = iSportStepInterface.getTodaySportStepArray(); 121 | mStepArrayTextView.setText(stepArray); 122 | } catch (RemoteException e) { 123 | e.printStackTrace(); 124 | } 125 | } 126 | break; 127 | } 128 | default: 129 | break; 130 | } 131 | } 132 | 133 | 134 | /*****************计时器*******************/ 135 | private Runnable timeRunable = new Runnable() { 136 | @Override 137 | public void run() { 138 | 139 | currentSecond = currentSecond + 1000; 140 | timeTextView.setText(getFormatHMS(currentSecond)); 141 | if (!isPause) { 142 | //递归调用本runable对象,实现每隔一秒一次执行任务 143 | mhandmhandlele.postDelayed(this, 1000); 144 | } 145 | } 146 | }; 147 | //计时器 148 | private Handler mhandmhandlele = new Handler(); 149 | private boolean isPause = false;//是否暂停 150 | private long currentSecond = 0;//当前毫秒数 151 | /*****************计时器*******************/ 152 | 153 | /** 154 | * 根据毫秒返回时分秒 155 | * 156 | * @param time 157 | * @return 158 | */ 159 | public static String getFormatHMS(long time) { 160 | time = time / 1000;//总秒数 161 | int s = (int) (time % 60);//秒 162 | int m = (int) (time / 60);//分 163 | int h = (int) (time / 3600);//秒 164 | return String.format("%02d:%02d:%02d", h, m, s); 165 | } 166 | } 167 | -------------------------------------------------------------------------------- /app/src/main/java/com/today/step/MyReceiver.java: -------------------------------------------------------------------------------- 1 | package com.today.step; 2 | 3 | import android.content.Context; 4 | import android.content.Intent; 5 | 6 | import com.today.step.lib.BaseClickBroadcast; 7 | 8 | public class MyReceiver extends BaseClickBroadcast { 9 | 10 | private static final String TAG = "MyReceiver"; 11 | 12 | @Override 13 | public void onReceive(Context context, Intent intent) { 14 | TSApplication tsApplication = (TSApplication) context.getApplicationContext(); 15 | if (!tsApplication.isForeground()) { 16 | Intent mainIntent = new Intent(context, MainActivity.class); 17 | mainIntent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_NEW_TASK); 18 | context.startActivity(mainIntent); 19 | } else { 20 | 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /app/src/main/java/com/today/step/TSApplication.java: -------------------------------------------------------------------------------- 1 | package com.today.step; 2 | 3 | import android.app.Activity; 4 | import android.app.Application; 5 | import android.os.Bundle; 6 | 7 | 8 | public class TSApplication extends Application { 9 | 10 | private static TSApplication sApplication; 11 | 12 | private int appCount = 0; 13 | 14 | @Override 15 | public void onCreate() { 16 | super.onCreate(); 17 | 18 | sApplication = this; 19 | 20 | registerActivityLifecycleCallbacks(new ActivityLifecycleCallbacks() { 21 | @Override 22 | public void onActivityCreated(Activity activity, Bundle savedInstanceState) { 23 | 24 | } 25 | 26 | @Override 27 | public void onActivityStarted(Activity activity) { 28 | appCount++; 29 | } 30 | 31 | @Override 32 | public void onActivityResumed(Activity activity) { 33 | 34 | } 35 | 36 | @Override 37 | public void onActivityPaused(Activity activity) { 38 | 39 | } 40 | 41 | @Override 42 | public void onActivityStopped(Activity activity) { 43 | appCount--; 44 | } 45 | 46 | @Override 47 | public void onActivitySaveInstanceState(Activity activity, Bundle outState) { 48 | 49 | } 50 | 51 | @Override 52 | public void onActivityDestroyed(Activity activity) { 53 | 54 | } 55 | }); 56 | } 57 | 58 | /** 59 | * app是否在前台 60 | * @return true前台,false后台 61 | */ 62 | public boolean isForeground(){ 63 | return appCount > 0; 64 | } 65 | 66 | public static TSApplication getApplication() { 67 | return sApplication; 68 | } 69 | 70 | } 71 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 |