Android5.0 SystemUI ANR 黑屏原因解解决方案

 更新时间:2016年9月20日 19:57  点击:1957
本文主要讲的内容是Android5.0L因SystemUI ANR导致的黑屏的问题现象、解决方案、初步分析、深入分析问题、及相关问题等,如果你在做Android开发的时候也遇到这种现象,可以参考一下本文。

本文我们来讲讲Android5.0L因SystemUI ANR导致的黑屏问题分析


一、问题现象

用户直观看到的现象是黑屏。出问题时StatusBar、NavigationBar和墙纸消失。大部分发生在FOTA重启之后,出现概率很低。

Platform:MSM8916

Android版本:5.0.2L

BuildType:user

系统软件版本:VA6V+L5V0

系统RAM:1GB

参考机行为:

1、5.0L的Nexus4和5.1L的Nexus5都没有重现此问题。

二、解决方案

通过初步分析、深入分析(具体分析过程、关键代码和log在下面会附上)我们清楚的知道了问题发生的原因:

1、开机初始化的过程中需要获取camera的相关参数,获取的过程中会以api级别打开camera(用户不可见的形式打开)然后快速关闭

2、在打开的过程中会开启一个Thermal deamon 线程进行thermal相关的处理,然后关闭时会等待这个thermal deamon线程退出

3、这个线程开启的时候会通过异步的方式执行一次thermal相关的处理,并等待结果返回, 执行的方式是多线程异步处理

在当前代码的执行状态下有一定概率(很小,只有开机或者重启时走这个流程)出现因为调度原因而先执行了closecamera的操作并先删除了异步处理结果的链表,然后等待thermal deamon线程退出,从而导致thermal deamon被唤醒时异步处理结果的链表已经被删除而出现死结。一旦死结产生,SystemUI就会ANR,然后依附于SystemUI的statusbar和navigationbar以及imagewallpaper都会被阻塞,一旦SystemUI进程被Kill,这些组件都会消失,产生黑屏现象。

针对以上问题的根本原因,我们给出以下解决方案:

1、修正代码的处理顺序

Closecamera 时先执行m_thermalAdapter.deinit等待thermal deamon线程将结果处理完成退出并返回,再free all pending api results,因为m_thermalAdapter.deinit会依赖pending api results,这同样是遵循初始化和反初始化的栈原则,即opencamera时最后初始化的是依赖别人最多的,但是不被别人依赖,因此 closecamera时需要先反初始化在opencamera时最后初始化的,按照栈的方式原则处理。

2、方案相关的具体代码和log





以上是发生死锁时锁对应的log以及相应代码和调用栈,通过红线圈住部分我们可以看到发生问题时的关键调用关系和状态,同时也给出了代码处理顺序有问题的地方。

3、最终方案的代码修改


三、问题初步分析

以Idol347出问题时候的一份典型trace和log为例,发现出问题时SystemUI的主线程block在了一个向CameraService发起的Binder调用中,从而导致SystemUI

的后续事件TimeOut引起ANR,主线程的具体trace如下:


然后继续追踪CameraService的服务端的trace,发现/system/bin/mediaserver中的处理CameraService的 Binder线程也被block了,然后追踪trace中各个线程的调用栈和互斥锁的使用,发现处理调用CameraService的 addlistener的Binderthread之所以被阻塞,是因为另外一个binder thread先占用了锁,然后在占用的过程中去注册thermal回调,但是注册的过程需要占用另外一个锁,但是这个锁被第三个thread在注销thermal回调的时候先占用,并且join另外一个thread,因此整个依赖环需要另外一个thread退出才能解,从当前的问题现象来看,这个thread不会太快退出,所以导致了ANR和黑屏问题。

通过初步分析我们发现的问题:

是否需要占用着锁的情况下去join另外一个thread,或者这种状态是否合理?


具体的调用栈和代码中锁的关系如下:











四、深入分析问题

经过初步我们定位到了第一个问题点,同时也产生了1个问题,接下来我们继续深入分析以期能到找到答案和问题的根本原因。

1、是否需要占用着锁的情况下去join另外一个thread,或者这种状态是否合理?

通过进一步分析和查看代码发现,Join的另外一个thread不能很快退出是因为它在执行callback时等待另外一个条件的满足,具体逻辑调用关系如下:





另外一个条件之所以不满足的原因:

开机初始化的过程中需要获取camera的相关参数,获取的过程中会以api级别打开camera(用户不可见的形式打开)然后快速关闭,在打开的过程中会开启一个Thermal deamon 线程进行thermal相关的处理,然后关闭时会等待这个thermal deamon线程退出,但是这个线程开启的时候会通过异步的方式执行一次thermal相关的处理,并等待结果返回,由于是多线程的异步处理,在当前代码的执行状态下就有一定概率(很小)出现因为调度原因而先执行了closecamera的操作并先删除了异步处理结果的链表,然后等待thermal deamon线程退出,从而导致thermal deamon被唤醒时异步处理结果的链表已经被删除而出现死结。

一旦死结产生,SystemUI就会ANR,然后依附于SystemUI的statusbar和navigationbar以及imagewallpaper都会被阻塞,一旦SystemUI进程被Kill,这些组件都会消失,产生黑屏现象。

五、其他相关问题

为什么大部分发生在FOTA升级之后?

由于这个问题是发生在启动或者重启时,而且只有这个过程才会有很小概率触发。而FOTA之后会自动重启,所以就有概率触发这个问题,由于用户平时使用中很少重启,所以概率不高。


本文我们先来看看android如何通过子线程来实现动画的实例,然后我们再详细讲讲Android 动画实现的原理,这样才能深入浅出。

android通过子线程来实现动画的实例


Android动画,一般是相对原始位置进行参照,本文我们来看看通过子线程修改物体位置实现动画的实例。


布局文件:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity" >

    <TextView
        android:id="@+id/show"
        android:layout_marginLeft="20dp"
        android:layout_marginTop="40dp"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@string/hello_world" />

    <Button
        android:id="@+id/button1"
        android:onClick="MyCLick"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentBottom="true"
        android:layout_alignParentRight="true"
        android:text="行动" />

    <Button
        android:id="@+id/button2"
        android:onClick="MyCLick"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentBottom="true"
        android:layout_marginRight="28dp"
        android:layout_toLeftOf="@+id/button1"
        android:text="获取位置" />

</RelativeLayout>

动画代码:

public class MainActivity extends Activity {

    TextView textView;
    MyRuns myRuns;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        textView = (TextView) findViewById(R.id.show);

        
        myRuns=new MyRuns(new MyHead(textView, 4, 8), true);//位移动画
    }

    static class MyHead extends Handler {// 坐标动画

        View view;// 操作元素
        float cx;
        float cy;

        public MyHead(View view, float cx, float cy) {
            super();
            this.view = view;
            this.cx = cx;
            this.cy = cy;
        }

        @Override
        public void handleMessage(Message msg) {
            // 更新ui
            view.setX(view.getX() + cx);
            view.setY(view.getY() + cy);
            super.handleMessage(msg);
        }

    }

    // 子线程更新位置
    class MyRuns implements Runnable {//更新UI界面

        MyHead head;
        boolean isFire = false;

        public MyRuns(MyHead head, boolean isFire) {
            super();
            this.head = head;
            this.isFire = isFire;
        }

        public boolean isFire() {
            return isFire;
        }

        public void setFire(boolean isFire) {
            this.isFire = isFire;
        }

        @Override
        public void run() {
            // TODO Auto-generated method stub
            try {
                while (true) {
                    if (!isFire) {
                        break;//停止动画
                    }
                    Thread.sleep(80);
                    Message message = new Message();
                    message.what = 3;
                    message.obj = "";
                    head.sendMessage(message);
                }
            } catch (Exception e) {
                // TODO: handle exception
            }
        }

    }
    
    //开始运动
    void StartThreed(MyRuns myRuns){
        myRuns.setFire(true);//开启
        new Thread(myRuns).start();
    }

    public void MyCLick(View view) {
        if (view.getId() == R.id.button1) {
            StartThreed(myRuns);
        } else if (view.getId() == R.id.button2) {
            myRuns.setFire(false);//结束子线程
            Toast.makeText(getApplicationContext(),
                    "坐标" + textView.getX() + "||" + textView.getY(),
                    Toast.LENGTH_SHORT).show();
        }

    }

}




Android 动画原理

Android 平台提供了一套完整的动画框架,使得开发者可以用它来开发各种动画效果。Android 动画框架详解由原理篇和实例篇两部分组成。本文是第一部分原理篇,主要分析 Tween 动画的实现原理, 最后简单介绍在 Android 中如何通过播放 Gif 文件来实现动画。第二部分实例篇将在原理篇的基础上,向您展示一个动画实例的实现。

 Android 平台提供了一套完整的动画框架,使得开发者可以用它来开发各种动画效果,本文将向读者阐述 Android 的动画框架是如何实现的。 任何一个框架都有其优势和局限性,只有明白了其实现原理,开发者才能知道哪些功能可以利用框架来实现,哪些功能须用其他途径实现。Android 平台提供了两类动画,一类是 Tween 动画,即通过对场景里的对象不断做图像变换 ( 平移、缩放、旋转 ) 产生动画效果;第二类是 Frame 动画,即顺序播放事先做好的图像,跟电影类似。本文是由两部分组成的有关 Android 动画框架详解的第一部分原理篇, 主要分析 Tween 动画的实现原理, 最后简单介绍在 Android 中如何通过播放 Gif 文件来实现动画。我们先看一下动画示例来一点感性认识。

Android 动画使用示例

使用动画示例程序的效果是点击按钮,TextView 旋转一周。读者也可以参看 Apidemos 中包 com.example.android.apis.animationview 下面的 Transition3d 和 com.example.android.apis.view 下面的 Animation1/Animation2/Animation3 示例代码。

清单 1. 代码直接使用动画

 package com.ray.animation;   
import android.app.Activity;   
import android.os.Bundle;   
import android.view.View;   
import android.view.View.OnClickListener;   
import android.view.animation.AccelerateDecelerateInterpolator;   
import android.view.animation.Animation;   
import android.view.animation.RotateAnimation;   
import android.widget.Button;   
 
public class TestAnimation extends Activity implements OnClickListener  
{   
    public void onCreate(Bundle savedInstanceState)  
    {   
        super.onCreate(savedInstanceState);   
        setContentView(R.layout.main);   
        Button btn =(Button)findViewById(R.id.Button);   
        btn.setOnClickListener(this);   
    }  
 
    public void onClick(View v)  
    {   
        Animation anim=null;   
        anim=new?RotateAnimation(0.0f,+360.0f);   
       anim.setInterpolator(new AccelerateDecelerateInterpolator());   
       anim.setDuration(3000);   
       findViewById(R.id.TextView01).startAnimation(anim);   
    }   
}

 使用 XML 文件方式,在打开 Eclipse 中新建的 Android 工程的 res 目录中新建 anim 文件夹,然后在 anim 目录中新建一个 myanim.xml( 注意文件名小写 ),内容如下 :


图 1. 使用 xml 文件方式

 <?xml version="1.0" encoding="utf-8"?>  
 <set xmlns:android="http://schemas.android.com/apk/res/android">  
 <rotate   
    android:interpolator="@android:anim/acclerate_decelerate_interpolator"  
    android:formDegress="0"  
    android:toDegress="+360"  
    android:duration="3000" />  
      
<!--rotate 旋转动画效果  
    属性:  
   interpolator 指定一个动画的插入器,用来控制动画的速度变化  
   fromDegress  动画起始时物件的角度  
   toDegress    动画结束时物件的旋转角度,正代表顺时针  
   duration     动画的持续时间,以毫秒为单位-->  
 
/set>

其中的 java 代码如下:

 package com.ray.animation;   
import android.app.Activity;   
import android.os.Bundle;   
import android.view.View;   
import android.view.View.OnClickListener;   
import android.view.animation.Animation;   
import android.view.animation.AnimationUtils;   
import android.widget.Button;   
import android.widget.TextView;   
public class TestAnimation extends Activity implements OnClickListener  
{   
   public void onCreate(Bundle savedInstanceState)  
   {   
       super.onCreate(savedInstanceState);   
       setContentView(R.layout.main);   
       Button btn =(Button)findViewById(R.id.Button01);   
       btn.setOnClickListener(this);   
   }   
 
   @Override   
   public void onClick(View v)  
   {   
      Animation anim=AnimationUtils.loadAnimation(this,R.anim.my_rotate_action);   
    findViewById(R.id.TextView01).startAnimation(anim);   
   }   
}


Android 动画框架原理

现有的 Android 动画框架是建立在 View 的级别上的,在 View 类中有一个接口 startAnimation 来使动画开始,startAnimation 函数会将一个 Animation 类别的参数传给 View,这个 Animation 是用来指定我们使用的是哪种动画,现有的动画有平移,缩放,旋转以及 alpha 变换等。如果需要更复杂的效果,我们还可以将这些动画组合起来,这些在下面会讨论到。

要了解 Android 动画是如何画出来的,我们首先要了解 Android 的 View 是如何组织在一起,以及他们是如何画自己的内容的。每一个窗口就是一棵 View 树,下面以我们写的 android_tabwidget_tutorial.doc 中的 tab 控件的窗口为例,通过 android 工具 hierarchyviewer 得到的窗口 View Tree 如下图 1 所示:

图 2. 界面 View 结构图


界面 View 结构图

图 3. 界面 View 结构和显示对应图


界面 View 结构和显示对应图图

其实这个图不是完整的,没有把 RootView 和 DecorView 画出来,RootView 只有一个孩子就是 DecorView,这里整个 View Tree 都是 DecorView 的子 View,它们是从 android1.5/frameworks/base/core/res/res/layout/screen_title.xml 这个 layout 文件 infalte 出来的,感兴趣的读者可以参看 frameworks\policies\base\phone\com\android\internal\policy\Imp\PhoneWindow.java 中 generateLayout 函数部分的代码。我们可以修改布局文件和代码来做一些比较 cool 的事情,如象 Windows 的缩小 / 关闭按钮等。标题窗口以下部分的 FrameLayou 就是为了让程序员通过 setContentView 来设置用户需要的窗口内容。因为整个 View 的布局就是一棵树,所以绘制的时候也是按照树形结构遍历来让每个 View 进行绘制。ViewRoot.java 中的 draw 函数准备好 Canvas 后会调用 mView.draw(canvas),其中 mView 就是调用 ViewRoot.setView 时设置的 DecorView。然后看一下 View.java 中的 draw 函数:

递归的绘制整个窗口需要按顺序执行以下几个步骤:

绘制背景;

如果需要,保存画布(canvas)的层为淡入或淡出做准备;

绘制 View 本身的内容,通过调用 View.onDraw(canvas) 函数实现,通过这个我们应该能看出来 onDraw 函数重载的重要性,onDraw 函数中绘制线条 / 圆 / 文字等功能会调用 Canvas 中对应的功能。下面我们会 drawLine 函数为例进行说明;

绘制自己的孩子(通常也是一个 view 系统),通过 dispatchDraw(canvas) 实现,参看 ViewGroup.Java 中的代码可知,dispatchDraw->drawChild->child.draw(canvas) 这样的调用过程被用来保证每个子 View 的 draw 函数都被调用,通过这种递归调用从而让整个 View 树中的所有 View 的内容都得到绘制。在调用每个子 View 的 draw 函数之前,需要绘制的 View 的绘制位置是在 Canvas 通过 translate 函数调用来进行切换的,窗口中的所有 View 是共用一个 Canvas 对象

如果需要,绘制淡入淡出相关的内容并恢复保存的画布所在的层(layer)

绘制修饰的内容(例如滚动条),这个可知要实现滚动条效果并不需要 ScrollView,可以在 View 中完成的,不过有一些小技巧,具体实现可以参看我们的 TextViewExample 示例代码

当一个 ChildView 要重画时,它会调用其成员函数 invalidate() 函数将通知其 ParentView 这个 ChildView 要重画,这个过程一直向上遍历到 ViewRoot,当 ViewRoot 收到这个通知后就会调用上面提到的 ViewRoot 中的 draw 函数从而完成绘制。View::onDraw() 有一个画布参数 Canvas, 画布顾名思义就是画东西的地方,Android 会为每一个 View 设置好画布,View 就可以调用 Canvas 的方法,比如:drawText, drawBitmap, drawPath 等等去画内容。每一个 ChildView 的画布是由其 ParentView 设置的,ParentView 根据 ChildView 在其内部的布局来调整 Canvas,其中画布的属性之一就是定义和 ChildView 相关的坐标系,默认是横轴为 X 轴,从左至右,值逐渐增大,竖轴为 Y 轴,从上至下,值逐渐增大 , 见下图 :

图 4. 窗口坐标系


窗口坐标系

Android 动画就是通过 ParentView 来不断调整 ChildView 的画布坐标系来实现的,下面以平移动画来做示例,见下图 4,假设在动画开始时 ChildView 在 ParentView 中的初始位置在 (100,200) 处,这时 ParentView 会根据这个坐标来设置 ChildView 的画布,在 ParentView 的 dispatchDraw 中它发现 ChildView 有一个平移动画,而且当前的平移位置是 (100, 200),于是它通过调用画布的函数 traslate(100, 200) 来告诉 ChildView 在这个位置开始画,这就是动画的第一帧。如果 ParentView 发现 ChildView 有动画,就会不断的调用 invalidate() 这个函数,这样就会导致自己会不断的重画,就会不断的调用 dispatchDraw 这个函数,这样就产生了动画的后续帧,当再次进入 dispatchDraw 时,ParentView 根据平移动画产生出第二帧的平移位置 (500, 200),然后继续执行上述操作,然后产生第三帧,第四帧,直到动画播完。具体算法描述如清单 2:


清单 2. 算法

 

dispatchDraw()   
    {   
        ....   
        Animation a = ChildView.getAnimation()   
        Transformation tm = a.getTransformation();   
        Use tm to set ChildView's Canvas;   
        Invalidate();   
        ....   
    }  


 图 5. 平移动画示意图


平移动画示意图

以上是以平移动画为例子来说明动画的产生过程,这其中又涉及到两个重要的类型,Animation 和 Transformation,这两个类是实现动画的主要的类,Animation 中主要定义了动画的一些属性比如开始时间、持续时间、是否重复播放等,这个类主要有两个重要的函数:getTransformation 和 applyTransformation,在 getTransformation 中 Animation 会根据动画的属性来产生一系列的差值点,然后将这些差值点传给 applyTransformation,这个函数将根据这些点来生成不同的 Transformation,Transformation 中包含一个矩阵和 alpha 值,矩阵是用来做平移、旋转和缩放动画的,而 alpha 值是用来做 alpha 动画的(简单理解的话,alpha 动画相当于不断变换透明度或颜色来实现动画),以上面的平移矩阵为例子,当调用 dispatchDraw 时会调用 getTransformation 来得到当前的 Transformation,这个 Transformation 中的矩阵如下:

图 6. 矩阵变换图


矩阵变换图

所以具体的动画只需要重载 applyTransformation 这个函数即可,类层次图如下:

图 7. 动画类继承关系图


动画类继承关系图

用户可以定义自己的动画类,只需要继承 Animation 类,然后重载 applyTransformation 这个函数。对动画来说其行为主要靠差值点来决定的,比如,我们想开始动画是逐渐加快的或者逐渐变慢的,或者先快后慢的,或者是匀速的,这些功能的实现主要是靠差值函数来实现的,Android 提供了 一个 Interpolator 的基类,你要实现什么样的速度可以重载其函数 getInterpolation,在 Animation 的 getTransformation 中生成差值点时,会用到这个函数。

从上面的动画机制的分析可知某一个 View 的动画的绘制并不是由他自己完成的而是由它的父 view 完成,所有我们要注意上面 TextView 旋转一周的动画示例程序中动画的效果并不是由 TextView 来绘制的,而是由它的父 View 来做的。findViewById(R.id.TextView01).startAnimation(anim) 这个代码其实是给这个 TextView 设置了一个 animation,而不是进行实际的动画绘制,代码如下 :

 public void startAnimation(Animation animation)   
    {   
      
        animation.setStartTime(Animation.START_ON_FIRST_FRAME);   
      
        setAnimation(animation); invalidate();   
      
    } 

 

其他的动画机制的代码感兴趣的读者请自己阅读,希望通过原理的讲解以后看起来会轻松点,呵呵。

以上就是 Android 的动画框架的原理,了解了原理对我们的开发来说就可以清晰的把握动画的每一帧是怎样生成的,这样便于开发和调试。它把动画的播放 / 绘制交给父 View 去处理而不是让子 View 本身去绘制,这种从更高的层次上去控制的方式便于把动画机制做成一个易用的框架,如果用户要在某个 view 中使用动画,只需要在 xml 描述文件或代码中指定就可以了,从而把动画的实现和 View 本身内容的绘制(象 TextView 里面的文字显示)分离开了,起到了减少耦合和提高易用性的效果。

动画实现示例

在这个例子中,将要实现一个绕 Y 轴旋转的动画,这样可以看到 3D 透视投影的效果,代码如下 ( 清单 4):

清单 3. 实现一个绕 Y 轴旋转的动画


 package com.example.android.apis.animation;   
import android.view.animation.Animation;   
import android.view.animation.Transformation;   
import android.graphics.Camera;   
import android.graphics.Matrix;   
/**  
* An animation that rotates the view on the Y axis between two specified angles.  
* This animation also adds a translation on the Z axis (depth) to improve the effect.  
*/   
public class Rotate3dAnimation extends Animation   
{   
   private final float mFromDegrees;   
   private final float mToDegrees;   
   private final float mCenterX;   
   private final float mCenterY;   
   private final float mDepthZ;   
   private final boolean mReverse;   
   private Camera mCamera;   
   /**  
    * Creates a new 3D rotation on the Y axis. The rotation is defined by its  
    * start angle and its end angle. Both angles are in degrees. The rotation  
    * is performed around a center point on the 2D space, definied by a pair  
    * of X and Y coordinates, called centerX and centerY. When the animation  
    * starts, a translation on the Z axis (depth) is performed. The length  
    * of the translation can be specified, as well as whether the translation  
    * should be reversed in time.  
    *  
    * @param fromDegrees the start angle of the 3D rotation  
    * @param toDegrees the end angle of the 3D rotation  
    * @param centerX the X center of the 3D rotation  
    * @param centerY the Y center of the 3D rotation  
    * @param reverse true if the translation should be reversed, false otherwise  
    */   
   public Rotate3dAnimation(float fromDegrees, float toDegrees,   
                 float centerX,       float centerY,  
                 float depthZ,       boolean reverse)   
   {   
       mFromDegrees = fromDegrees;   
       mToDegrees = toDegrees;   
       mCenterX = centerX;   
       mCenterY = centerY;   
       mDepthZ = depthZ;   
       mReverse = reverse;   
   }   
 
   @Override   
   public void initialize(int width, int height, int parentWidth, int parentHeight)   
   {   
       super.initialize(width, height, parentWidth, parentHeight);   
       mCamera = new Camera();   
   }   
 
   @Override   
   protected void applyTransformation(float interpolatedTime, Transformation t)  
   {   
       final float fromDegrees = mFromDegrees;   
       float degrees = fromDegrees + ((mToDegrees - fromDegrees) * interpolatedTime);   
       final float centerX = mCenterX;   
       final float centerY = mCenterY;   
       final Camera camera = mCamera;   
       final Matrix matrix = t.getMatrix();   
       camera.save();   
         
       if (mReverse)  
       {   
           camera.translate(0.0f, 0.0f, mDepthZ * interpolatedTime);   
       }   
       else   
       {   
           camera.translate(0.0f, 0.0f, mDepthZ * (1.0f - interpolatedTime));   
       }   
         
       camera.rotateY(degrees);   
       camera.getMatrix(matrix);   
       camera.restore();   
       matrix.preTranslate(-centerX, -centerY);   
       matrix.postTranslate(centerX, centerY);   
   }   
}


在这个例子中我们重载了 applyTransformation 函数,interpolatedTime 就是 getTransformation 函 数传下来的差值点,在这里做了一个线性插值算法来生成中间角度:float degrees = fromDegrees + ((mToDegrees - fromDegrees) * interpolatedTime); Camera 类是用来实现绕 Y 轴旋转后透视投影的,我们只需要其返回的 Matrix 值 , 这个值会赋给 Transformation 中的矩阵成员,当 ParentView 去为 ChildView 设置画布时,就会用它来设置坐标系,这样 ChildView 画出来的效果就是一个绕 Y 轴旋转同时带有透视投影的效果。利用这个动画便可以作出像立体翻页等比较酷的效果。如何使用这个 animation 请见 ApiDemos 程序包 com.example.android.apis.animation 中的 Transition3d.java 代码。


Android 中显示 Gif 格式图

有关这一部分,本文将不做详细介绍。 感兴趣的读者请参看 Apidemos 中 com.example.android.apis.graphics 下面的 BitmapDecode.java 中的示例代码。

这里先简单说明一下,它的实现是通过 Movie 这个类来对 Gif 文件进行读取和解码的,同时在 onDraw 函数中不断的绘制每一帧图片完成的,这个示例代码在 onDraw 中调用 invalidate 来反复让 View 失效来让系统不断调用 SampleView 的 onDraw 函数;至于选出哪一帧图片进行绘制则是传入系统当前时间给 Movie 类,然后让它根据时间顺序来选出帧图片。反复让 View 失效的方式比较耗资源,绘制效果允许的话可以采取延时让 View 失效的方式来减小 CPU 消耗。

目前使用这个方式播放一些 Gif 格式的动画时会出现花屏的现象,这是因为 Android 中使用的 libgif 库是比较老的版本,新的 tag 不支持,所以导致花屏,解决办法有制作 Gif 图片时别使用太新的 tag 或完善 android 中对应的 libgif 库。


结束语

本文介绍了 Android 动画框架的基本原理,可以帮助开发者深入理解 Android 的动画是如何实现的,从而能够充分利用 android 现有框架来做出够眩、够酷的动画效果。


appBase是一个Android app开发的基础集合,这样的目的可以让我们的Android app开发得更快速,本文我们来看看用appBase如何快速开发购物车

appBase基础介绍

appBase是什么?

appBase是一个Android app开发的基础集合,目的是任何应用都可以在这个基础之上开发app,省去了搭建框架的时间。

appBase=xutils+fastjson+avlib

    xutils使用了其中HttpUtils、BitmapUtils、DbUtils
    fastjson使用了json解析
    avlib大家比较陌生,这个库是我另外一个简单的工具库。主要功能是View的自动绑定、View的常用数据自动绑定、万能Adapter等

目的:是为了让懂java的同学能够快速上手Android开发。


一、看看框架结构

01.jpeg

- apicloud.sdk是对apicloud的云API的调用做了简单封装
- base:只包含BaseActivity
- http:基于HttpUtils简化了常用的网络请求,定义网络参数APIs的配置
- presenter:采用了MVP中的P来命名,可以让非UI处理业务抽出放到这个结构中,因此BasePresenter诞生了。
- util:常用的工具类
- widget:常用的自定义组件(待扩展)
- Application:继承android.app.Application,为了统一使用框架中的组件对象,避免了组件的重复创建。因此建议使用这个类配置application的name。当然也可以基于此类扩展。


二、创建一个新项目

    第一步:创建一个空的Android project
    技术分享
    注意:删除自动添加的android-support-v4.jar(appBase中包含有)
    第二步:引用appBase
    技术分享

    第三步:修改AndroidManifest.xml

    <manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.snicesoft.appbase.demo"
    android:versionCode="1"
    android:versionName="1.0" >

    <uses-sdk
        android:minSdkVersion="8"
        android:targetSdkVersion="21" />

    <application
        android:allowBackup="true"
        android:name="com.snicesoft.Application"
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name"
        android:theme="@style/AppTheme" >
    </application>
    </manifest>

    添加:android:name=”com.snicesoft.Application”

    第四步:创建Activity

    package com.snicesoft.appbase.demo;
    import com.snicesoft.avlib.annotation.Layout;
    import com.snicesoft.avlib.rule.IData;
    import com.snicesoft.avlib.rule.IHolder;
    import com.snicesoft.base.BaseActivity;
    @Layout(R.layout.activity_main)
    public class MainActivity extends
        BaseActivity<MainActivity.Holder, MainActivity.Data> {
        public class Holder extends IHolder {

            @Override
            public void initViewParams() {

            }

        }

        public class Data extends IData {

        }

        @Override
        public Data newData() {
            return new Data();
        }

        @Override
        public Holder newHolder() {
            return new Holder();
        }
    }

    看着class一栏,大家可能会花了眼,怎么这么长。这只是一种写法,推荐的写法(内部类)。我来说明下这个类:
    IHolder是指View自动绑定的容器
    IData是指View的数据自动绑定容器

    第五步:使用IHolder和IData

    package com.snicesoft.appbase.demo;
    import com.snicesoft.avlib.annotation.DataBind;
    import com.snicesoft.avlib.annotation.Id;
    import com.snicesoft.avlib.annotation.Layout;
    import com.snicesoft.avlib.rule.IData;
    import com.snicesoft.avlib.rule.IHolder;
    import com.snicesoft.base.BaseActivity;
    @Layout(R.layout.activity_main)
    public class MainActivity extends
        BaseActivity<MainActivity.Holder, MainActivity.Data> {
        public class Holder extends IHolder {
            @Id(R.id.textView1)
            TextView textView1;
            @Id(R.id.button1)
            Button button1;
            @Override
            public void initViewParams() {

            }

        }

        public class Data extends IData {
            @DataBind(id = R.id.textView1)
            String tv1 = "我是自动绑定的TextView";
            @DataBind(id = R.id.button1)
            String btn1 = "我是自动绑定的Button";
        }

        @Override
        public Data newData() {
            return new Data();
        }

        @Override
        public Holder newHolder() {
            return new Holder();
        }
    }

    运行结果

02.png


Android appBase 购物车开发


购物车,在商城app中是必不可少的一部分,也是使用的比较多的,这里简单的做一个效果。

先来看看效果图

这里写图片描述


1、创建项目

第一种、引用appBase项目即可
第二种、将appBase的jar文件copy到libs下

03.jpeg

我用的第二种,如上图所示。


2、代码生成

通过代码生成器生成Activity、Presenter、Adapter

1、生成Activity(默认生成Presenter)

01.jpeg
2、生成Adapter

01.jpeg
3、网络请求数据

这里网络数据使用的是APICloud,那么就需要对于APICloudSDK进行配置。最新的appBase讲配置放了出来,只要在applcation中进行代码配置就可以了。


package com.example.shopcartdemo;

import com.apicloud.sdk.APICloudSDK;
import com.snicesoft.Application;
import com.snicesoft.http.HttpReq;

public class MyApplication extends Application {
    final String APP_ID = "A6960031839242";
    final String APP_KEY = "3F248D5F-50DB-782A-F437-E13796238B9E";

    @Override
    public void onCreate() {
        super.onCreate();
        APICloudSDK.getInstance().init(APP_ID, APP_KEY);
        APICloudSDK.getInstance().init(hu());
        HttpReq.debug = true;
    }
}


这里添加了DialogPresenter,作用就是为了请求的时候对dialog的控制

DialogPresenter


package com.example.shopcartdemo.presenter;

import android.app.ProgressDialog;
import android.content.Context;

import com.snicesoft.presenter.BasePresenter;
import com.snicesoft.util.DialogUtil;

public class DialogPresenter<C extends BasePresenter.Callback> extends
        BasePresenter<C> {

    public DialogPresenter(Context context) {
        super(context);
        progressDialog = DialogUtil.getProgressDialog(context);
    }

    ProgressDialog progressDialog;

    protected void showDialog(CharSequence message, boolean... flag) {
        if (flag != null) {
            if (flag.length > 0)
                progressDialog.setCancelable(flag[0]);
            if (flag.length > 1)
                progressDialog.setCanceledOnTouchOutside(flag[1]);
        }
        progressDialog.setMessage(message);
        if (!progressDialog.isShowing())
            progressDialog.show();
    }

    protected void closeDialog() {
        if (progressDialog.isShowing())
            progressDialog.dismiss();
    }
}


MainPresenter


package com.example.shopcartdemo.presenter;

import java.util.ArrayList;
import java.util.List;

import android.content.Context;

import com.alibaba.fastjson.JSON;
import com.apicloud.sdk.APICloudSDK;
import com.example.shopcartdemo.adapter.ShopCartAdapter;
import com.lidroid.xutils.exception.HttpException;
import com.snicesoft.http.HttpCallback;
import com.snicesoft.presenter.BasePresenter;
import com.snicesoft.util.CommonUtils;

public class MainPresenter extends DialogPresenter<MainPresenter.Callback> {

    public MainPresenter(Context context) {
        super(context);
    }

    public interface Callback extends BasePresenter.Callback {
        void setShopCartList(List<ShopCartAdapter.Data> list);
    }

    public static class ShopCart {
        String title;
        int price;
        int count;

        public String getTitle() {
            return title;
        }

        public void setTitle(String title) {
            this.title = title;
        }

        public int getPrice() {
            return price;
        }

        public void setPrice(int price) {
            this.price = price;
        }

        public int getCount() {
            return count;
        }

        public void setCount(int count) {
            this.count = count;
        }

    }

    public void getShopCartList() {
        showDialog("正在加载");
        APICloudSDK
                .getInstance()
                .GET("/mcm/api/ShopCart?filter=%7B%22where%22%3A%7B%7D%2C%22skip%22%3A0%2C%22limit%22%3A20%7D",
                        null, new HttpCallback() {

                            @Override
                            public void onSuccess(String result) {
                                closeDialog();
                                List<ShopCart> array = JSON.parseArray(result,
                                        ShopCart.class);
                                List<ShopCartAdapter.Data> list = new ArrayList<ShopCartAdapter.Data>();
                                for (ShopCart cart : array) {
                                    list.add(new ShopCartAdapter.Data(
                                            0,
                                            cart.title,
                                            cart.price,
                                            "http://ck.haier.com/UpLoad/2015-05-15/a5e8cac4-2671-4aa0-83a7-66c64e051f95.jpg",
                                            cart.count));
                                }
                                if (callback != null)
                                    callback.setShopCartList(list);
                            }

                            @Override
                            public void onFailure(HttpException arg0) {
                                closeDialog();
                                CommonUtils.showToast(getContext(), "请求失败,请稍后重试");
                            }
                        });
    }
}



在这个类中,网络请求和网络解析实体对象都用内部类来定义,注意:内部类定义一定要用static class,否则fastjson无法正常解析,会导致无法反射创建对象。

4、数据绑定和交互

首先看下Activity


package com.example.shopcartdemo;

import java.util.List;

import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.CheckBox;
import android.widget.ListView;
import android.widget.TextView;

import com.example.shopcartdemo.adapter.ShopCartAdapter;
import com.example.shopcartdemo.adapter.ShopCartAdapter.ViewCallback;
import com.example.shopcartdemo.presenter.MainPresenter;
import com.snicesoft.avlib.annotation.DataBind;
import com.snicesoft.avlib.annotation.DataType;
import com.snicesoft.avlib.annotation.Id;
import com.snicesoft.avlib.annotation.Layout;
import com.snicesoft.avlib.rule.IData;
import com.snicesoft.avlib.rule.IHolder;
import com.snicesoft.base.BaseActivity;

@Layout(R.layout.activity_main)
public class MainActivity extends
        BaseActivity<MainActivity.Holder, MainActivity.Data> implements
        MainPresenter.Callback, ViewCallback {
    class Holder extends IHolder {
        @Id(R.id.lvShopCard)
        ListView lvShopCard;
        @Id(R.id.cbAll)
        CheckBox cbAll;
        @Id(R.id.tvPrice)
        TextView tvPrice;

        @Override
        public void initViewParams() {
            cbAll.setOnClickListener(new OnClickListener() {
                @Override
                public void onClick(View v) {
                    _data.shopCartAdapter.setAll(cbAll.isChecked());
                    tvPrice.setText("¥" + _data.shopCartAdapter.calc());
                }
            });
        }
    }

    class Data extends IData {
        @DataBind(id = R.id.lvShopCard, dataType = DataType.ADAPTER)
        ShopCartAdapter shopCartAdapter = new ShopCartAdapter(getBaseContext());
    }

    @Override
    public Holder newHolder() {
        return new Holder();
    }

    @Override
    public Data newData() {
        return new Data();
    }

    MainPresenter presenter;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        presenter = new MainPresenter(this);
        presenter.setCallback(this);
        _data.shopCartAdapter.setCallback(this);
    }

    @Override
    protected void onResume() {
        super.onResume();
        presenter.getShopCartList();
    }

    @Override
    public void onClick(View v) {
        super.onClick(v);
        switch (v.getId()) {
        case R.id.btnGoPay:
            break;
        case R.id.btnDelete:
            break;
        default:
            break;
        }
    }

    boolean isNormal = true;

    @Override
    public void setShopCartList(List<ShopCartAdapter.Data> list) {
        _data.shopCartAdapter.setDataList(list);
        refreshView();
    }

    @Override
    public void refreshView() {
        _holder.cbAll.setChecked(_data.shopCartAdapter.isAll());
        _holder.tvPrice.setText("¥" + _data.shopCartAdapter.calc());
    }

}


基本的Holder和Data将组件和数据进行简单的管理,清晰可见。
presenter将业务进行分离,将传统的activity中请求数据进行分离。
ViewCallback这个类是为了解决Adapter与Activity直接的交互定义的接口。

接下来看看Adapter


package com.example.shopcartdemo.adapter;

import android.content.Context;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.CheckBox;
import android.widget.CompoundButton;
import android.widget.CompoundButton.OnCheckedChangeListener;
import android.widget.ImageView;

import com.example.shopcartdemo.R;
import com.snicesoft.avlib.AVLib;
import com.snicesoft.avlib.annotation.DataBind;
import com.snicesoft.avlib.annotation.DataType;
import com.snicesoft.avlib.annotation.Id;
import com.snicesoft.avlib.annotation.Layout;
import com.snicesoft.avlib.rule.IData;
import com.snicesoft.avlib.rule.IHolder;
import com.snicesoft.avlib.view.ViewFinder;
import com.snicesoft.avlib.widget.AvAdapter;

@Layout(R.layout.item_shopcart)
public class ShopCartAdapter extends
        AvAdapter<ShopCartAdapter.Holder, ShopCartAdapter.Data> {

    public ShopCartAdapter(Context context) {
        super(context);
    }

    class Holder extends IHolder {
        @Id(R.id.btnAdd)
        Button btnAdd;
        @Id(R.id.btnDelete)
        Button btnDelete;
        @Id(R.id.cbSelect)
        CheckBox cbSelect;
        @Id(R.id.img)
        ImageView img;

        @Override
        public void initViewParams() {
        }
    }

    public static class Data extends IData {
        long gid;

        boolean isChecked = true;

        public long getGid() {
            return gid;
        }

        @DataBind(id = R.id.tvTitle)
        String title;
        @DataBind(id = R.id.tvPrice, prefix = "¥")
        int price;
        @DataBind(id = R.id.img, dataType = DataType.IMG)
        String image;
        @DataBind(id = R.id.edtCount)
        int count;

        public Data(long gid, String title, int price, String image, int count) {
            super();
            this.gid = gid;
            this.title = title;
            this.price = price;
            this.image = image;
            this.count = count;
        }
    }

    public interface ViewCallback {
        void refreshView();
    }

    ViewCallback callback;

    public void setCallback(ViewCallback callback) {
        this.callback = callback;
    }

    @Override
    public Holder newHolder() {
        return new Holder();
    }

    @Override
    public void bindAfter(int position, final View view, Holder holder,
            final Data data) {
        holder.cbSelect.setOnCheckedChangeListener(null);
        holder.cbSelect.setChecked(data.isChecked);
        holder.btnAdd.setOnClickListener(new OnClickListener() {

            @Override
            public void onClick(View v) {
                data.count++;
                AVLib.dataBindTo(data, new ViewFinder(view), "count");
                if (callback != null)
                    callback.refreshView();
            }
        });
        holder.btnDelete.setOnClickListener(new OnClickListener() {

            @Override
            public void onClick(View v) {
                if (data.count > 1) {
                    data.count--;
                    AVLib.dataBindTo(data, new ViewFinder(view), "count");
                    if (callback != null)
                        callback.refreshView();
                }
            }
        });

        holder.cbSelect
                .setOnCheckedChangeListener(new OnCheckedChangeListener() {

                    @Override
                    public void onCheckedChanged(CompoundButton buttonView,
                            boolean isChecked) {
                        data.isChecked = isChecked;
                        if (callback != null)
                            callback.refreshView();
                    }
                });

    }

    public void setAll(boolean isChecked) {
        for (Data data : getDataList()) {
            data.isChecked = isChecked;
        }
        notifyDataSetChanged();
    }

    public int calc() {
        int total = 0;
        for (Data data : getDataList()) {
            if (data.isChecked)
                total += data.count * data.price;
        }
        return total;
    }

    public boolean isAll() {
        for (Data data : getDataList()) {
            if (!data.isChecked)
                return false;
        }
        return true;
    }
}



Holder的组件定义原则:需要在当前所在类中调用即可定义
Data的DataBind使用原则:需要绑定的的组件都可以。
当然通过上面可以看出Data中可以定义不用DataBind注解的字段,这个替代了传统的辅助Map或者List解决一些问题。
在上面的Data中用isChecked来表示CheckBox是否选中。这个问题,在我刚开始写android的时候,常常会用Map集合将position对应的boolean值记录下来,然后在getView中去检测。现在可以简单的通过Data几种管理,使得Adapter的字段不需要那么繁琐。

对于isChecked的使用产生了下面3个业务方法
setAll:这个方法用来设置全部选中(Activity中的全选按钮事件)
calc:这个方法用来计算购物车总额(Activity中的总计)
isAll:这个方法用来判断是否全部被选中(Activity的全选按钮设置checked的依据)


开发思路我们就讲得差不多了,做开发,最重要的还是思路,整体代码我们就不给出了,有兴趣的朋友可以自己整理代码,加深印象。

对于初学者对于app中的样式与按钮可能不知道如何来做了,下文我们就一起来看一篇Android开发之安卓上Mac样式的按钮了,例子如下。

最近试着玩玩Android开发,做一个小玩意儿的时候,总感觉默认的按钮样式太糟糕,看到网上几幅IPhone截图,觉得按钮有点感觉,就想着抄一个过来……相当的没有技术含量,只不过记性不好,记录一下。

PhotoShop上的准备

用简单的方法,设置background图片。

先渐变填充圆角矩形(半径4左右),渐变的首尾颜色自己定好了,不过过渡位置我试了下,大约是上图1所示,在40%和60%增加两个色标,值为首尾之差的1/3和2/3。
做出立体效果和阴影,如2所示
做出边框线,如3所示
然后调两个亮一点和暗一点的(没用过IPhone不知道有焦点和按下时什么样子我这里就偷懒弄个明暗变化)按钮。
应用到按钮上

在drawable里定义一个式样文件

<?xml version="1.0" encoding="utf-8"?>
<selector
  xmlns:android="http://schemas.android.com/apk/res/android">
  <item android:state_pressed="true" android:drawable="@drawable/game_button_pressed" />
  <item android:state_focused="true" android:drawable="@drawable/game_button_focused" />
  <item android:drawable="@drawable/game_button_normal" />
</selector>

然后按钮的background指向它,这就完了~~

追记:

后来看到一种完全用XML定义按钮的方法,靠谱,虽然变化好像有些单调,但可以不用图片毕竟是好事,先记录一下方法,有空试试~


<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
 <item android:state_pressed="true">
  <shape>
   <gradient android:startColor="#0d76e1" android:endColor="#0d76e1" android:angle="270" />
   <stroke android:width="1dip" android:color="#f403c9" />
   <corners android:radius="2dp" />
   <padding android:left="10dp" android:top="10dp"
    android:right="10dp" android:bottom="10dp" />
  </shape>
 </item>
 
 <item android:state_focused="true">
  <shape>
   <gradient android:startColor="#ffc2b7" android:endColor="#ffc2b7" android:angle="270" />
   <stroke android:width="1dip" android:color="#f403c9" />
   <corners android:radius="2dp" />
   <padding android:left="10dp" android:top="10dp"
    android:right="10dp" android:bottom="10dp" />
  </shape>
 </item>
 
 <item>
  <shape>
   <gradient android:startColor="#000000" android:endColor="#ffffff" android:angle="180" />
   <stroke android:width="1dip" android:color="#f403c9" />
   <corners android:radius="5dip" />
   <padding android:left="10dp" android:top="10dp"
    android:right="10dp" android:bottom="10dp" />
  </shape>
 </item>
</selector>

现在的app应用都可以调用手机的摄像头了,这样可以直接拍照了,对于在开发端来讲摄像头调用有许多的一些细节要注意了,下面来看一篇小编整理的Android应用调用摄像头开发例子


这两天玩Android玩的废寝忘食,Blog都好几天没加东西了,惭愧!记录一下这两天最崩溃的一个问题。

好早就装了开发环境,真正着手还是这两天,非常的生疏,虽然有SDK文档,那么多蚊子一般的字,实在没心思慢慢研究。这不想调用摄像头,原以为很容易就能搞定的,累计花了大概有一天的时间才只能保证不出错……至于效果嘛,难说啊!

先看API-examples里有调用 摄像头的例子,在模拟器上虽然看不出什么效果,毕竟还是能执行的,就是一个方块在黑白相间的背景上移动呗。

就这么一个Google提供的范例,传到我的HTC G2上也能一执行就报错,我对Google的尊敬之情顿时减少了0.0001%啊……(当然有可能是G2不够标准,但毕竟其他的软件都是能用的,看来是有不少健壮代码了啊)。联机调试看了一下,出错的这一行(android-7里的):


parameters.setPreviewSize(w, h);

查一下,摄像头不是所有随便的(w, h)都能够认识的,所以呢,我们有了下面这样的增强版:

List<Size> mSupportedPreviewSizes;
mSupportedPreviewSizes = mCamera.getParameters().getSupportedPreviewSizes();
mPreviewSize = getOptimalPreviewSize(mSupportedPreviewSizes, width, height);
 
private Size getOptimalPreviewSize(List<Size> sizes, int w, int h) {
        final double ASPECT_TOLERANCE = 0.1;
        double targetRatio = (double) w / h;
        if (sizes == null) return null;
 
        Size optimalSize = null;
        double minDiff = Double.MAX_VALUE;
 
        int targetHeight = h;
 
        // Try to find an size match aspect ratio and size
        for (Size size : sizes) {
            double ratio = (double) size.width / size.height;
            if (Math.abs(ratio - targetRatio) > ASPECT_TOLERANCE) continue;
            if (Math.abs(size.height - targetHeight) < minDiff) {
                optimalSize = size;
                minDiff = Math.abs(size.height - targetHeight);
            }
        }
 
        // Cannot find the one match the aspect ratio, ignore the requirement
        if (optimalSize == null) {
            minDiff = Double.MAX_VALUE;
            for (Size size : sizes) {
                if (Math.abs(size.height - targetHeight) < minDiff) {
                    optimalSize = size;
                    minDiff = Math.abs(size.height - targetHeight);
                }
            }
        }
        return optimalSize;
    }
后来的Sample里有了这段代码,看起来强大了不少。然而非常不幸的,首先getSupportedPreviewSizes()这个函数在2.1之后才有,我一开始是打算用1.6开发的……好吧我改,这个先不说,自己的手机已经刷到2.1了,这个函数的返回值居然是null?!如果确实想老版本上也用的话,怎么办??

有鉴于有软件可以达成,所以肯定是有方法的!得这么写:

public class SupportedSizesReflect {
 private static Method Parameters_getSupportedPreviewSizes = null; 
 private static Method Parameters_getSupportedPictureSizes = null;
 
 static {
  initCompatibility();
 };
 
 private static void initCompatibility() {
  try {
   Parameters_getSupportedPreviewSizes = Camera.Parameters.class.getMethod(
     "getSupportedPreviewSizes", new Class[] {});
 
   Parameters_getSupportedPictureSizes = Camera.Parameters.class.getMethod(
     "getSupportedPictureSizes", new Class[] {});
 
  } catch (NoSuchMethodException nsme) {
   nsme.printStackTrace();
   Parameters_getSupportedPreviewSizes = Parameters_getSupportedPictureSizes = null;   
  }  
 }
 
 /**
  * Android 2.1之后有效
  * @param p
  * @return Android1.x返回null
  */
 public static List<Size> getSupportedPreviewSizes(Camera.Parameters p) {
  return getSupportedSizes(p, Parameters_getSupportedPreviewSizes);
 }
 
 public static List<Size> getSupportedPictureSizes(Camera.Parameters p){
  return getSupportedSizes(p, Parameters_getSupportedPictureSizes);
 } 
 
 @SuppressWarnings("unchecked")
 private static List<Size> getSupportedSizes(Camera.Parameters p, Method method){
  try {
   if (method != null) {
    return (List<Size>) method.invoke(p);
   } else {
    return null;
   }
  } catch (InvocationTargetException ite) {
   Throwable cause = ite.getCause();
   if (cause instanceof RuntimeException) {
    throw (RuntimeException) cause;
   } else if (cause instanceof Error) {
    throw (Error) cause;
   } else {
    throw new RuntimeException(ite);
   }
  } catch (IllegalAccessException ie) {
   return null;
  }
 } 
}

啊啊~,リフレクションなんか、大嫌い……然后还要用类似这样的方法调用~


@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width,int height) {
 
 Camera.Parameters params = camera.getParameters();
 
 List<Size> supportedPictureSizes
    = SupportedSizesReflect.getSupportedPictureSizes(params);
 List<Size> supportedPreviewSizes
    = SupportedSizesReflect.getSupportedPreviewSizes(params);
 
 if ( supportedPictureSizes != null &&
  supportedPreviewSizes != null &&
  supportedPictureSizes.size() > 0 &&
  supportedPreviewSizes.size() > 0) {
 
  //2.x
  pictureSize = supportedPictureSizes.get(0);
 
  int maxSize = 1280;
  if(maxSize > 0){
   for(Size size : supportedPictureSizes){       
    if(maxSize >= Math.max(size.width,size.height)){
     pictureSize = size;
     break;
    }      
   }
  }
 
  WindowManager windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);  
  Display display = windowManager.getDefaultDisplay();  
  DisplayMetrics displayMetrics = new DisplayMetrics();
  display.getMetrics(displayMetrics);
 
  previewSize = getOptimalPreviewSize(
       supportedPreviewSizes,
       display.getWidth(),
       display.getHeight());
 
  params.setPictureSize(pictureSize.width, pictureSize.height);  
  params.setPreviewSize(previewSize.width, previewSize.height);        
 
 }
 this.camera.setParameters(params);
 try {
  this.camera.setPreviewDisplay(holder);
 } catch (IOException e) {
  e.printStackTrace();
 }
 this.camera.startPreview();
}

死机无数次之后总结出来的啊,发现程序写的一个不好强制结束了,摄像头都无法再次启用了,kill都不行,只能重新启动手机才好。重启一次还那么慢,谁知道有比较适合G2的row?

哦还有一个,预览画面90°的,2.X后可以用parameters.set(“rotation”, “90″),之前的话得写成parameters.set(“orientation”, “portrait”)。但是据说不是所有的机器都可以的…


上面讲的是摄像头的初始化,如果觉得这么就万事OK的话,那就大错特错了。接下来的东西让人感到更加头痛。

在我的这个应用里,不需要把拍下来的图片存储,只需要把预览的图片数据处理一下就好,很自然的我只是用了onPreviewFrame调用,考虑处理传递进来的data数据流就是了。

网上很多帖子都说,然后用BitmapFactory的decodeByteArray()函数来解析图片就行了,我试了一下,发现这真是彻头彻尾的谎言,data字节流默认是YCbCr_420_SP(虽然可以改,但其他的格式未必兼容),decodeByteArray()压根儿不认!SDK2.2之后,似乎提供了一个YuvImage的类来转一下(那Google一开始提供这个借口是做什么的?),难道就要把老机给抛弃了么??万万不能啊(穷人最理解穷人们了)!

好在这个世界总是不缺少好人和牛人的,有人提供了这么一段转换的代码:

static public void decodeYUV420SP(int[] rgb, byte[] yuv420sp, int width, int height) {
    final int frameSize = width * height;
 
    for (int j = 0, yp = 0; j < height; j++) {
        int uvp = frameSize + (j >> 1) * width, u = 0, v = 0;
        for (int i = 0; i < width; i++, yp++) {
            int y = (0xff & ((int) yuv420sp[yp])) - 16;
            if (y < 0) y = 0;
            if ((i & 1) == 0) {
                v = (0xff & yuv420sp[uvp++]) - 128;
                u = (0xff & yuv420sp[uvp++]) - 128;
            }
 
            int y1192 = 1192 * y;
            int r = (y1192 + 1634 * v);
            int g = (y1192 - 833 * v - 400 * u);
            int b = (y1192 + 2066 * u);
 
            if (r < 0) r = 0; else if (r > 262143) r = 262143;
            if (g < 0) g = 0; else if (g > 262143) g = 262143;
            if (b < 0) b = 0; else if (b > 262143) b = 262143;
 
            rgb[yp] = 0xff000000 | ((r << 6) & 0xff0000) | ((g >> 2) & 0xff00) | ((b >> 10) & 0xff);
        }
    }
}

我不是很清楚这里面的原理,但是它能在我这里工作,暂时可以了……然后你才可以吧处理完的rgb[]传给decodeByteArray()。

顺便好心的把使用SDK2.2之后的也贴上吧,万一有用呢……

public void onPreviewFrame(byte[] data, Camera arg1) {
    FileOutputStream outStream = null;
    try {
        YuvImage yuvimage = new YuvImage(data,ImageFormat.NV21,arg1.getParameters().getPreviewSize().width,arg1.getParameters().getPreviewSize().height,null);
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        yuvimage.compressToJpeg(new Rect(0,0,arg1.getParameters().getPreviewSize().width,arg1.getParameters().getPreviewSize().height), 80, baos);
 
        outStream = new FileOutputStream(String.format("/sdcard/%d.jpg", System.currentTimeMillis()));       
        outStream.write(baos.toByteArray());
        outStream.close();
 
        Log.d(TAG, "onPreviewFrame - wrote bytes: " + data.length);
    } catch (FileNotFoundException e) {
        e.printStackTrace();
    } catch (IOException e) {
        e.printStackTrace();
    } finally {
    }
    Preview.this.invalidate();
}


哦,得到的图像旋转了90°(似乎有的机型设置一下setRotation(90)可以搞定,但还是那句话,不通用啊,况且这个是2.1之后的API)。手动转一下吧……


Matrix matrix = new Matrix();
matrix.postRotate(90);
// 这里的rgb就是刚刚转换处理的东东
Bitmap bmp = Bitmap.createBitmap(rgb, 0, w, w, h, Bitmap.Config.ARGB_4444);
Bitmap nbmp = Bitmap.createBitmap(bmp,
          0, 0, bmp.getWidth(),  bmp.getHeight(), matrix, true);
终于正常了~~~

考虑到需要做识别,自然得先把它转成灰度图像,经典心理公式Gray = R*0.299 + G*0.587 + B*0.114出场了,但是手机的计算速度不那么快,这样的浮点运算还是尽量避免吧~ 于是考虑Gray = (R*299 + G*587 + B*114 + 500) / 1000或者Gray = (R*30 + G*59 + B*11 + 50) / 100。但是除法总是还是不够快,用移位吧……Gray = (R*19595 + G*38469 + B*7472) >> 16,稍微小一点,用Gray = (R*38 + G*75 + B*15) >> 7也足够了。

经过一番努力学习,把写就的代码兴致勃勃的在手机上跑了一下,虽然不够快结果出来了,想想也是大负荷运算啊,自我安慰客户应该可以有这样的耐心吧。

就在这个时候,我突然想起一件很重要的事情!
我需要的是灰度图,也就是亮度风量,而最开始的YUV,不就是亮度色度饱和度么?!那么Y分类不就是我需要的灰度值吗!!我在做什么,辛辛苦苦转成RGB,再转成亮度,吃饱了撑着不是。想到这里我立刻用头撞墙九九一百八十一次,一悼念我那白白死去的脑细胞的在天之灵。立刻重写,删除大量代码,快多了,效果也好~~ 鄙视一下两小时前的自己!

[!--infotagslink--]

相关文章

  • 409错误是什么 http 409错误怎么解决

    409错误是什么?http 409错误怎么解决呢?不少站长在遇到这个错误代码之后都一筹莫展,本次一聚教程网为大家带来了详细的说明,快来看看吧。 409错误是什么: HTTP 40...2017-01-22
  • http 405错误是什么 http 405错误怎么解决

    http 405错误是什么?http 405错误怎么解决?相信很多站长都在找这两个问题的答案,本次小编为大家带来了详细的教程,快来看看吧。 405错误是什么: HTTP 405错误是H...2017-01-22
  • 403错误是什么 403错误怎么解决

    403错误是HTTP状态码的一种,属于“请示错误”,表示服务器拒绝请求。如果在搜索引擎尝试抓取您网站上的有效网页时显示此状态代码,那么,这可能是您的服务器或主机拒绝搜索...2017-01-22
  • 412错误是什么 412错误怎么解决

    412错误是什么?412错误怎么解决?本次一聚教程网将为大家带来详细的介绍,帮助大家全面了解412错误的意思以及解决412错误的方法。 412错误是什么: HTTP 412错误,(Precond...2017-01-22
  • Perl CPAN::Modulelist的解决办法

    今天用CPAN安装Term::ReadLine,报了个这样的错误 Going to read /root/.cpan/sources/modules/03modlist.data.gz Can't locate object method "data" via package "C...2016-11-25
  • Android子控件超出父控件的范围显示出来方法

    下面我们来看一篇关于Android子控件超出父控件的范围显示出来方法,希望这篇文章能够帮助到各位朋友,有碰到此问题的朋友可以进来看看哦。 <RelativeLayout xmlns:an...2016-10-02
  • 406错误是什么 406错误怎么解决

    HTTP 406错误是HTTP协议状态码的一种,表示无法使用请求的内容特性来响应请求的网页。一般是指客户端浏览器不接受所请求页面的 MIME 类型。 而MIME类型是在把输出...2017-01-22
  • 407错误是什么 407错误怎么解决

    407错误是什么?407错误怎么解决?不少站长都遇到过407错误,下面小编将告诉大家如何处理407错误。 407错误是什么: HTTP 407错误是HTTP协议状态码的一种,表示需要代...2017-01-22
  • c++中的system("pause")的作用和含义解析

    这篇文章主要介绍了c++中system("pause")的作用和含义,非常不错,具有参考借鉴价值,需要的朋友参考下吧...2020-04-25
  • 410错误是什么 http 410错误怎么解决

    410错误是HTTP协议状态码的一种,本次一聚教程网将为大家详细介绍HTTP 410错误是什么,以及410错误的解决办法。 410错误是什么: HTTP 410错误是HTTP协议状态码的...2017-01-22
  • HTTP 400错误是什么 HTTP 400错误怎么解决

    每当遇到http错误代码为400,代表客户端发起的请求不符合服务器对请求的某些限制,或者请求本身存在一定的错误,那么HTTP 400错误怎么解决呢?请看下文介绍。 目前400错...2017-01-22
  • Android开发中findViewById()函数用法与简化

    findViewById方法在android开发中是获取页面控件的值了,有没有发现我们一个页面控件多了会反复研究写findViewById呢,下面我们一起来看它的简化方法。 Android中Fin...2016-09-20
  • Android模拟器上模拟来电和短信配置

    如果我们的项目需要做来电及短信的功能,那么我们就得在Android模拟器开发这些功能,本来就来告诉我们如何在Android模拟器上模拟来电及来短信的功能。 在Android模拟...2016-09-20
  • 夜神android模拟器设置代理的方法

    夜神android模拟器如何设置代理呢?对于这个问题其实操作起来是非常的简单,下面小编来为各位详细介绍夜神android模拟器设置代理的方法,希望例子能够帮助到各位。 app...2016-09-20
  • android自定义动态设置Button样式【很常用】

    为了增强android应用的用户体验,我们可以在一些Button按钮上自定义动态的设置一些样式,比如交互时改变字体、颜色、背景图等。 今天来看一个通过重写Button来动态实...2016-09-20
  • Android WebView加载html5页面实例教程

    如果我们要在Android应用APP中加载html5页面,我们可以使用WebView,本文我们分享两个WebView加载html5页面实例应用。 实例一:WebView加载html5实现炫酷引导页面大多...2016-09-20
  • 深入理解Android中View和ViewGroup

    深入理解Android中View和ViewGroup从组成架构上看,似乎ViewGroup在View之上,View需要继承ViewGroup,但实际上不是这样的。View是基类,ViewGroup是它的子类。本教程我们深...2016-09-20
  • Android自定义WebView网络视频播放控件例子

    下面我们来看一篇关于Android自定义WebView网络视频播放控件开发例子,这个文章写得非常的不错下面给各位共享一下吧。 因为业务需要,以下代码均以Youtube网站在线视...2016-10-02
  • Android用MemoryFile文件类读写进行性能优化

    java开发的Android应用,性能一直是一个大问题,,或许是Java语言本身比较消耗内存。本文我们来谈谈Android 性能优化之MemoryFile文件读写。 Android匿名共享内存对外A...2016-09-20
  • Android设置TextView竖着显示实例

    TextView默认是横着显示了,今天我们一起来看看Android设置TextView竖着显示如何来实现吧,今天我们就一起来看看操作细节,具体的如下所示。 在开发Android程序的时候,...2016-10-02