精华内容
下载资源
问答
  • bfs 最少步数 千次阅读
    2018-03-01 14:21:26

    【例8.3】最少步数


    时间限制: 1000 ms        内存限制: 65536 KB
    提交数: 455     通过数:241 

    【题目描述】

    在各种棋中,棋子的走法总是一定的,如中国象棋中马走“日”。有一位小学生就想如果马能有两种走法将增加其趣味性,因此,他规定马既能按“日”走,也能如象一样走“田”字。他的同桌平时喜欢下围棋,知道这件事后觉得很有趣,就想试一试,在一个(100×100)的围棋盘上任选两点A、B,A点放上黑子,B点放上白子,代表两匹马。棋子可以按“日”字走,也可以按“田”字走,俩人一个走黑马,一个走白马。谁用最少的步数走到左上角坐标为(1,1)的点时,谁获胜。现在他请你帮忙,给你A、B两点的坐标,想知道两个位置到(1,1)点可能的最少步数。

    【输入】

    A、B两点的坐标。

    【输出】

    最少步数。

    【输入样例】

    12 16
    18 10

    【输出样例】

    8
    9

    【来源】


    No

    【算法分析】

    因为A,B两点是随机输入的,所以没有数学规律,只能用广度优化搜索。

    但是,它们终点一样,所以我们可以把(1,1)看作起点,把AB看作终点,只需要一次广度优化搜索。

    Bfs主要就是队列思想,可以用que[k][1],que[k][2]记录从(1,1)到该点,用que[k][3]记录最小步数。初始时,que中只有一个元素(1,1),最小步数为0。

    A数组记录(1,1)到每点所需要的最小步数。初始时,a[1][1]=0,除此之外的所有元素值设为-1。

    约束条件:

    不能越出界外。由于马的所有可能的落脚点s均在s的范围内,因此马一越界就将s值赋为0,表示已经扩展过,到达这里至少需要0布,但这样可以避免马再次落入这些界外点。

    以前到达过得点,无需在到,第一次到即为最小步数。

    【代码实现】

     

    #include<bits/stdc++.h>
    using namespace std;
    int dx[12]={-2,-2,-1,1,2,2,2,2,1,-1,-2,-2},
        dy[12]={-1,-2,-2,-2,-2,-1,1,2,2,2,2,1};
    int main()
    {
    
        int a[105][105],que[10000][4]={0};
        memset(a,0xff,sizeof(a));  //初始化a为-1
        int head=0,tail=1;   
        int x1,y1,x2,y2;
        cin>>x1>>y1>>x2>>y2;
        que[1][1]=1;
        que[1][2]=1;
        que[1][3]=0;//初始化队列,(1,1)  最少步数0
        int x,y;
        do
        {
            head++;
            for(int k=0;k<12;k++)  //12个方向扩展
            {
                x=que[head][1]+dx[k];
                y=que[head][2]+dy[k];
                if(x>0&&y>0)
                if(a[x][y]==-1)  //a[x][y]必须为没扩展过
                {
                    tail++;
                    a[x][y]=que[head][3]+1;//计算(1,1)到先(x,y)的最小步数
                    que[tail][1]=x;        //(1,1)到(x,y)的最小步数入队
                    que[tail][2]=y;
                    que[tail][3]=a[x][y];
                    while(a[x1][y1]>0&&a[x2][y2]>0)//输出
                  {
                        cout<<a[x1][y1]<<endl;
                        cout<<a[x2][y2]<<endl;
                        system("pause");   //提交时,删掉,不然函数受限
                        return 0;
                    }
                }
            }
        }while(head<tail);
        return 0;
    }
    
    

    更多相关内容
  • 在Android4.4版本之后,部分机型实现了Sensor.TYPE_STEP_COUNTER传感器,用于纪录用户行走的步数。从手机开机开始纪录,手机关机时重置0。 这个记步芯片是系统级别的,相对之前老版本的传感器记步,性能一些优化...

    目前android计步有两种方式

    系统计步芯片

    在Android4.4版本之后,部分机型实现了Sensor.TYPE_STEP_COUNTER传感器,用于纪录用户行走的步数。从手机开机开始纪录,手机关机时重置为0。
    这个记步芯片是系统级别的,相对之前老版本的传感器记步,性能有一些优化:
    不会因为App单独用了记步的功能而额外耗电
    系统芯片记步是持续的,能够优化部分机型后台不记步的问题。

    加速度传感器计算方式

    加速度传感器非常耗电,导致App的耗电量很高,影响用户体验。
    需要后台实时运行才能实现记步的功能,如果App进程被系统或者安全软件杀死,导致记步功能没办法使用

     


    开发之前的调研工作,搜遍baidu,google,github都没有找到我想要的demo和文章,大多数都是需要Service保活。 
    对于各大手机厂商为了提高电池的续航里程AlertManager、BOOT_COMPLETED、Service的START_STICKY基本上都是不起作用的,Service后台保活更是不可能。 
    下面是我实现的计步模块和大家一起学习 
     

    App计步模块优化的三个过程
    第一个过程上线: 
    由于功能着急上线,项目最开始计步模块单独使用加速度传感器Sensor.TYPE_ACCELEROMETER进行计算步数,同时Service需要在后台存活才能计步,否则不能计步。

    第二个过程计步器: 
    项目运行一段时间公司开始推广走路计步这个模块,所以开始重新开发计步模块,这次使用了Android4.4以上提供的计步传感器Sensor.TYPE_STEP_COUNTER来完成,这次重新开发整个计步模块有了质的飞跃,由于采用了计步传感器不在需要后台保活Service,同时计步传感器的功耗特别低,整个模块也更省电了。

    第三个过程优化计步: 
    用户量变大了投诉也变多了,android各种各样的机型真是让人蛋疼,终于到了第三个阶段优化阶段。 
    目前最多的问题: 
    1.Android4.4以上的系统但是手机没有计步协处理器 
    2.部分手机步数出现暴增现象,有可能一天几十万步 
    3.部分手机出现一天清零好多次。 
    4.开机计步不能自启动,需要打开app(我已经监听BOOT_COMPLETED广播) 
    5.隔天分隔(0点分隔)不好用每天早上需要打开app(我已经设置AlertManager)

    这篇文章就来介绍现在app的计步模块,已经解决上述问题1、2、3,至于4、5问题是系统问题正在寻找解决方案,大家也可以帮帮忙在评论中给我提示。 
    已经将计步模块单独封装成libModule上传github如果有开发者需要的或者想交流的可以很方便的使用下载,点击这里下载。

    计步方式背景知识
    1.加速度传感器Sensor.TYPE_ACCELEROMETER计步方式: 
    这种方式是有开源的算法根据加速度传感器进行计算步数,点击这里查看原作者源码; 
    优点:只要有加速度传感器的设备都可以使用,相对来说可以使用的设备较多。 
    缺点:步数的准确性取决于算法且算法比较难优化;需要后台保活Service否则不能计步;计步算法比较费电;部分手机锁屏不能计步;

    2.计步传感器Sensor.TYPE_STEP_COUNTER计步方式: 
    官方解释翻译(本人英文不是很好根据理解翻译,如有错误请指出): 
    这个传感器是返回手机系统启动到当前时间的所有步数。手机系统重启传感器返回步数为0。还返回一个时间戳,表示最后一次步数的时间。这个计步传感器是个硬件,功耗非常低。如果你想记录步数,注册该传感器不要注销,他能自动在后台计步,在app唤醒的时候会返回计步总数。应用程序需要注册该传感器,否则不能返回步数。 
    优点:硬件计步准确性高;功耗小;只要注册不用后台Service自动计步; 
    缺点:Android4.4系统以上的部分手机;手机系统重启计步器清零;不能返回步数明细(步数对应时间),只是返回当前时间的总步数。

    计步模块两种计步方式都采用: 
    判断是否支持Sensor.TYPE_STEP_COUNTER如果支持采用计步传感器,如果不支持用加速度传感器计步。 
    使用加速度传感器计步需要用户自己手动设置后台自启动,否则不能计步。 
    使用计步传感器需要在程序中克服他的缺点:手机系统重启计步器清零;不能返回步数明细(步数对应时间),只是返回当前时间的总步数。

    先介绍接入方法,在介绍计步模块原理

    接入方法
    1.先下载计步demo TodayStepCounter 
    2.demo项目结构如下图: 
     TodayStepCounter项ç®ç»æå¾.png
    由图可见todaystepcounterlib是计步模块封装好的Module,它对外提供的接口就是ISportStepInterface.aidl 
    3.如何接入: 
    查看对外接口ISportStepInterface.aidl如下代码:

    // ISportStepInterface.aidl
    package com.today.step.lib;
    interface ISportStepInterface {
        /**
         * 获取当前时间运动步数
         */
         int getCurrentTimeSportStep();
         /**
          * 获取当天步数列表,json格式
          */
         String getTodaySportStepArray();
    }


    查看使用代码MainActivity.java,里面关键代码有注释非常简单

    public class MainActivity extends AppCompatActivity {
        private static String TAG = "MainActivity";
        private static final int REFRESH_STEP_WHAT = 0;
        //循环取当前时刻的步数中间的间隔时间
        private long TIME_INTERVAL_REFRESH = 500;
        private Handler mDelayHandler = new Handler(new TodayStepCounterCall());
        private int mStepSum;
        private ISportStepInterface iSportStepInterface;
        private TextView mStepArrayTextView;
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            //初始化计步模块
            TodayStepManager.init(getApplication());
            mStepArrayTextView = (TextView)findViewById(R.id.stepArrayTextView);
            //开启计步Service,同时绑定Activity进行aidl通信
            Intent intent = new Intent(this, TodayStepService.class);
            startService(intent);
            bindService(intent, new ServiceConnection() {
                @Override
                public void onServiceConnected(ComponentName name, IBinder service) {
                    //Activity和Service通过aidl进行通信
                    iSportStepInterface = ISportStepInterface.Stub.asInterface(service);
                    try {
                        mStepSum = iSportStepInterface.getCurrentTimeSportStep();
                        updateStepCount();
                    } catch (RemoteException e) {
                        e.printStackTrace();
                    }
         mDelayHandler.sendEmptyMessageDelayed(REFRESH_STEP_WHAT, TIME_INTERVAL_REFRESH);

                }
                @Override
                public void onServiceDisconnected(ComponentName name) {
                }
            }, Context.BIND_AUTO_CREATE);
        }
        class TodayStepCounterCall implements Handler.Callback{
            @Override
            public boolean handleMessage(Message msg) {
                switch (msg.what) {
                    case REFRESH_STEP_WHAT: {
                        //每隔500毫秒获取一次计步数据刷新UI
                        if (null != iSportStepInterface) {
                            int step = 0;
                            try {
                                step = iSportStepInterface.getCurrentTimeSportStep();
                            } catch (RemoteException e) {
                                e.printStackTrace();
                            }
                            if (mStepSum != step) {
                                mStepSum = step;
                                updateStepCount();
                            }
                        }
                     mDelayHandler.sendEmptyMessageDelayed(REFRESH_STEP_WHAT, TIME_INTERVAL_REFRESH);
                        break;
                    }
                }
                return false;
            }
        }
        private void updateStepCount() {
            Log.e(TAG,"updateStepCount : " + mStepSum);
            TextView stepTextView = (TextView)findViewById(R.id.stepTextView);
            stepTextView.setText(mStepSum + "步");
        }
        public void onClick(View view){
            switch (view.getId()){
                case R.id.stepArrayButton:{
                    //显示当天计步数据详细,步数对应当前时间
                    if(null != iSportStepInterface){
                        try {
                            String stepArray = iSportStepInterface.getTodaySportStepArray();
                            mStepArrayTextView.setText(stepArray);
                        } catch (RemoteException e) {
                            e.printStackTrace();
                        }
                    }
                    break;
                }
                default:break;
            }
        }
    }


    计步模块原理

    计步模åæµç¨å¾.png
    计步模块流程图
     
    讲解流程图: 
    1.整个计步模块是由一个运行在单独进程的Service(TodayStepService)来提供,由于运行在单独的进程所以对外提供的接口采用aidl形式(ISportStepInterface)。 
    2.零点分隔广播(TodayStepAlertReceive):用来解决跨天计步模块归零问题,由于计步传感器不会根据天来分割只是返回当前步数的总和,所以需要这个广播来对计步模块进行分割,只要跨天了计步模块就归零从0开始计步。 
    3.开机广播(TodayStepBootCompleteReceiver):开机广播用来解决手机重启计步传感器归零问题,由于计步传感器手机重启会归零,所以收到开机广播会做步数合并,启动Service从上次关机的步数开始累加。 
    4.数据库(TodayStepDBHelper):用来记录当天步数明细,一个时间对应一个步数 
    5.加速度传感器计步(TodayStepDcretor):由于android4.4以下或者一些特殊的手机不提供计步传感器所以这些机型采用加速度传感器进行计步,通过OnStepCounterListener监听返回给TodayStepService . 
    6.计步传感器计步(TodayStepCounter):android4.4以上提供了计步协处理器,可以通过计步传感器计步功耗小,计步准,通过OnStepCounterListener监听返回给TodayStepService . 
    7.关机监听(TodayStepShutdownReceiver):用来判断手机是否关机,当重启手机打开计步Service根据这个标志来判断是否重启进行步数合并,主要是增加精度有时开机广播不能收到。

    加速度传感器计步流程图
     å é度传æå¨è®¡æ­¥æµç¨å¾.png
    讲解流程图: 
    Android4.4以下或者一些特殊的手机不提供计步传感器,我只能用加速度传感器计步,加速度传感器的原理就是利用一定的算法模拟出步数(加速度传感器计步算法不在本篇文章讨论的范围之内),用这种方式计步Service一定要在后台存活否则不能计步,这种方式跨天分隔步数利用Intent.ACTION_TIME_TICK广播回调来判断当前时间和上次PreferencesHelper记录的时间是否相同如果不同步数归零从0开始计步,步数的记录采用PreferencesHelper来保存,防止当天重启手机系统步数归零。

    计步传感器计步流程图
     è®¡æ­¥ä¼ æå¨è®¡æ­¥æµç¨å¾.png
    讲解流程图: 
    Android4.4以上的可以使用计步传感器进行计步,至于计步传感器的有点上面已经介绍了。这种方式部分手机可以不需要程序自启动权限。 
    跨天分隔步数采用两种方式: 
    1.第一种方式和上面一样采用Intent.ACTION_TIME_TICK广播,这里不多说了。 
    2.第二种方式采用AlertManager方式也就是设置0点闹钟,在这个0点广播中对步数进行分隔,这个AlertManager不是每个手机都可以启动的。 
    手机系统重启判断采用四种方式: 
    1.开机广播监听BOOT_COMPLETED,这个监听不是每个手机都可以收到,如果收到可以启动Service,然后做步数合并使计步模块从上次关机时的步数开始累加,如果收不到只能用下面几种方式增加重启的判断了。 
    2.关机广播监听ACTION_SHUTDOWN,这个监听不是每个手机都可以收到,如果收到,在用户手动启动 Service中可以判断系统重启了。 
    3.记录运行时间判断手机重启,上次运行的时间大于当前运行时间判断为重启,只是增加精度,极端情况下连续重启,会判断不出来。 
    4.上次传感器步数总和,当前传感器步数小于上次传感器步数肯定是重新启动了,只是用来增加精度不是绝对的

    计步传感器计步核心流程

    计步核å¿æµç¨.png

    这个流程图的讲解都在图片上。

    提高计步精度:
    1.设置app**后台自启动** 
    2.各种安全软件设置app为白名单,为了保证app不被任何安全软件在后台杀死。 
    以上两种方式保证通知栏中一直显示app的步数。 
    3.手机系统重启,如果通知栏中没有显示步数,表示app没有收到开机监听,需要手动启动app,否则步数会丢失 。 
    app一直在后台存活肯定会耗电,部分手机可以在后台关闭的情况下计步,但是这种方式需要每天早上打开一次app让计步模块对步数进行清零否则步数会丢失。

    需要优化:
    1.每次传感器回调都会写三次SharedPreferences。 
    2.计步模块在后台存活,每天过0点开始计步都会丢失一些步数,丢失的步数跟启动计步传感器需要的步数有关,例如:我的测试机连续走10步才可以启动计步传感器回调,所以就丢失10步。

    总结:
    Android计步就是在和android系统作斗争,各种系统监听回调都不好用(AlertManager、BOOT_COMPLETED、JobScheduler),还要解决计步传感器的一些限制(系统重启清零,不能自动分天,部分手机进程杀死不能计步),还要规避不同手机的问题,我们只能尽量做到不丢失步数,提高计步精度,目前我在测试计步发现支付宝计步非常准,我猜测系统为支付宝做了系统进程。 
    只有不断天坑,优化,增加计步准确性,也请个位大神下载代码一起交流。

    特此感谢 https://github.com/finnfu/stepcount 作者

     

    简书地址:https://www.jianshu.com/p/cfc2a200e46d 


    github地址:https://github.com/jiahongfei/TodayStepCounter

    展开全文
  • Android计步模块优化(今日步数

    千次阅读 2017-12-22 16:37:39
    最近在项目中研究计步模块,主要功能记录当天步数,类似微信运动,支付宝计步,咕咚今日步数

    简书地址:https://www.jianshu.com/p/cfc2a200e46d
    github地址:https://github.com/jiahongfei/TodayStepCounter

    最近在项目中研究计步模块,主要功能记录当天步数,类似微信运动,支付宝计步,咕咚今日步数。
    开发之前的调研工作,搜遍baidu,google,github都没有找到我想要的demo和文章,大多数都是需要Service保活。
    对于各大手机厂商为了提高电池的续航里程AlertManager、BOOT_COMPLETED、Service的START_STICKY基本上都是不起作用的,Service后台保活更是不可能。
    下面是我实现的计步模块和大家一起学习
    github地址
    之前也有一篇文章写计步模块,这篇文章是对上面文章的优化,github代码已经更新到最新了。
    Android计步模块(类似微信运动)

    App计步模块优化的三个过程

    第一个过程上线:
    由于功能着急上线,项目最开始计步模块单独使用加速度传感器Sensor.TYPE_ACCELEROMETER进行计算步数,同时Service需要在后台存活才能计步,否则不能计步。

    第二个过程计步器:
    项目运行一段时间公司开始推广走路计步这个模块,所以开始重新开发计步模块,这次使用了Android4.4以上提供的计步传感器Sensor.TYPE_STEP_COUNTER来完成,这次重新开发整个计步模块有了质的飞跃,由于采用了计步传感器不在需要后台保活Service,同时计步传感器的功耗特别低,整个模块也更省电了。

    第三个过程优化计步:
    用户量变大了投诉也变多了,android各种各样的机型真是让人蛋疼,终于到了第三个阶段优化阶段。
    目前最多的问题:
    1.Android4.4以上的系统但是手机没有计步协处理器
    2.部分手机步数出现暴增现象,有可能一天几十万步
    3.部分手机出现一天清零好多次。
    4.开机计步不能自启动,需要打开app(我已经监听BOOT_COMPLETED广播)
    5.隔天分隔(0点分隔)不好用每天早上需要打开app(我已经设置AlertManager)

    这篇文章就来介绍现在app的计步模块,已经解决上述问题1、2、3,至于4、5问题是系统问题正在寻找解决方案,大家也可以帮帮忙在评论中给我提示。
    已经将计步模块单独封装成libModule上传github如果有开发者需要的或者想交流的可以很方便的使用下载,点击这里下载

    计步方式背景知识

    1.加速度传感器Sensor.TYPE_ACCELEROMETER计步方式:
    这种方式是有开源的算法根据加速度传感器进行计算步数,点击这里查看原作者源码
    优点:只要有加速度传感器的设备都可以使用,相对来说可以使用的设备较多。
    缺点:步数的准确性取决于算法且算法比较难优化;需要后台保活Service否则不能计步;计步算法比较费电;部分手机锁屏不能计步;

    2.计步传感器Sensor.TYPE_STEP_COUNTER计步方式:
    官方解释翻译(本人英文不是很好根据理解翻译,如有错误请指出):
    这个传感器是返回手机系统启动到当前时间的所有步数。手机系统重启传感器返回步数为0。还返回一个时间戳,表示最后一次步数的时间。这个计步传感器是个硬件,功耗非常低。如果你想记录步数,注册该传感器不要注销,他能自动在后台计步,在app唤醒的时候会返回计步总数。应用程序需要注册该传感器,否则不能返回步数。
    优点:硬件计步准确性高;功耗小;只要注册不用后台Service自动计步;
    缺点:Android4.4系统以上的部分手机;手机系统重启计步器清零;不能返回步数明细(步数对应时间),只是返回当前时间的总步数。

    计步模块两种计步方式都采用:
    判断是否支持Sensor.TYPE_STEP_COUNTER如果支持采用计步传感器,如果不支持用加速度传感器计步。
    使用加速度传感器计步需要用户自己手动设置后台自启动,否则不能计步。
    使用计步传感器需要在程序中克服他的缺点:手机系统重启计步器清零;不能返回步数明细(步数对应时间),只是返回当前时间的总步数。

    先介绍接入方法,在介绍计步模块原理

    接入方法

    1.先下载计步demo TodayStepCounter
    2.demo项目结构如下图:
    TodayStepCounter项目结构图.png
    由图可见todaystepcounterlib是计步模块封装好的Module,它对外提供的接口就是ISportStepInterface.aidl
    3.如何接入:
    查看对外接口ISportStepInterface.aidl如下代码:

    // ISportStepInterface.aidl
    package com.today.step.lib;
    interface ISportStepInterface {
        /**
         * 获取当前时间运动步数
         */
         int getCurrentTimeSportStep();
         /**
          * 获取当天步数列表,json格式
          */
         String getTodaySportStepArray();
    }

    查看使用代码MainActivity.java,里面关键代码有注释非常简单

    public class MainActivity extends AppCompatActivity {
        private static String TAG = "MainActivity";
        private static final int REFRESH_STEP_WHAT = 0;
        //循环取当前时刻的步数中间的间隔时间
        private long TIME_INTERVAL_REFRESH = 500;
        private Handler mDelayHandler = new Handler(new TodayStepCounterCall());
        private int mStepSum;
        private ISportStepInterface iSportStepInterface;
        private TextView mStepArrayTextView;
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            //初始化计步模块
            TodayStepManager.init(getApplication());
            mStepArrayTextView = (TextView)findViewById(R.id.stepArrayTextView);
            //开启计步Service,同时绑定Activity进行aidl通信
            Intent intent = new Intent(this, TodayStepService.class);
            startService(intent);
            bindService(intent, new ServiceConnection() {
                @Override
                public void onServiceConnected(ComponentName name, IBinder service) {
                    //Activity和Service通过aidl进行通信
                    iSportStepInterface = ISportStepInterface.Stub.asInterface(service);
                    try {
                        mStepSum = iSportStepInterface.getCurrentTimeSportStep();
                        updateStepCount();
                    } catch (RemoteException e) {
                        e.printStackTrace();
                    }
         mDelayHandler.sendEmptyMessageDelayed(REFRESH_STEP_WHAT, TIME_INTERVAL_REFRESH);
    
                }
                @Override
                public void onServiceDisconnected(ComponentName name) {
                }
            }, Context.BIND_AUTO_CREATE);
        }
        class TodayStepCounterCall implements Handler.Callback{
            @Override
            public boolean handleMessage(Message msg) {
                switch (msg.what) {
                    case REFRESH_STEP_WHAT: {
                        //每隔500毫秒获取一次计步数据刷新UI
                        if (null != iSportStepInterface) {
                            int step = 0;
                            try {
                                step = iSportStepInterface.getCurrentTimeSportStep();
                            } catch (RemoteException e) {
                                e.printStackTrace();
                            }
                            if (mStepSum != step) {
                                mStepSum = step;
                                updateStepCount();
                            }
                        }
                     mDelayHandler.sendEmptyMessageDelayed(REFRESH_STEP_WHAT, TIME_INTERVAL_REFRESH);
                        break;
                    }
                }
                return false;
            }
        }
        private void updateStepCount() {
            Log.e(TAG,"updateStepCount : " + mStepSum);
            TextView stepTextView = (TextView)findViewById(R.id.stepTextView);
            stepTextView.setText(mStepSum + "步");
        }
        public void onClick(View view){
            switch (view.getId()){
                case R.id.stepArrayButton:{
                    //显示当天计步数据详细,步数对应当前时间
                    if(null != iSportStepInterface){
                        try {
                            String stepArray = iSportStepInterface.getTodaySportStepArray();
                            mStepArrayTextView.setText(stepArray);
                        } catch (RemoteException e) {
                            e.printStackTrace();
                        }
                    }
                    break;
                }
                default:break;
            }
        }
    }

    计步模块原理

    计步模块流程图

    计步模块流程图.png
    讲解流程图:
    1.整个计步模块是由一个运行在单独进程的Service(TodayStepService)来提供,由于运行在单独的进程所以对外提供的接口采用aidl形式(ISportStepInterface)。
    2.零点分隔广播(TodayStepAlertReceive):用来解决跨天计步模块归零问题,由于计步传感器不会根据天来分割只是返回当前步数的总和,所以需要这个广播来对计步模块进行分割,只要跨天了计步模块就归零从0开始计步。
    3.开机广播(TodayStepBootCompleteReceiver):开机广播用来解决手机重启计步传感器归零问题,由于计步传感器手机重启会归零,所以收到开机广播会做步数合并,启动Service从上次关机的步数开始累加。
    4.数据库(TodayStepDBHelper):用来记录当天步数明细,一个时间对应一个步数
    5.加速度传感器计步(TodayStepDcretor):由于android4.4以下或者一些特殊的手机不提供计步传感器所以这些机型采用加速度传感器进行计步,通过OnStepCounterListener监听返回给TodayStepService .
    6.计步传感器计步(TodayStepCounter):android4.4以上提供了计步协处理器,可以通过计步传感器计步功耗小,计步准,通过OnStepCounterListener监听返回给TodayStepService .
    7.关机监听(TodayStepShutdownReceiver):用来判断手机是否关机,当重启手机打开计步Service根据这个标志来判断是否重启进行步数合并,主要是增加精度有时开机广播不能收到。

    加速度传感器计步流程图

    加速度传感器计步流程图.png
    讲解流程图:
    Android4.4以下或者一些特殊的手机不提供计步传感器,我只能用加速度传感器计步,加速度传感器的原理就是利用一定的算法模拟出步数(加速度传感器计步算法不在本篇文章讨论的范围之内),用这种方式计步Service一定要在后台存活否则不能计步,这种方式跨天分隔步数利用Intent.ACTION_TIME_TICK广播回调来判断当前时间和上次PreferencesHelper记录的时间是否相同如果不同步数归零从0开始计步,步数的记录采用PreferencesHelper来保存,防止当天重启手机系统步数归零。

    计步传感器计步流程图

    计步传感器计步流程图.png
    讲解流程图:
    Android4.4以上的可以使用计步传感器进行计步,至于计步传感器的有点上面已经介绍了。这种方式部分手机可以不需要程序自启动权限。
    跨天分隔步数采用两种方式:
    1.第一种方式和上面一样采用Intent.ACTION_TIME_TICK广播,这里不多说了。
    2.第二种方式采用AlertManager方式也就是设置0点闹钟,在这个0点广播中对步数进行分隔,这个AlertManager不是每个手机都可以启动的。
    手机系统重启判断采用四种方式:
    1.开机广播监听BOOT_COMPLETED,这个监听不是每个手机都可以收到,如果收到可以启动Service,然后做步数合并使计步模块从上次关机时的步数开始累加,如果收不到只能用下面几种方式增加重启的判断了。
    2.关机广播监听ACTION_SHUTDOWN,这个监听不是每个手机都可以收到,如果收到,在用户手动启动 Service中可以判断系统重启了。
    3.记录运行时间判断手机重启,上次运行的时间大于当前运行时间判断为重启,只是增加精度,极端情况下连续重启,会判断不出来。
    4.上次传感器步数总和,当前传感器步数小于上次传感器步数肯定是重新启动了,只是用来增加精度不是绝对的

    计步传感器计步核心流程

    计步核心流程.png
    这个流程图的讲解都在图片上。

    提高计步精度:

    1.设置app**后台自启动**
    2.各种安全软件设置app为白名单,为了保证app不被任何安全软件在后台杀死。
    以上两种方式保证通知栏中一直显示app的步数。
    3.手机系统重启,如果通知栏中没有显示步数,表示app没有收到开机监听,需要手动启动app,否则步数会丢失 。
    app一直在后台存活肯定会耗电,部分手机可以在后台关闭的情况下计步,但是这种方式需要每天早上打开一次app让计步模块对步数进行清零否则步数会丢失。

    需要优化:

    1.每次传感器回调都会写三次SharedPreferences。
    2.计步模块在后台存活,每天过0点开始计步都会丢失一些步数,丢失的步数跟启动计步传感器需要的步数有关,例如:我的测试机连续走10步才可以启动计步传感器回调,所以就丢失10步。

    总结:

    Android计步就是在和android系统作斗争,各种系统监听回调都不好用(AlertManagerBOOT_COMPLETEDJobScheduler),还要解决计步传感器的一些限制(系统重启清零,不能自动分天,部分手机进程杀死不能计步),还要规避不同手机的问题,我们只能尽量做到不丢失步数,提高计步精度,目前我在测试计步发现支付宝计步非常准,我猜测系统为支付宝做了系统进程。
    只有不断天坑,优化,增加计步准确性,也请个位大神下载代码一起交流。

    特此感谢 https://github.com/finnfu/stepcount 作者

    展开全文
  • 本项目基于微信手机应用平台的一款运动互动型小程序,实现了用户即时运动步数群内PK与个人动态的发布,小程序前端采用原生框架,后端采用基于Node的koa2框架,数据库采用MYSQL,对象存储采用七牛云,服务器采用...

    1. 项目规划

    本项目为基于微信手机应用平台的一款运动互动型小程序,实现了用户即时运动步数群内PK与个人动态的发布小程序前端采用原生框架,后端采用基于Node的koa2框架,数据库采用MYSQL,对象存储采用七牛云,服务器采用阿里ECS,域名采用CA认证。

    运行效果如下

    2. 支撑技术点分析

    2.1 七牛云存储

    在这个项目中,有个功能为动态发布,允许用户上传图片,动态发布后,所有人可在动态广场看到该动态,存储图片有很多方式,例如通过表单将文件转为为二级制发送给后端,然后存数据库中,但是,这样我就要多写很多代码,所有我决定将图片存储到图床,我数据库中保存图片链接即可,图床有很多,不一一描述了,我这次用的是七牛云,个人认证成功后将获得一定空间的免费存储空间。

    建立存储空间(ydonline),选定存储区域(华北)。

    注册成功后,将获得两组秘钥,这东西很重要,上传文件时,需要知道uptoken,uptoken是根据AK与SC生成的,七牛云开发文档中建议后端生成uptoken值,但我嫌麻烦,直接在线生成了uptoken,也就是说该uptoken是写死的。

    uptoken生成地址:http://pchou.qiniudn.com/qiniutool/uptoken.html deadline的时间设置长一些

    引入官方开发文件:qiniuUploader.js

    小程序端存储图片关键代码:

    releaseNotice.js
    const qiniuUploader = require("qiniuUploader.js");
    
    function didPressChooesImage(that) {
      initQiniu();
      // 微信 API 选文件
      wx.chooseImage({
        count: 1,
        success: function (res) {
          var filePath = res.tempFilePaths[0];
          // 交给七牛上传
          qiniuUploader.upload(filePath, (res) => {
            let imageurl = that.data.imageurl;
            imageurl.push(getApp().data.qiniu_domain + res.key);
            that.setData({
              'imageurl': imageurl
            });
          }, (error) => {
            console.error('error: ' + JSON.stringify(error));
          });
        }
      }
      )
    }
    
    function initQiniu() {
      //配置详解 https://github.com/gpake/qiniu-wxapp-sdk/blob/master/README.md
      var options = {
        region: 'NCN', // 华东区 根据存储区域填写
        //uptokenURL: 'UpTokenURL.com/uptoken', 从指定 url 通过 HTTP GET 获取 uptoken,返回的格式必须是 json 且包含 uptoken 字段,例如: {"uptoken": "0MLvWPnyy..."}
        uptoken: getApp().data.qiniu_uptoken,
        domain: getApp().data.qiniu_domain
      };
      qiniuUploader.init(options);
    }
    
    
    ---------------------------------------------------------------------------------------  
    app.js
    
    App({
      data: {
        qiniu_domain: 'http://pgwn32i53.bkt.clouddn.com/',//七牛图片外链域名
        qiniu_uptoken: 'uxQXOgxXDtF-1uM_V15KQSIky5Xkdww0GhoAksLF:LWUt0HMVbICEDaSOMnMF3YLoUH4=:eyJzY29wZSI6Inlkb25saW5lIiwiZGVhZGxpbmUiOjE4NTU2NzA0MDF9'
    
      },
    })
    
    
    

    上传文件到指定的存储空间,七牛会返回文件的key值,加上七牛给你的外链域名,这样,你就可以显示文件了。


    2.2 服务器与域名

    我买过两次服务器,第一次是阿里的,另外一次也是阿里的。但这次,我买的是windows版云ECS ,首先,明确需求。

    1. 远程连接部署项目(安装环境,运行后台,域名解析,外网访问)
    2. 进行域名CA认证, 小程序的请求必须得是HTTPS。

    在windows 服务器上部署项目 简单, 远程链接时,选择共享本地某个硬盘的资料,连接成功后,把本地的环境软件全部安装上去,本次需要在服务器上安装 node.js、git、mysql这三个软件。

    购买SSL证书

    https://yundunnext.console.aliyun.com/?p=casnext#/overview/cn-hangzhou

    阿里有对单域名免费的证书,时间为1年,于是我为该项目的域名购买了https。

    点击下载 =>选择 其他

    解压后,里面有两个文件,一个是crt,一个是key,将这两个文件发在后台文件夹下/ssl包下 (可随便命名)

    后台加载这两个文件。

    app.js 关键代码
    var app = require('koa'),
        https = require('https');
    app=new app();
    
    var options = {
      key: fs.readFileSync('./ssl/key.key'),  //ssl文件路径
      cert: fs.readFileSync('./ssl/crt.crt')  //ssl文件路径
    };
    
    // start the server
    https.createServer(options, app.callback()).listen(443);
    

    这样,后台就跑在htpps下了。

    在服务器上运行后台:

    然后你用自己的电脑打开该域名,你会发现根本连接不上。

    那么,这是什么鬼?

    原来 阿里的windows 服务器 防火墙做了限制,且服务器安全组也做了限制。

    打开防火墙=>高级设置=>入站规则=>新建规则。

    选择端口 =>填写端口

    本次需要填写三个端口号, 后台的80(http) 443(https) 3306(mysql)

    1. 在阿里云控制器那里配置安全组,如下图所示。

    填写 那三个端口号, 授权对象那里 填写 0.0.0.0/0

    做完这一步,大功告成了,你的域名可以被访问了。


    3. 数据库设计

    数据表有两张,分为动态表与运动数据表,如下图所示。

    4. 前端设计与开发

    4.1 首页

    首页主要由动态广场与底部的tabbar组成,动态广场显示状态正常(state==1)的动态,私人动态与禁止动态不能被他人所看见,在数据表设计中,uid其实就是openid。 用户上传了图片后,数据库中保存的是其访问地址,多个地址之间用逗号分隔,形成字符串, 前端拿到图片地址后,将其转成数组。

    关键代码如下:

     onShow:async function(){
        var that = this;
        let pageno = 1;
        let pagesize = this.data.pagesize;
        this.setData({ pageno:1});
        noticesource.findalllnotice(pageno, pagesize).then(function (res) {
          console.log(res);
          for (let i in res) {
            let image = res[i].photo.split(',');
            res[i].imageurl = image;
          }
          that.setData({ allnotice:  res });
        })
      },
    

    动态页面使用分页加载,每次下拉加载10条,内容展示区使用 scroll-view组件,设置bindscrolltolower=“upper”,下拉时触发upper方法。

    关键代码如下:

     upper: function () {
        var allnotice = this.data.allnotice;
        var pageno = ++this.data.pageno;
        var pagesize = this.data.pagesize;
        console.log(pageno);
        var that = this;
        this.setData({ pageno: this.data.pageno++ });
        noticesource.findalllnotice(pageno, pagesize).then(function (res) {
          console.log(res);
          for (let i in res) {
            let image = res[i].photo.split(',');
            res[i].imageurl = image;
          }
            if (res.length > 0) {
              allnotice=allnotice.concat(res);
            }
          console.log(allnotice);
          that.setData({ allnotice: Array.from(new Set(allnotice)) });
          })
        }
    

    4.2 动态发布页

    动态发布页主要有文本输入框,图片上传区、发布按钮组成。

    该页面为重要页面,在发布前,我们需要获取发布者的头像、昵称、openid,如果这三个必要条件缺一的话,我们就会阻止用户发布动态并且提示用户授权登录。

    获取openid关键代码如下:

    app.js 
    
    wx.login({
          success(res) {
            console.log('code: ' + res.code);
            if (res.code) {
              loginsource.login(res.code).then(function (data) {
                console.log(data);
                wx.setStorage({
                  key: 'openid',
                  data: data.openid,
                })
                wx.setStorage({
                  key: 'session_key',
                  data: data.session_key,
                })
    
              })
            } else {
              console.log('登录失败!' + res.errMsg)
            }
          }
        });
    

    用户在进入小程序时,我就会让一次后台请求,根据wx.login返回的code解密生成 openid与 session_key,我将其存放在storage中,**其实,我不推荐将session_key放在storage中,按正常开发方式,应该是后台临时存储session_key,并返回一个sessionid给用户,不应该把session_key返回给用户。**但是,我在用koa-session时,一直没有成功,不知道什么鬼!没办法,我只好把session_key返回给用户了。

    上传图片关键代码:

      didPressChooesImage: function () {
        if (this.data.imageurl.length>=4){
          wx.showToast({
            title:"不能超过4张",
    
          })
          return 
        }
        var that = this;
        didPressChooesImage(that); //没有写错,只是同名,见2.1节
      },
          //删除指定图片
    deleteImage: function (e) {
        let index = e.currentTarget.dataset.index;
        var that = this;
        var imagetemp = that.data.imageurl;
        imagetemp.splice(index, 1);
        that.setData({ imageurl: imagetemp });
      },
    
    

    4.3 我的页面

    我的页面包含了用户的数据统计信息与小程序的推广信息,目前有待完善,从我的页面进去的动态页面只包含用户自己发布的。

    在未登陆时,页面显示默认头像,点击头像,授权个人信息,显示微信头像与昵称。
    

    4.4 群间运动PK

    接下来,重头戏来了,运动数据pk为该小程序的核心功能,

    功能点:

    1)获取用户此时的运动步数并展示出来

    2)分享自己的运动步数到微信群 并在页面上形成 pk排名区

    3)其他用户通过分享进入小程序,系统获取其群id与运动步数 与同一微信群的用户进行pk

    4)每次分享或点击分享链接,系统将自动更新该用户的运动步数

    5) pk排名区只展示当日的排名情况,晚上12点后自动清空pk区

    技术点:

    1)获取用户运动步数

    2)获取群id

    1. 各群之间间运动数据隔离

    效果如下图所示

    授权后显示步数。

    点击选择一个聊天后,将发布分享到微信群里,分享成功后,前端获取到ShareTicket,群内其他人通过该链接进来,前端也会获取到ShareTicket,调用 wx.getShareInfo()将加密数据发送给后端解密,可获得 openGid ,将用户的步数与openGid等信息存储起来,形成了groupsport表。

    保存当日已分享的群id,获取ShareTicket 关键代码:

      
      onLoad: function (opt) {
          //在storage中创建用户的当日分享情况 也就是分享到了哪些群,将这些群id存在一个与日期挂钩的对象中,到了第二天,清空群id.  
        var that =this;
        var nowDate = new Date();
        var year = nowDate.getFullYear();
        var month = nowDate.getMonth() + 1 < 10 ? "0" + (nowDate.getMonth() + 1)
          : nowDate.getMonth() + 1;
        var day = nowDate.getDate() < 10 ? "0" + nowDate.getDate() : nowDate
          .getDate();
        var dateStr = year + "-" + month + "-" + day;
       
          var value = wx.getStorageSync('openGidlist')
          if (value!=undefined||value!=null) {
           console.log(value);
            if (value.date != dateStr || value.Gidlist == undefined || value.Gidlist==null){
              wx.setStorageSync('openGidlist', { date: dateStr, Gidlist: [] });
            }
          }
          //设置开启ShareTicket
      wx.showShareMenu({
          withShareTicket: true
        })
     }
    

    获取个人运动数据

    onShow: function () {
    wx.getWeRunData({
          success: function (res) {
            console.log(res);
            let session_key = wx.getStorageSync("session_key");
            console.log(session_key);
            sportsource.getsportdata(res, session_key).then(function (data) {
              console.log(data);
              that.setData({ todaysportcount: data.data.stepInfoList[30].step });
            })
           
          }
        })
      }
    

    分享时获取群id

     onShareAppMessage: function () {
          var nowDate = new Date();
          var year = nowDate.getFullYear();
          var month = nowDate.getMonth() + 1 < 10 ? "0" + (nowDate.getMonth() + 1)
            : nowDate.getMonth() + 1;
          var day = nowDate.getDate() < 10 ? "0" + nowDate.getDate() : nowDate
            .getDate();
          var dateStr = year + "-" + month + "-" + day;
          var that=this;
          return {
            title: '我已经运动了' + this.data.todaysportcount+'步,你敢来PK吗?',
            path: 'pages/sportpk/sportpk',
            success: function (res) {
              var shareTickets = res.shareTickets;
              if (shareTickets.length == 0) {
                return false;
              }
              console.log('shareTickets'+shareTickets[0]);
    
              wx.getShareInfo({
                shareTicket: shareTickets[0],
                success: function (res) {
                  var encryptedData = res.encryptedData;
                  var iv = res.iv;
                  console.log(res);
                  let session_key = wx.getStorageSync("session_key");
                  sportsource.getsportdata(res, session_key).then(function (data) {
                    console.log(data);
                    let openGid = data.data.openGId;
                    that.setData({ openGid: openGid});
                    let openid = that.data.openid;
                    let todaysportcount = that.data.todaysportcount;
                    let avatarUrl = that.data.userInfo.avatarUrl;
                    let nickname = that.data.userInfo.nickName;
                    if (todaysportcount == undefined || todaysportcount==null){
                      wx.showToast({
                        title: '请重新打开运动权限',
                        duration: 2000
                      })
                      return
                    }
                    if (avatarUrl == undefined || avatarUrl == null || nickname == undefined || nickname==null) {
                      wx.showToast({
                        title: '请先点击头像登录',
                        duration: 2000
                      })
                      return
                    }
                    // let openGidlist = that.data.openGidlist;
                    let openGidlist = wx.getStorageSync('openGidlist');
                    openGidlist.Gidlist.push(openGid);
                    openGidlist.Gidlist = [...new Set(openGidlist.Gidlist)]
                    console.log("---------------------------------------");
                    console.log('openGidlist.Gidlist' + openGidlist.Gidlist);
                     console.log("---------------------------------------");
                    wx.setStorageSync('openGidlist', { date: dateStr, Gidlist: openGidlist.Gidlist});
                    sportsource.creategroupsport({
                       openGid,
                       openid,
                       todaysportcount,
                       avatarUrl,
                       nickname
                       }).then(function(){
                         that.setData({ sharegroupdata: [] });
                         that.getfirstgroupsport();
                       })
    
                  })
    
                }
              })
            },
            fail: function (res) {
              // 转发失败
            }
          }
        }
    

    刷新群内pk

     getfirstgroupsport: async function () {
        var that = this;
        let openGidlist = wx.getStorageSync('openGidlist');
        if (openGidlist.Gidlist != undefined || openGidlist.Gidlist != null) {
          if (openGidlist.Gidlist.length > 0) {
            for (let i in openGidlist.Gidlist) {
              let data = await sportsource.getgroupsport(openGidlist.Gidlist[i]);
              console.log(data);
              let sharegroupdata = that.data.sharegroupdata;
              sharegroupdata.push(data.data.rows);
              that.setData({ sharegroupdata: sharegroupdata })
            }
    
          }
        }
      },
    

    点击分享链接获取shareTicket,通过链接进入小程序的场景值是1044

    app.js
    onShow (opt){
      console.log("opt.scene" + opt.scene);
      if (opt.scene==1044){
        this.globalData.shareTicket = opt.shareTicket;
      }
    },
    

    5. 后端开发

    后端采用的是koa2,通过sequelize.js实现与mysql的连接。

    5.1 获取用户openid与session_key

    function getOpenId(code) {
      console.log(code);
      return new Promise((resolve, reject) => {
          const id = ''; // AppID(小程序ID)
          const secret = '';// AppSecret(小程序密钥)
          let url = `https://api.weixin.qq.com/sns/jscode2session?appid=${id}&secret=${secret}&js_code=${code}&grant_type=authorization_code`;
        axios.get(url).then(function (response) {
            console.log("response.data:"+response.data);
            resolve(response.data);
          })
          .catch(function (error) {
            console.log(error);
            reject(error);
          });
    
    })
    }
    async function login(ctx) {
      const {code} = ctx.query;
      const data = await getOpenId(code);
      ctx.body = data;
    
    }
    

    5.2 运动数据与群id数据获取

    这两个数据属于隐私数据,需要用算法解密,具体的流程,微信开发手册上有,我就不多说了,需要用到上一步的session_key来解密。

    //WXBizDataCryptconst:微信提供的解密方法
    WXBizDataCryptconst WXBizDataCrypt = require('./WXBizDataCrypt')
    
    var appId = '';
    async function getsportdata(ctx) {
      var encryptedData=ctx.query.encryptedData;
      var iv=ctx.query.iv;
      var session_key=ctx.query.session_key;
      console.log("session_key"+session_key);
      var pc = new WXBizDataCrypt(appId,session_key);
    
      var data = pc.decryptData(encryptedData,iv);
    
      console.log('解密后 data: ', data);
      ctx.body={
        success:true,
        data:data
      }
    }
    

    5.3 创建与读取运动数据

    当用户分享自己的运动数据到微信群内时或者微信群内其他用户通过该链接进入小程序时,后端将获创建或者更新该用户的群内运动数据。

    const creategroupsport = async function(data){ // 给某个用户创建一条群运动记录
      let nowDate = new Date();
      let year = nowDate.getFullYear();
      let month = nowDate.getMonth() + 1 < 10 ? "0" + (nowDate.getMonth() + 1)
        : nowDate.getMonth() + 1;
      let day = nowDate.getDate() < 10 ? "0" + nowDate.getDate() : nowDate
        .getDate();
      let dateStr = year + "-" + month + "-" + day;
      var countdata = await Todolist.findAndCount({
        where:{openGid:data.openGid,createdate:dateStr,openid: data.openid}
      })
      var count=0;
      if(countdata!=undefined||countdata!=null) {
          count = countdata.count;
      }
      if(count==0) {
        await Todolist.create({
          openGid: data.openGid,
          openid: data.openid,
          todaysportcount: data.todaysportcount,
          createdate: dateStr,
          avatarUrl: data.avatarUrl,
          nickname: data.nickname
        })
      }
      else {
        await Todolist.update({
          todaysportcount: data.todaysportcount,
          avatarUrl: data.avatarUrl,
          nickname: data.nickname,
          openid: data.openid,
        },{
          where:{
            id:countdata.rows[0].id
    
          }
        })
      }
      return true
    }
    
    //读取群内运动数据
    const getgroupsport = async function(openGid){
      console.log('openGid'+openGid);
      let nowDate = new Date();
      let year = nowDate.getFullYear();
      let month = nowDate.getMonth() + 1 < 10 ? "0" + (nowDate.getMonth() + 1)
        : nowDate.getMonth() + 1;
      let day = nowDate.getDate() < 10 ? "0" + nowDate.getDate() : nowDate
        .getDate();
      let dateStr = year + "-" + month + "-" + day;
      const data= await Todolist.findAndCount({
        where:{openGid:openGid,createdate:dateStr},
        order: [
          ['todaysportcount', 'DESC']
        ]
      })
      console.log(data);
      return data;
    }
    

    5.4 动态的获取

    对用户发布的动态,后台目前主要有 增,改,查三类方法,我说一下分页查询。

    const findallnotice = async function(ctx){ // 查询所有
    	let pageno=ctx.pageno;
    	let pagesize=ctx.pagesize;
    	console.log(pageno,pagesize);
     const data= await Todolist.findAndCount({
                where: {state:1},
                offset:(pageno - 1) * pagesize,//开始的数据索引,比如当page=2 时offset=10 ,而pagesize我们定义为10,则现在为索引为10,也就是从第11条开始返回数据条目
                limit:pagesize*1//每页限制返回的数据条数
           })
     console.log(data);
      return data;
    }
    
    const findmynotice = async function(ctx){ // 查询自己的
      let pageno=ctx.pageno;
      let pagesize=ctx.pagesize;
      let uid=ctx.openid;
      console.log(pageno,pagesize,uid);
      const data= await Todolist.findAndCount({
        where:{uid:uid},
        offset:(pageno - 1) * pagesize,//开始的数据索引,比如当page=2 时offset=10 ,而pagesize我们定义为10,则现在为索引为10,也就是从第11条开始返回数据条目
        limit:pagesize*1//每页限制返回的数据条数
      })
      console.log(data);
      return data;
    }
    

    6. 总结

    我洋洋洒洒写了几千字,看上去举重若轻,但是在实际开发中经常会碰见各种各样的问题,该项目从原型设计与开发到部署都是我独自完成的,中间也踩了一些坑,这个项目最终没能上线,是因为,个人主体账号不能发布关于GUC的小程序。

    本文首发于我的个人博客: https://www.catac.cn,转载时请注明来源,

    该项目源码地址:https://github.com/oumingliang/ydonline.git

    也欢迎各位与我交流,个人QQ:2541511219

    展开全文
  • 第一,非华为手机用户下载一个华为运动健康app(华为用户不用下载,手机自带)第二步,打开手机设置,在日期和时间里把自动设置按钮关闭,如图打开时区那一栏,点击搜索“纽埃”,如果没有搜到则搜索纽约,总之美洲...
  • nyoj58 最少步数(DFS)

    千次阅读 2015-07-31 08:45:07
    最少步数 时间限制:3000 ms | 内存限制:65535 KB 难度:4 描述 这一个迷宫,0~8行和0~8列:  1,1,1,1,1,1,1,1,1  1,0,0,1,0,0,1,0,1  1,0,0,1,1,0,0,0,1  1,0,1,0,1,1,0,1,1
  • python+appium爬取微信运动数据,并分析好友的日常步数情况 声明:仅供技术交流,请勿用于非法用途,如其它非法用途造成损失,和本博客无关 目录 python+appium爬取微信运动数据,并分析好友的日常步数情况 ...
  • 1330:【例8.3】最少步数

    千次阅读 2018-07-23 11:32:49
    他的同桌平时喜欢下围棋,知道这件事觉得很有趣,就想试一试,在一个(100×100)的围棋盘上任选两A、B,A放上黑子,B放上白子,代表两匹马。棋子可以按“日”字走,也可以按“田”字走,俩人一个走黑马,一...
  • 例子8.3 最少步数 在各种棋中,棋子的走法总是一定的,如中国象棋马走日。 一位小学生就想如果马能两种走法将增加其趣味性,因此,他...谁用最少的步数走到左上角坐标(1,1)的时,谁就获胜。 现在他请你...
  • 象棋最少步数问题

    千次阅读 2020-02-17 08:31:32
    他的同桌平时喜欢下围棋,直到这件事觉得很有趣,就想试一试,在一个(100x100)的围棋盘上任选两A,B,A放上黑子,B放上白子,代表两匹马。棋子可以按“日”走,也可以按“田”字走。两人一个人走黑马,一...
  • 我们大家不论是生活中还是在工作中都可能离不开微信,我们通过微信来交流、传播信息,微信中是一个挺高级的软件,许多的小功能,微信还可以记录步数对好友进行一个步数排行,那么如何开启微信记录每日步数呢?...
  • 这次我们来特别的,我希望你的程序能够用最多的步数达到要求,而且在此过程中不重复出现任何一种状态。 请联想曾经学过的汉诺塔知识。 Input 输入包含多组数据测试,每组数据只有圆盘数n( 1 <= n <= 12) ...
  • 获取微信步数

    千次阅读 2019-08-04 09:47:26
    //获取微信步数 getWalkCounts:function(){ var that = this; wx.login({ success(res) { wx.getWeRunData({ success(res1) { console.log(res1) if (res1.er...
  • 最近做一个健康的项目,需要获取运动步数,睡眠时间,于是研究了一下HealthKit,下面分享一下,下来几张图: Demo下载地址: https://github.com/TechAlleyBoy/HealthKitDemo 一:准备工作 1:在开发者账号...
  • 传送门:最少步数 这次,我决定用“最笨的方法”来做这道题(我也只会这样): 1330:【例8.3】最少步数 时间限制: 1000 ms 内存限制: 65536 KB 提交数: 2212 通过数: 1173  【题目描述】 在各种棋中,棋子的...
  • 最少步数(迷宫问题类型)

    千次阅读 2016-07-21 22:10:17
    最少步数 时间限制:3000 ms | 内存限制:65535 KB 难度:4 描述 这一个迷宫,0~8行和0~8列:  1,1,1,1,1,1,1,1,1  1,0,0,1,0,0,1,0,1  1,0,0,1,1,0,0,0,1  1,0,1,0,1,1,0,1,1  1,0,0,0,0,1,0,0,1...
  • 一个环,n个(编号 0 ~ n-1 ), 问从0出发,经过k回到原点(0多少种方法 ? 、解题思路 & 代码 再回到 0 可以从右面回来,也可以从左面回来,即先到达旁边的一个,看看多少回来的方法即可...
  • 1、安装下载【卓易健康】app,可...5、建议每次递交步数间隔不要超过9000,如果第一次是8999,第次就17998以此类推,微信上限是98800. 6、凌晨12点到2关闭修改,选择其他时间修改。如果不会或者理解差请多看几遍。
  • ACM--BFS--nyoj 58--最少步数

    千次阅读 2016-05-02 19:40:24
    最少步数 时间限制:3000 ms | 内存限制:65535 KB 难度:4 描述 这一个迷宫,0~8行和0~8列:  1,1,1,1,1,1,1,1,1  1,0,0,1,0,0,1,0,1  1,0,0,1,1,0,0,0,1  1,0,1,0,1,1,0,1,1  1,0,0,0,0,1,0,0,...
  • 他的同桌平时喜欢下围棋,知道这件事觉得很有趣,就想试一试,在一个(100×100)的围棋盘上任选两A、B,A放上黑子,B放上白子,代表两匹马。棋子可以按“日”字走,也可以按“田”字走,俩人一个走黑马,一...
  • 进制转化为二进制的两种方法

    万次阅读 多人点赞 2020-12-29 21:14:52
    如果我们要把进制的150转化为二进制,可以使用下面两种方法: 第一种方法:表格法 这种方法的核心思想就是用进制的各位来“拼凑”出我们的进制。 我们先把进制各位的位权列在表格里面。(我们如何...
  • NYOJ 58 最少步数(广搜入门经典)

    万次阅读 2016-07-15 20:01:09
    最少步数 时间限制:3000 ms | 内存限制:65535 KB 难度:4 描述 这一个迷宫,0~8行和0~8列:  1,1,1,1,1,1,1,1,1  1,0,0,1,0,0,1,0,1  1,0,0,1,1,0,0,0,1  1,0,1,0,1,1,0,1,1  ...
  • NYOJ 58 最少步数

    千次阅读 2012-07-08 20:17:38
    最少步数 时间限制:3000 ms | 内存限制:65535 KB 难度:4 描述 这一个迷宫,0~8行和0~8列:  1,1,1,1,1,1,1,1,1  1,0,0,1,0,0,1,0,1  1,0,0,1,1,0,0,0,1  1,0,1,0,1,1,0,1,1  ...
  • 以隐含层2个神经元例,程序每运行一次,进行20次模型建立和测试的结果,并写入txt文件中。将txt文件数据导入到excel表格中,对迭代次数和模型误差求均值。手动将程序运行5次,将每次得到的均值再求均值,并画图,...
  • 快手现在为什么不能滑动播放了

    千次阅读 2020-12-24 07:44:41
    快手现在为什么不能滑动播放了为了更方便地看快手上的视频,很多人都用了上滑切换作品的功能。然而有些用户的快手并没有上滑切换的功能,要怎么调用呢?一起来看看吧。方法如下:一、首先,更新快手APP至最新版本:...
  • 跟微信运动能同步的计步器是什么当然是动动计步器了,下面是将动动计步器步数导入到微信中的方法:1、首先要下载动动计步器,下载好了以后安装好,安装好了以后就可以正式开启计步了。2、安装好了动动计步器以后,...
  • 网上的24算法基本上都专注于4张牌凑24的算法甚至枚举了所有括号的组合,让人看得头晕眼花。这些算法若是推广到n个凑n,基本都歇菜了。 丧心病狂如这位:...
  • 请问走n回到0几种不同的走法

    千次阅读 2019-02-21 13:51:06
    请问走n回到0几种不同的走法(譬如n2, 0-1-0, 0-9-0共有两种走法)? public class Circle { /** * 单数不可能回到原点, 因为圆圈点为10 , 10是2 的倍数。 * 转换成求排列组合问题, 比如N == 6, 则必然...
  • Android计器算法实现(2)

    千次阅读 热门讨论 2018-11-19 21:59:33
    Android计器算法实现(2)前言算法实现的意义现实原因优缺点算法原理运动状态判断计步原理步长计算原理Java实现 前言 在之前我也写过两篇关于计器的博客,一篇介绍原理,一篇给出封装好的类型。这里是新算法...
  • 小米手环 / 运动手环 记功能原理

    万次阅读 多人点赞 2015-03-01 21:36:06
    很多朋友是第一次接触像小米手环...1、 手机上的运动步数是怎么来的? A:简单来说:小米手环能够精准计步由硬件和软件算法两方面组成,缺一不可。 硬件 是指小米手环里内置的那枚强悍的三轴加速度传感器ADXL362

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 268,951
精华内容 107,580
关键字:

为什么12点后有步数

友情链接: JNI.rar