16_fps所学(hw_sx_i2)

#1 总体实现思路:

            (1.1)测试目的是:要测试手机帧率(每秒钟拍多少张照片!)(如果用apk调用相机进行预览

                                              ,则每秒钟拍多少张照片是需要手动用代码设置的,问题是:假设你设置

                                               成30,但是实际上每秒钟不一定是拍到30,很有可能拍到27或33等!

                                               而且,不同的手机之间差异很大!)      


            (1.2)测试思路:当打开预览之后,每拍到一张新图片都会执行onImageAvailable()方法一次,

                                           所以,在该方法中起一个子线程ImageProcessor,在这个子线程中做的操作

                                           是让perSecondImageCount++,所以,只要我们开一个计时器,让它每隔1秒钟

                                           执行一次帧率计时器子线程,执行的内容就是拿到此刻的perSecondImageCount

                                           的值展示……,并且立即让perSecondeImageCount置为零,让它统计下一秒的! 

                                           这样不就实现了:每隔1秒钟我就可以统计这1秒钟内实际拍到了多少张图片!         


#2 收获的知识:

            (2.1)动态申请各种权限:

                                第一,时机:一般是在app第一次安装时提醒用户把所有该app需要的权限都申请完,再打开

                                                      app的第一个页面!并不是在用到某个权限时才去申请!(可以这样:第一次

                                                      安装app时把所有的权限都申请完,强制用户每项都选允许;以后每次要

                                                      用到某个权限时再检查一次即可!)


                                第二,用法:安卓中对于android6.0以及6.0以后的才需要动态申请权限,其他的不需要动态

                                                      申请,意思是可以直接用,默认可以调用所有权限!


                                                      android版本和api level对应关系(比如android4.4------api 19):

                                                      http://blog.csdn.net/only2xlr/article/details/49510415


                                                      判断要不要动态申请:

                                                      if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M)  表示6.0及以上!


                                                      判断是否具有了某个权限(表示如果不具有存储权限,则……):

if (ContextCompat.checkSelfPermission(MainActivity.this, MEMORY_PERMISSION) != PackageManager.PERMISSION_GRANTED)

                                       

                                                     弹出动态申请某个权限框的方法(执行完这个方法后会有如下现象)

                                                     (用户看到一个弹出框,系统会执行该Activity的onPause()方法、

                                                      系统会执行你重写好的onRequestPermissionsResult()方法):

ActivityCompat.requestPermissions(MainActivity.this, new String[]{MEMORY_PERMISSION}, MEMORY_REQUEST_CODE);

                  

                                                    接下来看onRequestPermissionsResult()方法怎么响应用户的选择:

                                                    首先根据request_code判断你是哪个框的,然后(用户选了禁止就继续弹框):

if (grantResults.length > 0) {
    
    if (grantResults[0] < 0) {
        
        //表示用户刚刚选了禁止;
        ActivityCompat.requestPermissions(MainActivity.this, new String[]{MEMORY_PERMISSION}, MEMORY_REQUEST_CODE);
    } else {

        //表示用户刚刚选了允许;
    }
}


            (2.2)handler的使用场景之一以及具体使用方法:

                                第一,我这里遇到的场景:在onCreate()本来有两个方法: initMyLog();和firstEnterApp();

                                                                            前者方法的目的是想在手机上创建一个文件,以便在以后的执行

                                                                            中把过程log写到该文件中去!但是你想要创建文件就必须先

                                                                            动态申请存储权限,所以,在initMyLog()执行之前,必须先

                                                                            申请到了存储权限才行,否则会报错!


                                                                             很容易想到这样解决:

                                                                              onCreate() {

                                                                                      ActivityCompat.requestPermissions(存储权限);

                                                                                      initMyLog();

                                                                                      firstEnterApp();

                                                                              }

                                              

                                                                              但是问题来了,申请权限的时候不是会触发onRequestResult()

                                                                              方法吗?只有用户选了允许之后,执行完onRequestResult()方

                                                                              法之后,才算是真正申请到了存储权限!!  但是,系统哪里会

                                                                              等你那么久,系统在执行完ActivityCompat.reque……()方法之后,

                                                                              立马就会执行initMyLog(),而此时是没有存储权限的,所以,

                                                                              会崩了!因为在initMyLog()中有在手机上写文件的代码,而实际

                                                                              上还没有存储权限呢!!


                               第二,上面的场景总结:试想一下,如果在执行完onRequestResult()方法后,并且用户选了

                                                                              允许存储权限之后再执行initMyLog()和firstEnterApp()方法,则

                                                                              肯定没有问题吧!  但是如何做到呢?即怎么做到:让一个不确定

                                                                              也不能控制时间顺序的回调方法执行结束之后再执行我们的某些

                                                                              方法呢??


                               第三,场景再总结:  怎么保证y方法一定在x方法执行完了之后才会被执行?

                                                                 而且x方法什么时候执行我们无法控制!!


                               第四,解决上面的场景:Handler登场了,对它来说,轻而易举!!!(这里先只讲一下

                                                                       handler怎么处理上面那个场景,它怎么处理的即原理后面弄懂了

                                                                       再讲!!)


                               第五,handler如何解决上面的场景:

                                                           目前这样理解,handler可以发送消息、也可以接收消息,假设对于同一个

                                                           handler,它发出了一条Message,不久后它便可以收到这条消息,然后做

                                                           相应的处理!!   


                                                           我们可以这样:在MainActivity中先声明一个全局变量handler, 注意在new

                                                           handler对象时可以重写handMessage()方法,即处理消息的方法;然后

                                                           我们的思路是:    在onRequstResult()方法中用户选择了允许存储权限

                                                           之后,我们用这个handler发一条消息!  然后之前在new这个handler的

                                                           时候重写handleMessage()方法,并且处理消息的内容是在这里面

                                                           调用initMyLog()方法和firstEnterApp()方法!!  这样不就实现了??


                                                           具体如下:

                                                           class MainActivity {

                                                                        private Handler handler = new Handler() {

                                                                                 //重写handleMessage(Message msg) {

                                                                                           if (msg.what == 1) {

                                                                                                   initMyLog();

                                                                                                   firstEnterApp();

                                                                                           }

                                                                                  }

                                                                        };


                                                                        定义initMyLog()方法{}


                                                                        定义firstEnterApp()方法{}


                                                                        onCreate() {

                                                                                 //申请存储权限;

                                                                         }


                                                                         onRequestResult() {

                                                                                  //确保用户选了允许存储权限之后发送一条handler消息:

                                                                                  Message msg = new Message();

                                                                                  msg.what = 1;

                                                                                  msg.setTarget(handler);  //设置让谁发送!

                                                                                  msg.sendToTarget();  //发送的意思

                                                                         }

                                                           }     


                               第六,handler解决上面场景的原理:

                                                                            

            (2.3)打Log的重要性以及技巧:(见2.9)


            (2.4)runOnUiThread()的适用场景:

                                 个人理解:这个一般用在这样的场景下:就是你在一个子线程中(非主线程的子线程),

                                 如果你想做一个更改主线程UI的操作,则如果直接操作则会报错! 但如果加上这个就ok!


            (2.5)Camera2、以及TextureView、ImageReader控件的使用:

                                  Camera2怎么打开预览的过程笔记上写得很详细了,这里就不介绍了,这里假设你已经

                                  成功打开了预览了,而预览的过程其实就是在连续地拍照,后两者的作用是:  每拍到

                                  一张图片时,会把这张图片立马存到TextureView的Surface上,然后ImageReader可以

                                  立马从TextureView上获取到那张图片,获取到了图片之后你可以count++、处理图片等

                                  操作(比如yuv->rgb等)!!    所以,在打开预览时会有代码把TextureView和

                                  ImageReader这两个家伙绑定!   而且事先会给ImageReader加一个监听者,

                                  一旦你帮他们两个家伙绑定了,开始预览后,每来一张图片,则imagReader的监听者就

                                  可以监听到TextureView的Surface改变了,即拍到了新的图片,那么,imagReader的监听

                                  者就会执行它的方法onImageAvailable(),这个方法是你事先写好的,里面就可以写

                                  当来了新图片时我们做的操作了!!


                                  所以,大约有这样的逻辑: 

                                  a,  TextureView和ImageReader这两个家伙绑定;

                                  b,  imageReader.set(监听者);

                                  c,  而这个监听者对象主要监视和reader绑定的TextureView有没有新的图片;

                                  d,  这个监听者有个方法:  onImageAvailable(ImageReader reader) ,在这个

                                       方法中,我们一般是这样:  Image image = reader.acquireLatestImage();

                                                                                    处理图片;

                                                                                    image.close();

                                  e,而预览的过程中每拍到一张新的图片,TextureView的Surface都会更新,

                                     则会被imageReader的监听者监视到,所以会执行它的onImageAvailable()方法!!!


                                  注意几个问题:  在哪个地方设置预览时到底每秒钟拍多少张图片呢?

                                                           实际上只有一个地方,就是在建立预览请求时设置的!不过我们在开启

                                                           预览之前一般还会设置另一个参数,就是iamgeReader的一个参数,在

                                                           开启预览之前,我们在new它的时候,一般会把maxImage也设置为刚刚那个

                                                           数!即: 假设setfps=30,则imageReader变量的maxImages参数也设置为

                                                           30!表示: 让预览时每秒钟拍30张照片,而且让imageReader最多每秒钟

                                                           会去TextureView上检查30次! 


                                                           目前发现有一个这样的现象:即用reader调用了acquireLatestImage()方法后,

                                                           不是得到一张图片吗,你用完了这张图片之后一定要image.close()之后,才可以

                                                           再一次用reader调用acquireLatestImage()方法,否则会报错!!                                                     



            (2.6)多线程的引入原因、引入需要注意的事项、通讯方式:

                                    在2.5的基础上进一步分析:我们知道来了一张图片之后我们处理图片的代码

                                    肯定在onImageAvailable()中写吧!一般是让perSecondImageCout++!问题来了,

                                    假设这个处理图片的代码比较复杂,比较耗时间,那么,假设你之前设置成预览时每秒钟

                                    拍30张照片,那么,理论上来说每秒钟内会执行30次onImageAvailable()方法,但是,

                                    由于onImageAvailable()方法中的逻辑代码比较耗时,导致可能最终只执行了22次该方法!

                                    因此,这样可能会影响我们的结果!!


                                    事实发现,如果每次执行onImageAvailable()方法时,如果每次都开启一个子线程来处理,

                                    则可以“省时”,即可以最终执行到30次!!


                                     但是问题又来了,有些时候可能会出错,因为每个子线程的代码都是这样:

                                     {

                                                Image image = reader.acquireLatestImage();

                                                处理图片;

                                                image.close();

                                     }


                                     有可能出现这样的情况: 子线程1刚运行到处理图片,还没有运行完它的image.close()方法,

                                     这个时候cpu被子线程2抢到了,子线程2就运行了reader.acquierLatestImage();语句,则

                                     崩了!!(即多线程虽然提升了效率,但是却带来了安全问题,因为多线程情况下运行

                                     时机的不确定性,所以,,,,)


                                     这个时候,我们就得想办法实现: 一定要让第一个子线程的方法执行完了之后才可以

                                     开始执行第二个线程! 我尝试过这两种方案,第一是在每个子线程开启之后加这个语句:

                                     thread.start();   thread.join();  则可以保证,但是效率又降低了,即假设你设置成fps=30,

                                     则现在onImageAvailable()方法只能执行大约22次左右!!      另外一种方案是用线程池

                                     中的单个线程来处理,即不用每次都new一个子线程,而是从线程池中取同一个线程来

                                     处理,这样肯定可以保证这个线程第一次运行完、再运行第二次、再运行第三次、……

                                     但是效率仍然是22!



                                     目前采用的方法是用线程池:使用方法如下:

                                     先定义一个线程池对象(Single的意思是这个从里面取线程时每次取的都是同一个线程)

                                     private ExecutorService mSingleThreadPool = Executors.newSingleThreadExecutor();


                                      然后你定义一个类实现Runnable(),并重写run()方法,在里面写好处理的逻辑代码;


                                      然后在onImageAvailable()方法中只需要:

                                      mSingleThreadPool.execute(new MyRunnable());  即可!


                                       **目前各线程之间的通讯方式是  共享同进程下的 全局变量!!

 


            (2.7)adb介绍以及常用的一些命令:


                                   adb介绍:


                                   adb常用命令:

                                             adb  devices     查看和该电脑连接上的手机有哪些!

                                             adb  kill-server 

                                             adb  start-server

                                             adb  install  将电脑上的某个apk文件拖过来, 然后回车!(装apk到手机上)

                                             adb  -s  设备号  install  -r  -d   (电脑上连有多个手机、replace、delete)


                                             adb   pull   /sdcard/fps   d:/java  (将手机根目录下的fps文件夹复制到电脑上)

                                             (注意:事先在电脑上d盘下建一个名字叫做java的文件夹!)

                                             (注意:这里的  /sdcard  目录是指  手机内置的内存根目录,不是我们装上去的卡!)


                                            adb   push   /d:/java   /sdcard/   (将电脑上d盘下的java文件夹复制到手机根目录!)



            (2.8)简单windows脚本介绍:

                                           首先设置一下后缀名可见:随便打开一个文件夹,组织,文件夹和搜索选项,查看,……

                                           然后随便建一个文本文档,里面的内容等下说,然后改成.bat格式,那么,在windows

                                           下双击这个文件就相当于在dos窗口中执行那里面的语句了!


                                           所以,这个文件的内容可以是:  adb  pull  /sdcard/fps  d:/java等内容!

                                           不过,貌似目前没有成功!


            (2.9)假设把你的apk拿给别人测试你的Log要达到的要求:

                                           要提前做好你的apk可能会崩掉的准备,那么,你就得提前做好Log了!

                                           首先如果你自己有写文件记录过程日志,最好在过程日志中如下:

try {
    processBw.write("[" + new SimpleDateFormat("yyyy_MM_dd_HH_mm_ss").format(new Date()) + "]----------" + "[" + android.os.Process.myPid() + "]----------" + "[" + android.os.Process.myTid() + "--------]----------" + "[ImageReader.OnImageAvailableListener类]----------" + "[onImageAvailable()方法]----------" + "[isStartTesting == 1]" + "\r\n");
    processBw.flush();
} catch (IOException e) {
    e.printStackTrace();
}

                                             即:时间+进程id+线程id+类名+方法名+描述

                                             这样,就算崩了可以知道你运行到了哪里之后崩掉了!


                                             另外,假设有些地方的执行,还没等到你创建你的日志文件,则你肯定要提前

                                             在那些地方用Log.i写好过程语句!这样,就算你的代码很早就崩了,起码测试人员

                                             可以帮你抓到Android Log信息,你可以根据这个查到在哪里崩的!


                                             上面只是保证了有过程Log,但是错误信息呢?在你的代码中如果有try catch的地方,

                                              一定要在catch的Exception那里,加上Log.i(TAG, e.toString());这样,测试人员帮你

                                              抓到的Android Log中就可以看到具体的错误信息了!



            (2.10)android中文件创建的方式参考:

String timeStamp = new SimpleDateFormat("yyyy_MM_dd_HH_mm_ss").format(new Date());
String phoneFactory = android.os.Build.BRAND;
String phoneModel = android.os.Build.MODEL;
String phoneRelease = android.os.Build.VERSION.RELEASE;
String processName = "/" + phoneFactory + "_" + phoneModel + "_" + phoneRelease + "_" + timeStamp + "_fps" + "_PROCESS" + ".txt";
String resultName = "/" + phoneFactory + "_" + phoneModel + "_" + phoneRelease + "_" + timeStamp + "_fps" + "_RESULT" + ".txt";

File sdcard = Environment.getExternalStorageDirectory();
File myFolder = new File(sdcard, "/fps");
myFolder.mkdirs();

if ((!sdcard.exists()) || (!myFolder.exists())) {
    return;
}

try {

    processTxt = new File(myFolder, processName);
    resultTxt = new File(myFolder, resultName);

    //可以追加!
    processFos = new FileOutputStream(processTxt, true);
    resultFos = new FileOutputStream(resultTxt, true);

    processBw = new BufferedWriter(new OutputStreamWriter(processFos));
    resultBw = new BufferedWriter(new OutputStreamWriter(resultFos));


} catch (FileNotFoundException e) {
    e.printStackTrace();
} catch (Exception e) {
    e.printStackTrace();
}



            (2.11)编程规范中自己经历的一些比较重要的:



#3 遇到的大问题:

            (3.1)问题1--

                        现象:将fps设置为1-200,在nova2手机上运行ok,但在honor6x、nubia、小米手机都直接崩,

                                   fps设置成2时,基本没有问题,但有些时候有些手机会崩(虽然概率很小,但一定会出现)!

                        原因:就是每执行到onImageAvailable()时,都开启一个新的子线程,然后由于第一个子线程的

                                   image.close()还没执行完,第二个子线程就执行了reader.acquireLatestImage()方法,所以,

                                   崩了!!

                        解决: 用线程池中的单个线程处理图片;


            (3.2)问题2--

                        现象:将fps设置成2,在nova2上运行正常,小米手机运行一轮就崩、honor6x手机也是运行一轮

                                   就崩!(运行一轮的意思是:运行完setfps=2后准备运行setfps=3时,立马崩!)

                        原因: 每次运行完一轮不是要关闭预览嘛,即会让reader.close();并且 reader=null; 接着又会进行

                                    下一轮的测试,即又要重新开启预览,所以,reader=new ……;  

                                    可以理解成每一轮都要这样:

                                    {

                                              iamgeReader = new ImageReader();

                                              测该轮;

                                              imageReader.close();   imageReader = null;

                                     }

                                     问题就在: 上一轮结束了,还没来得及关闭reader,而下一轮就开始了,即下一轮的

                                     reader已经new好了,而这个时候才执行上一轮的关闭reader,这就导致下一轮

                                     的reader已经为空了,而下一轮的reader在使用的时候就为空了!(这还是多线程抢cpu

                                     引起的问题其实!!)


                        解决:  两轮之间隔久一点!确保上一轮关掉了,再执行下一轮的!

                                     或者可以考虑两轮之间用不同的reader对象!!


            (3.3)问题3--

                        现象:其他手机其他场景都正常,nubia手机一打开立马崩!

                        原因:文件权限问题,见#2的2.2

                        解决:


            (3.4)问题4--遗留问题

                         现象:测试fps的demo在处理图片时,只要让perSecondImageCount++即可,所以,

                                    在这里用线程池的单线程处理,效率仍然可以达到设置成30实际也是30;

                                    但是将来测心率的demo中处理图片时就要加上yuv->rgb操作了,这样就比较

                                    耗时,所以,设置成30可能实际只有22,这样就少了好多!  那么,你就得考虑

                                    到底要效率但可能出现bug;还是要安全,宁愿少好多图片!

                         原因:

                         解决:


            (3.5)问题5--

                         现象:

                         原因:

                         解决:



#4 感悟:

              (4.1)出现bug时怎么办?:

                                  **切忌:遇到bug时就根据现象去猜测原因,凭经验去揣测错误原因!!!

                                                (这条是目前自己最大的最严重的一个问题,在学校写代码可能根据你的

                                                  经验可以才出来,但是企业中的代码你会遇到无限多的从来没经历过的

                                                  问题,,,  另外,猜测出的问题很浪费时间,而且根本不是解决问题

                                                  的方法!!!)(在#3中我故意强调各个手机不一样,故意告诉你

                                                  报的错跟现象完全不搭边啊,你怎么猜!!)(如果你根据现象去猜

                                                  则你就会想:不同的手机运行却不一样,这肯定是硬件的问题,跟我

                                                   的代码没有关系!!   实际上就是你的代码的问题!!

                                                   多线程之间共享变量时特别需要注意!!!所以不要凭猜!)


                                 **怎么办?:  第一,一定、一定要定位到具体崩在哪一行:方法很简单,在这行代码

                                                                   的前后、被调用地方等打上详细的log,比如进程id、线程id等也

                                                                   加上……

                                                        第二, 一定、一定要得到具体的错误原因,

                                                                   比如,你可以在这行崩的代码上加上try catch,然后在

                                                                   catch(Exception e)中把e打印出来!

                                                        第三,当得出具体的错误原因之后,可能你对它完全陌生,甚至根本没法

                                                                   分析,不要怕,接下来你可以两种方式同时进行,第一,在网上查

                                                                   该错误,英文的不用怕,多尝试看英文的,百分之七十看不懂错误

                                                                   原因,不怕,接下来,你在崩的这行所在的方法中打上具体的log信息,

                                                                   比如,该方法一进来时间进程id线程id、该方法出去的……、该行

                                                                   前后的语句的具体log、……   结合log情况再去看那个出错原因,

                                                                   很有可能可以知道为什么!!

                                                        第四,如果上面还没解决,则在这个基础上再去问别人,就很清晰了!


                                 **举例:

              (4.2)提高开发效率的方式:(及时交流、不要秀操作)


                                                         遇到问题不要自己闷在那里解决,及时反馈,说不定人家一眼就知道怎么

                                                         解决,或者提出很好的优化方案!所以,多交流、多对对!







  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值