Quantcast
Channel: CSDN博客移动开发推荐文章
Viewing all 5930 articles
Browse latest View live

Activity启动模式详解

$
0
0

众所周知,android给我们提供了standardsingleTopsingleTasksingleInstance 4种Activity的启动模式,其中:

standard:标准模式,即默认启动模式;

singleTop:顶单例模式,即要求实例处于栈顶;

singleTask:内单例模式,即要求栈中只有一个实例;

singleInstance:全局单例模式;

接下来将会对以上4种加载模式进行讲解。

1standard模式

每次通过这种模式启动目标Activity时,android总会为目标Activity创建一个新的实例,并将该实例添加到当前的任务栈中,这种模式不会启动新的任务栈,新创建的Activity实例将被添加到原有的任务栈中。如:

public class StandardActivity extends AppCompatActivity {

    private TextView tv_content;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_standard);
        tv_content = (TextView) findViewById(R.id.tv_content);
        tv_content.setText("当前Activity为:"+"\n"
                +this.toString()
                +"\n"+" TaskId为:"+this.getTaskId());
    }

    public void skip2StandardActivity(View view){
        Intent intent = new Intent(StandardActivity.this,StandardActivity.class);
        startActivity(intent);
    }
}
运行程序,可以发现每次点击StandardActivity都会创建一个新的StandardActivity实例,在控制台中输入:adb shell dumpsys activity然后回车可以看到Activity的栈情况

需要注意的是,Standard加载模式无须配置目标ActivitylaunchMode属性,目标Activity会默认采用standard加载模式。


2singleTop模式

singleTop模式与standard模式基本相似,唯一不同的是,当将要被启动的目标Activity已经处于任务栈栈顶时,系统不会重新创建目标Activity的实例,而是直接复用已有的Activity,当然,如果将要被启动的目标Activity没有位于任务栈栈顶的话,此时系统会重新目标Activity,并将创建的新的Activity实例添加到任务栈中(此时与standard加载模式相同)。如:

public class StandardActivity extends AppCompatActivity {

    private TextView tv_content;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_standard);
        tv_content = (TextView) findViewById(R.id.tv_content);
        tv_content.setText("当前Activity为:"+"\n"
                +this.toString()
                +"\n"+" TaskId为:"+this.getTaskId());
    }

    public void skip2StandardActivity(View view){
        Intent intent = new Intent(StandardActivity.this,StandardActivity.class);
        startActivity(intent);
    }

    public void skip2SingleTopActivity(View view){
        Intent intent = new Intent(StandardActivity.this,SingleTopActivity.class);
        startActivity(intent);
    }
}

目标SingleTopActivity 的主要代码如下所示:

public class SingleTopActivity extends AppCompatActivity {

    private TextView tv_content;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_standard);
        tv_content = (TextView) findViewById(R.id.tv_content);
        tv_content.setText("当前Activity为:"+"\n"
                +this.toString()
                +"\n"+" TaskId为:"+this.getTaskId());
    }
    public void skip2StandardActivity(View view){
        Intent intent = new Intent(SingleTopActivity.this,StandardActivity.class);
        startActivity(intent);
    }

    public void skip2SingleTopActivity(View view){
        Intent intent = new Intent(SingleTopActivity.this,SingleTopActivity.class);
        startActivity(intent);
    }
}
另外,需要在AndroidManifest清单文件中为目标SingleTopActivity配置launchMode属性,并将其指定为singleTop,如下所示:

<activity android:name=".SingleTopActivity"
    android:launchMode="singleTop"/>
此时,运行程序可以发现,当在StandardActivity中点击SingleTopActivity按钮时会创建一个SingleTopActivity的实例,当在SingleTopActivity中点击SingleTopActivity按钮时,由于SingleTopActivity已经在任务栈的栈顶,因此不会再创建新的实例,当然,如果在SingleTopActivity中点击StandardActivity按钮回到StandardActivity界面时,此时再点击SingleTopActivity按钮,会发现,又创建了一个新的SingleTopActivity的实例。同样的,在控制台输入:adb shell dumpsys activity命令可以查看Activity的任务栈情况如下所示:


3singleTask模式

采用singleTask加载模式的Activity在同一个任务栈内只有一个实例,当系统采用singleTask模式启动目标Activity时,可分为如下几种情况:

1)如果将要启动的目标Activity不存在,系统将会创建目标Activity的实例,并将它添加到任务栈的栈顶中;

2)如果将要启动的目标Activity已经位于任务栈的栈顶,此时与singleTop模式相同,系统不会重新创建新的目标Activity的实例,而是直接复用已有的Activity实例;

3)如果将要启动的目标Activity已经存在,但没有位于任务栈的栈顶,那么此时系统将会把位于该Activity上面的所有Activity移出任务栈,从而使得目标Activity处于任务栈的栈顶。如

public class StandardActivity extends AppCompatActivity {

    private TextView tv_content;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_standard);
        tv_content = (TextView) findViewById(R.id.tv_content);
        tv_content.setText("当前Activity为:"+"\n"
                +this.toString()
                +"\n"+" TaskId为:"+this.getTaskId());
    }

    public void skip2StandardActivity(View view){
        Intent intent = new Intent(StandardActivity.this,StandardActivity.class);
        startActivity(intent);
    }
    public void skip2SingleTaskActivity(View view){
        Intent intent = new Intent(StandardActivity.this,SingleTaskActivity.class);
        startActivity(intent);
    }
}
SingleTaskActivity的代码如下所示:

public class SingleTaskActivity extends AppCompatActivity {

    private TextView tv_content;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_standard);
        tv_content = (TextView) findViewById(R.id.tv_content);
        tv_content.setText("当前Activity为:"+"\n"
                +this.toString()
                +"\n"+" TaskId为:"+this.getTaskId());
    }
    public void skip2StandardActivity(View view){
        Intent intent = new Intent(SingleTaskActivity.this,StandardActivity.class);
        startActivity(intent);
    }

    public void skip2SingleTaskActivity(View view){
        Intent intent = new Intent(SingleTaskActivity.this,SingleTaskActivity.class);
        startActivity(intent);
    }
}
当然,跟singleTop模式一相,同样需要在AndroidManifest清单文件中跟SingleTaskActivity添加launchMode属性并将其指定为singleTask,如下所示:

<activity android:name=".SingleTaskActivity"
    android:launchMode="singleTask"/>
程序运行后,在StandardActivSity中点击SingleTaskActivity按钮跳转到SingleTaskActivity时会创建一个SingleTaskActivity的实例,此时点击StandardActivity按钮跳转到StandardActivity中,在多次点击StandardActivity后,再点击SingleTaskActivity按钮会发现,此时并没有创建新的SingleTaskActivity的实例,并且原先位于SingleTaskActivity实例上面的所有StandardActivity的实例都消失了。



4singleInstance模式

singleInstance这种加载模式可以保证系统无论从哪个任务栈中启动目标Activity,都只会创建一个目标Activity的实例,并会使用一个全新的任务栈来装载该Activity的实例。

当以singleInstance模式启动目标Activity时,可分为如下两种情况:

1)如果将要启动的目标Activity不存在,系统会先创建一个全新的任务栈,再创建目标Activity的实例,并将它加入到新的任务栈的栈顶;

2)如果将要启动的目标Activity已经存在,无论它位于哪个任务栈中,系统都将会把该Activity所在的任务栈转到最前面,从而使该Activity显示出来。

需要注意的是采用singleInstance模式加载的Activity总是位于任务栈的栈顶,并且加载的Activity所在的任务栈中只包含该Activity。代码如下所示:

public class StandardActivity extends AppCompatActivity {

    private TextView tv_content;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_standard);
        tv_content = (TextView) findViewById(R.id.tv_content);
        tv_content.setText("当前Activity为:"+"\n"
                +this.toString()
                +"\n"+" TaskId为:"+this.getTaskId());
    }

    public void skip2StandardActivity(View view){
        Intent intent = new Intent(StandardActivity.this,StandardActivity.class);
        startActivity(intent);
    }
    public void skip2SingleInstanceActivity(View view){
        Intent intent = new Intent(StandardActivity.this,SingleInstanceActivity.class);
        startActivity(intent);
    }
}
SingleInstanceActivity的代码如下所示:

<span style="font-size:12px;">public class SingleInstanceActivity extends AppCompatActivity {

    private TextView tv_content;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_standard);
        tv_content = (TextView) findViewById(R.id.tv_content);
        tv_content.setText("当前Activity为:"+"\n"
                +this.toString()
                +"\n"+" TaskId为:"+this.getTaskId());
    }
    public void skip2StandardActivity(View view){
        Intent intent = new Intent(SingleInstanceActivity.this,StandardActivity.class);
        startActivity(intent);
    }
    public void skip2SingleInstanceActivity(View view){
        Intent intent = new Intent(SingleInstanceActivity.this,SingleInstanceActivity.class);
        startActivity(intent);
    }
}</span><span style="font-size:14px;">
</span>
AndroidManifest清单文件中给SingleInstanceActivity添加launchMode属性并将其指定为singleInstance,代码如下所示:

<activity android:name=".SingleInstanceActivity"
    android:launchMode="singleInstance"/>
运行程序可以看出,当在StandardActivity中点击SingleInstanceActivity按钮时,如果SingleInstanceActivity实例不存在,则会先创建一个SingleInstaceActivity的实例,在SingleInstaceActivity中点击StandardActivity按钮回到StandardActivity后,连续点击几次StandardActivity按钮进行跳转后再点击SingleInstanceActivity按钮会发现SingleInstanceActivity并没有再次创建新的实例。同样的,在控制台通过:adb shell dumpsys activity命令可以查看Task栈的情况,从结果中可以发现点击SingleInstanceActivity按钮时新创建了一个任务栈并将新创建的SingleInstanceActivity的实例添加到了该任务栈中。



此时,关于Activity的启动模式就算是已经讲解完了。

 源码

作者:u010933680 发表于2016/8/21 18:10:54 原文链接
阅读:38 评论:0 查看评论

剖析 Android 属性动画,知其然知其所以然(上)

$
0
0

转载请注明出处:http://blog.csdn.net/My_TrueLove/article/details/52262935
访问 ruicb.com,一键抵达我的博客!

注:本文偏度较长,越往后越精彩,请务必从头开始阅读,循序渐进。

有关 Android 动画的发展已经老生常谈了,我总结一下从别处学来的,作为开头。我们知道在 Android 3.0 之前只有逐帧动画(Frame Animation)和补间动画(View Animation)。逐帧动类似于我们常见的GIF,将多张图片按照一定的顺序、时间间隔轮流播放,形成简单的动画效果,在有些场景下还是比较实用的;补间动画是Android提供的针对View(及其子类)的动画操作类,包含平移(translation)、旋转(rotation)、缩放(scale)、透明度变换(alpha)这四个操作。

在3.0之后呢,Google为Android添加了属性动画(Property Animation),致力于为开发者提供更好的动画解决方案。有别于补间动画的是,属性动画已不再针对View设计,而是更偏向于对值进行动画变换,可以操作任何对象的任意属性,这一点先记住,后面会讲到,因为本文的猪脚就是属性动画!(你以为的错别字,其实不是错别字,下同)

1. 属性动画 VS 补间动画

既然有了补间动画,为什么Google还要设计属性动画呢,原因很明显。最明显的就是属性动画解决了补间动画移动后点击时间留在原位的问题,其次就是属性动画扩展性高,不再局限于补间动画的那四种变换。总之,相比于补间动画,我更推荐易用、实用性高的属性动画,接下来,就一起来全面认识一下属性动画吧。

2. API 一览

我打算在介绍之前,先抛出我们接下来要介绍的内容,这样你带着这些东西去读,会更有目的性,也更加容易融会贯通。注意,以下有关作用的描述翻译自源码中的注释文档,因为我觉得只有官方的才是最权威的,天朝一大抄的氛围,已经让我不敢轻易相信有些东西了。翻译不到位的,还请谅解。同时还有许多没有提及的,所以建议读者自己去查看,体会一下

2.1 abstract class Animator

继承关系:无父类(除Object)
作用:是所有提供基础动画支持的类的超类,这些动画应当可以被开始、结束,并可以添加动画监听。
补充:知道就行了

2.2 class ValueAnimator

继承关系:ValueAnimator extends Animator
作用:本类为运行动画提供了一个简单的计时引擎(timing engine),用来计算动画完成值(animated values)并设置到目标对象上。
补充:可以看出,其仅针对Value(值)进行动画变换,但它却是属性动画的核心类,后面要介绍的 ObjectAnimatorValueAnimator 基础上进行的二次封装。

2.3 final class ObjectAnimator

继承关系:ObjectAnimator extends ValueAnimator
作用:本类为 ValueAnimator 的子类,支持对目标对象的属性进行动画(animating properties on target objects)。这个类的构造函数,通过参数定义需要进行动画操作的目标对象,以及动画所针对的属性名称。确定对象内部存在合适的 gettersetter 方法,然后动画将在必要时调用这些方法以对属性进行动画。
其他:该类使用 final 修饰,无法再被其他类继承;其直接对目标对象的具体属性进行动画操作,前提是该属性存在相应的 settergetter 方法。

2.4 AnimatorSet extends Animator

继承关系:AnimatorSet extends Animator
作用:本类可以在特定的顺序下播放一组 Animator 对象动画(plays a set of Animator objects in the specified order)。动画可以被设置为同时播放、异步播放或者在特定的延时之后播放。
其他:AnimatorSet 操作的对象为 AnimatorValueAnimatorObjectAnimator 都是 Animator 的子类,所以都可以借助 AnimatorSet 实现组合动画。

有了以上几个类(实际常用的是 ObjectAnimator 和 AnimatorSet),我们就可以使用属性动画基本的功能实现大多数动画效果啦,想想都很激动,下面就开始正式介绍基础篇的内容。

3. 基础部分

下面将重点介绍几个类的用法,由于在 API 一览中已经对各个类进行了简单的介绍,所以接下来就直奔主题,上代码。

3.1 ValueAnimator

其为属性动画的核心类,但实际使用中,我们一般很少与其直接打交道,但不代表我们不需要了解他的用法。一般情况下,我们都会使用其提供的静态方法 ofXXX() 实现相应的值动画,包含以下几个方法:

ValueAnimator - ofXXX

下面重点讲解一下 ValueAnimator.ofFloat(float... values) 的用法:

1. 基本用法

实现在100ms内,将一个值从0过渡到1,并打印出来:

ValueAnimator - ofFloat

打印如下,默认刷新频率为10ms,但实际频率视实际情况有所波动:

打印

OK,成功的将值打印出来了,是不是很简单。但是细心地你会发现,值得变化并不是均匀的,明显感觉值是先加速增长,后减速增长。确实是这样,对属性动画有一定了解的或许知道插值器(interpolator)这个东西,不知道的也没关系,下篇会介绍。在这儿你只需知道,属性动画默认使用了先加速后减速的插值器(注释文档:The default value is AccelerateDecelerateInterpolator)

2. 更多方法

除了为 ValueAnimator 设置动画时常、添加更新监听外,我们还可以为 ValueAnimator 设置动画重复的次数、重复模式、以及添加其它监听。详见下图:

其他监听方法

3. 参数介绍

ofFloat(float... values) 方法接收可变参数,意味着我们可以传一个甚至多个参数。但是,根据下图可知,官方建议我们传两个以上参数。

官方实现

但我们依然可以传一个参数,并且成正常运行。因为此时系统会默认将我们传入的唯一参数作为结束值,而开始值则默认为 0,我们通过源码验证一下。跟踪源码,会进入KeyframeSet 类的 ofFloat(float... values) 方法,其中包含下面一段代码(有删减,并添加注释):

参数处理

源码之下,了无秘密,要我说最好的文档还是官方源码和注释文档!

传入一个参数、两个参数我们都知道了,那么n个值(n>2)呢?其效果就等价于 n-1 个平均耗时 duraction/(n-1) 的动画连续进行平滑过渡。具体的就不再细说,感兴趣的可以自己打印值看看结果。

除了 ofFloat(float... values) 方法,还有类似的 ofInt(int... values)ofArgb(int... values),使用方式基本一样,就不再一一介绍。至于ofObject(TypeEvaluator evaluator, Object... values)ofPropertyValuesHolder(PropertyValuesHolder... values) ,将留在进阶篇介绍,暂时不影响我们学习后续内容。

3.2 ObjectAnimator

ValueAnimator 介绍的篇幅较多,实际上是在为 ObjectAnimator 减轻压力,因为 ObjectAnimatorValueAnimator 子类,ValueAnimator 的方法在 ObjectAnimator 中同样适用。下面着重介绍 ObjectAnimator 所特有的且常用的方法,在 ValueAnimator 中已介绍的方法便不再赘述。但其 ofXXX() 方法较多,而且有许多重载的,不可能都介绍,依然挑常用的讲,大家要学会举一反三。

3.2.1 使用 ObjectAnimator 实现补间动画的效果

1. 平移

3s内,水平方向上向右平移300像素,然后逆向重复

ObjectAnimator - translate

效果如下:
ObjectAnimator - translate

2. 旋转

注意看图中注释。

ObjectAnimator - 旋转

现在,分别尝试不同的旋转参考轴,看一下效果。

rotation:
ObjectAnimator - rotation
rotationX:
ObjectAnimator - rotationX
rotationY:
ObjectAnimator - rotationY

3. 透明度

透明度

效果如下:
ObjectAnimator - alpha

4. 缩放

缩放

效果如下:
ObjectAnimator - scale

3.2.2 说一说参数问题

以上借助 ObjectAnimator 实现了补间动画的所有效果,且都是借助 ObjectAnimator.ofFloat(Object target, String propertyName, float... values) 方法,这也是我们常用的。至于其他方法,大家可以自己动手试一下,就不再一一列举,用法都差不多,在进阶篇中还会再提及部分方法的使用。继续说 ofFloat(Object target, String propertyName, float... values) 方法,其第一个和第三个参数都好理解,唯一让人困惑的是第二个参数是 String 类型,到底应该传入什么?看着好像是直接操作属性啊,没错,属性动画当然是操作属性。可是我们进入View 类中并没有发现这个属性,这就奇怪了…

我想,你大概是忘记了在文章开头介绍 ObjectAnimator 时,提到的 settergetter 方法了吧?ObjectAnimator 真正作用的是 方法,而不是直接操纵属性。拿 scaleX 举例,其对应的 gettersetter 方法就是 getScaleXsetScaleX,不信你可以在 View 中查找一下试试。同理,对应的还有 setTranslationX | getTransLationsetAlpha | getAlpha 等。更多动画操作等着你去发现,希望大家能够灵活运用其中的方法,这样才能做出炫酷吊炸天的效果。

3.2.3 填坑

记得文章开头强调过:属性动画已不再针对View设计,而是更偏向于对值进行动画变换,可以操作任何对象的任意属性

ValueAnimator 只是针对值进行动画,说其不针对 View 毫不违和。那么 ObjectAnimator 又怎么体现呢?其实,从 ObjectAnimator.ofXXX(...) 方法的参数我们就可以看出来,所有要求传入的目标对象 target 均没有限定为 View 类型,反而是 Object 或者 泛型T。我想这样说还是不太好证实,下面,举个栗子:

定义一个普通的汽车类 Car(该类不是 View类的子类),然后通过 speedAnim() 方法,对 Car 对象进行动画操作,模拟汽车加速的过程:

Car

首先,我没没有在代码中显示的调用 setSpeed(float speed) 方法,但控制台却打印出了Log(下图),再一次证明了 ObjectAnimator 作用于 settergetter 方法。

当然,我们关注的重点还是是否实现了动画,那么看一下打印的 Log:

ObjectAnimator - Car-log

借助Log显示的速度值,显示了汽车匀加速的过程(由左侧刷新时间间隔可知,动画刷新频率有波动,导致速度的变化看上去并不是匀加速,但实际上是匀加速过程),也算是一个动画吧?要知道这个是补间动画所无法实现的。

此处为了演示我只是将速度打印出来,其实我们可以利用这个 speed 值做一些有意义的事,我就不再延伸了。再次强调一下,ofFloat(...) 中传入的 Car类不是 View 的子类。OK,求证完毕!

相信到这儿,你应该明白属性动画已不再针对View设计,以及 ObjectAnimator 是借助getter 和 setter 方法实现动画的说法了吧。

3.2.4 继续填坑

且慢,又有人说话了!根据上述代码及log,只能证明 ObjectAnimator 只是借助了 setter 方法,哪有 getter 方法的事?很明显 getSpeed() 方法里面的 log 都没打印!额,好像是啊,差点被我蒙混过关,幸好被你发现了,既然这样,继续填坑吧圆场吧。

接下来,我们修改上述第95行代码为:

ObjectAnimator speedAnim = ObjectAnimator.ofFloat(car, "speed", 20f);

改动很小,去掉了一个参数”0f”,再运行打印一次log:

ObjectAnimator - Car-log

上图红色框圈中的部分,是不是 getSpeed() 方法所打印的内容?看来确实起作用了,这时候我们顺其自然想到是因为少传了那个“0f”,看来姿势已经对了,但具体是为什么呢?

在第一遍模拟加速,以及在前面对 View 进行各种动画时,我们都在属性后面设置了两个以上的可变参数,但这一次是一个参数。这时候,我们又会联想到在介绍 ValueAnimator 时一个参数的情况,根据源码可知系统会默认开始值为0f,恰好Log里面打印的也是0,原来是这样!

但仔细一想不对,如果像 ValueAnimator 一样直接设默认值,而不是调用 getSpeed() 方法,为啥会有第一句的日志?原来,当我们使用 ObjectAnimator 且只传一个值时,ObjectAnimator 会主动去调用对象的 getter 方法获取开始值,而不是像 ValueAnimator 那样直接设默认值。所以,对于 ObjectAnimator 来说,getter 方法是视情况调用的,但 setter 方法却一定会被调用

3.2.5 延伸:通过源码了解ObjectAnimator如何使用 getter 和 setter 方法

本部分属于延伸内容,旨在通过源码了解 ObjectAnimator 是如何调用对象的 gettersetter 方法的,对源码不感冒的可以直接略过,但我还是建议大家看一下。为方便理解,我将源码提取出来进行了适当删减,并翻译、添加了注释,若有不对的,还望指正

ObjectAnimator 获取属性对应的 gettersetter 方法时,会分别调用 PropertyValuesHolder 类的如下方法:

源码分析-1

由上述代码可知,无论获取 getter 还是 setter , 都会走到 setupSetterOrGetter(...) 方法中,来获取对应的方法,代码如下。该方法引入了缓存机制,感兴趣的可以看一下注释,不再补充。

源码分析-2

观察上述代码,我们发现如果是第一次查找某个属性对应的方法,缓存肯定不存在,该怎么办?我们注意到上图红框圈中的部分,也就是当缓存不存在时,会调用 getPropertyFunction(targetClass, prefix, valueType) 方法从目标Class对象中直接获取方法,获取成功过后再加入缓存方便下次直接使用。获取属性对应方法的代码我们跟进去看一下,如下图。

源码分析 -3

图中注释已经很清楚了,再补充两点。

第一:在方法最后我们发现,如果我们传入属性之后,系统没有找到对应的 gettersetter 方法时,其并没有报错进而崩溃,只是打印了错误原因,这块大家可以自己尝试注释相应的 gettersetter 方法验证一下,我只提个醒。

第二:该方法的两个关键作用分别依靠 PropertyValuesHolder 类的 getMethodName(...)Class 类的 getMethod(...) 方法实现的,下面我们贴一下这两个方法的代码,大家感受一下:

源码分析 -4

[打广告:有关反射的知识,可以参考我之前写的两篇博客,点这儿]

现在思路是不是超级清晰?那就好!但是现在正在写博客的我,有点头疼,因为还有一个我没有解决的问题,我在这儿写出来,有知道的同学还望指导一下:

根据我的理解,在 getMethod(…) 方法中,如果当前要找的 getter 或 setter 方法是私有的,则会抛出异常,进而会在上一级方法中捕获,并最终打印错误日志。于是我将 getter 方法给位私有private的,确实像我分析的那样;但是,当我将 setter 方法设置为私有private时,却没有打印错误日志,动画还能正常运行!Why?

最后,总结一下有关 settergetter 的东西,其实就两点:

1 - 只要有对应的 setter 方法存在(即使没有传入初始值、没有 getter 方法),动画就能正常运行;反之只要没有 setter 方法,动画就一定无法正常运行,因为没法将值赋给对应的变量。
2 - 注意图中圈中部分,没有传入初始值、没有 getter 方法,只有 setter 方法,根据第1点动画正常运行。可为什么正常运行的同时还打印错误?因为在没有初始值时,系统会去找对应的 getter 方法,发现没有找到时,会打印错误日志。最后,在获取无果的情况下,系统将初始值赋为对应数据类型的默认值。

3.3 针对 ValueAnimator 和 ObjectAnimator 的一点补充

至此,大家对 ValueAnimatorObjectAnimator 有了大概的了解,ValueAnimator 针对值进行变换,而 ObjectAnimator 是对 ValueAnimator 的扩展,会自动调用任意对象的 gettersetter 方法对任何属性进行动画操作,重点就在于这个 gettersetter 方法,而且我们始终没有提及 View 对象,只是说任意对象的任意属性

其次,上面借助 ObjectAnimator 实现的汽车加速的栗子,可以使用 ValueAnimator 来实现,代码如下:

ValueAnimator实现ObjectAnimator的car

打印结果就不附图了,和之前完全一样。此处使用 ValueAnimator 实现汽车加速效果,其本质就是对值进行操作,然后将 ObjectAnimator 封装的动画细节抽出来自己实现了:在开始时通过 getSpeed() 拿到初始值,并在动画过程中通过监听获取实时的 speed 值,调然后用 setSpeed(float speed) 方法将值设置进去。其中精髓你体会到了吗。

我之所以加这个补充,就是想大家都可以再一次融会贯通,举一反三。其实,对 View 动画也可以使用 ValueAnimator 实现哦,你懂得!只不过一般我们不那么做(废话,哈哈)。

结束

基础篇差不多就到这儿了,还有使用 AnimatorSet 实现组合动画,以及在 XML 中使用组合动画,本打算在本篇讲的,然后下篇讲一下 TypeEvaluator 和 插值器TimeInterpolator,这样两篇结束。但随着研究的深入,发现要写的远比计划的多了去了,决定多写几篇,具体的后续会更新,争取在一周内更完。

因为属性动画算是比较大的一块,需要对整体有清楚的意识才能写好。尽管我已经很努力的在写,尽可能让大家更容易理解,但难免会出现思路和表达上的问题,或者遗漏了什么,所以恳请大家在阅读、学习的过程中,有什么问题、或者好的想法,能够反馈给我,我会及时完善更新!如果觉得本文对你有帮助,可以点赞表示一下哈哈!

作者:My_TrueLove 发表于2016/8/21 18:31:19 原文链接
阅读:67 评论:1 查看评论

Android Gatt连接流程源码分析之ClientIf注册

$
0
0

本文将重点描述Android蓝牙GATT连接的大致流程,不会过多地纠缠代码细节,只为了从架构上梳理清楚,为接下来深入研究底层机制奠定一个宏观认识。

首先建立GATT连接前,我们通常要扫描蓝牙设备,获得设备的BluetoothDevice对象,然后调用connectGatt去建立GATT连接并等待连接状态回调,接下来我们就开始分析这一过程,首先看看connectGatt的实现:

public BluetoothGatt connectGatt(Context context, boolean autoConnect, BluetoothGattCallback callback, int transport) {
    BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
    IBluetoothManager managerService = adapter.getBluetoothManager();
    try {
        IBluetoothGatt iGatt = managerService.getBluetoothGatt();
        if (iGatt == null) {
            // BLE is not supported
            return null;
        }
        BluetoothGatt gatt = new BluetoothGatt(context, iGatt, this, transport);
        gatt.connect(autoConnect, callback);
        return gatt;
    } catch (RemoteException e) {Log.e(TAG, "", e);}
    return null;
}

这里主要是获取IBluetoothGatt,Gatt相关操作是单独抽出来的,没有都塞到IBluetoothManager中,否则会让IBluetoothManager显得很臃肿,作为蓝牙总管IBluetoothManager还是简洁一些为好。这个IBluetoothGatt的真正实现在GattService中,不过在进入GattService之前,我们先看看这个BluetoothGatt的connect函数,这里为了突出重点略去了一些代码。

boolean connect(Boolean autoConnect, BluetoothGattCallback callback) {
    if (!registerApp(callback)) {
        return false;
    }
    return true;
}

这里只调用了registerApp,从字面意思上理解貌似与连接无关,只是注册一个调用方,我们看看其实现:

private boolean registerApp(BluetoothGattCallback callback) {
    mCallback = callback;
    UUID uuid = UUID.randomUUID();

    try {
        mService.registerClient(new ParcelUuid(uuid), mBluetoothGattCallback);
    } catch (RemoteException e) {
        return false;
    }

    return true;
}

这里给用户传进来的callback保存起来,生成了一个UUID作为调用方的标识,然后调用IBluetoothGatt的registerClient去注册,奇怪的是这里传入的是另外一个BluetoothGattCallback,这是个典型的静态代理,想必回调后还要做一些额外处理才会走到我们自己的callback。

private final IBluetoothGattCallback mBluetoothGattCallback = new BluetoothGattCallbackWrapper() {
    public void onClientRegistered(int status, int clientIf) {
        mClientIf = clientIf;
        if (status != GATT_SUCCESS) {
            mCallback.onConnectionStateChange(BluetoothGatt.this, GATT_FAILURE, BluetoothProfile.STATE_DISCONNECTED);
            return;
        }
        try {
            mService.clientConnect(mClientIf, mDevice.getAddress(), !mAutoConnect, mTransport);
        } catch (RemoteException e) {
            Log.e(TAG,"",e);
        }
    }

    ......
}

这个Callback是一个BluetoothGattCallbackWrapper对象,相当于在我们自己的callback基础上增加了一些别的接口,这些接口只是用于系统内部调用。这里的onClientRegistered就是新增的接口之一,也是我们上面调用registerClient之后的回调。这个回调会返回一个clientIf和status,如果status不是成功则直接返回失败,否则继续调用IBluetoothGatt的clientConnect去真正建立连接。

好了,到此为止我们清楚了Gatt连接是分为两步的,首先要获取一个ClientIf,然后再去连接。这两个操作的实现都是在GattService中,我们先看registerClient,如下:

void registerClient(UUID uuid, IBluetoothGattCallback callback) {
    mClientMap.add(uuid, callback, this);
    gattClientRegisterAppNative(uuid.getLeastSignificantBits(), uuid.getMostSignificantBits());
}

这里给uuid和callback建立映射,等到需要回调的时候通过uuid就可以找到callback了。再来看看gattClientRegisterAppNative的实现,是在com_android_bluetooth_gatt.cpp中,如下:

static void gattClientRegisterAppNative(JNIEnv* env, jobject object, jlong app_uuid_lsb, jlong app_uuid_msb )
{
    bt_uuid_t uuid;
    if (!sGattIf) return;
    set_uuid(uuid.uu, app_uuid_msb, app_uuid_lsb);
    sGattIf->client->register_client(&uuid);
}

这里sGattIf是在GattService启动的时候初始化的,对应的是一组Gatt操作的接口,包括初始化、清理、gatt client和server相关的接口。这里gatt连接作为client端调到了register_client,传入的是调用方的uuid。其实现是btif_gattc_register_app函数,如下:

static bt_status_t btif_gattc_register_app(bt_uuid_t *uuid) {
    btif_gattc_cb_t btif_cb;
    memcpy(&btif_cb.uuid, uuid, sizeof(bt_uuid_t));
    return btif_transfer_context(btgattc_handle_event, BTIF_GATTC_REGISTER_APP, (char*) &btif_cb, sizeof(btif_gattc_cb_t), NULL);
}

这里调到了btif_transfer_context,从字面上理解是改变上下文,其实际意义是切换运行线程到btif task。这里有两个问题,为什么要切换线程?如何切换线程?

首先看第一个问题,为什么要切换线程,因为调用方是跨进程调到GattService中的,所以会运行在GattService的Binder线程池中,这样就要考虑多线程同步的问题了,因为Gatt Native层实现中有大量的全局变量,多线程环境下肯定会出问题。这里有两种做法,要么到处上锁,要么切换运行线程。类似的在Java中,我们要么加synchronized,要么干脆给所有操作都post到统一的工作线程中。

再来看第二个问题,在Android Java中,我们要切换运行线程只要将逻辑封装到Runnable中然后Post到目标线程的消息队列即可。而这里是Native层该怎么做呢?其实核心思想大致相同,线程中有一个消息队列,我们将消息和操作封装成一个msg,丢到该消息队列中,再将线程唤醒去取消息执行即可。虽然说起来简单,做起来可比Java复杂得多,只是Java中很多底层细节都封装得很好了,我们上层不用再考虑而已。

在蓝牙模块初始化的时候,native层会启动两个线程,一个是btif task,另一个是btu task。上层下来的所有调用都要先丢到btif task中,然后再看情况继续丢到btu task中处理。

我们回到btif_gattc_register_app这个函数,这里虽然切换了上下文,但是要做的事还是不变的,只是改头换面了一下,变成了一个BTIF_GATTC_REGISTER_APP消息和btgattc_handle_event函数。这个函数从字面意思上理解是处理各类事件的,想必里面就是switch case了,我们只关心BTIF_GATTC_REGISTER_APP事件,其处理函数为BTA_GATTC_AppRegister,如下:

void BTA_GATTC_AppRegister(tBT_UUID *p_app_uuid, tBTA_GATTC_CBACK *p_client_cb) {
    ......

    if ((p_buf = (tBTA_GATTC_API_REG *) GKI_getbuf(sizeof(tBTA_GATTC_API_REG))) != NULL)
    {
        p_buf->hdr.event    = BTA_GATTC_API_REG_EVT;
        if (p_app_uuid != NULL) {
            memcpy(&p_buf->app_uuid, p_app_uuid, sizeof(tBT_UUID));
        }
        p_buf->p_cback      = p_client_cb;

        bta_sys_sendmsg(p_buf);
    }
    return;
}

到这里真让人无语了,简单的一个注册就像踢皮球一样被丢来丢去,又被封装成消息发射出去了,这回是被丢到了另一个线程中,就是传说中的btu task。怎么丢的我们暂时不管,还是先搞清楚怎么注册才最要紧,经过了千辛万苦终于到了真正的注册环节,就是bta_gattc_register函数了,如下:

void bta_gattc_register(tBTA_GATTC_CB *p_cb, tBTA_GATTC_DATA *p_data) {
    tBTA_GATTC               cb_data;
    memset(&cb_data, 0, sizeof(cb_data));
    cb_data.reg_oper.status = BTA_GATT_NO_RESOURCES;

    for (i = 0; i < BTA_GATTC_CL_MAX; i ++) {
        if (!p_cb->cl_rcb[i].in_use) {
            if ((p_cb->cl_rcb[i].client_if = GATT_Register(p_app_uuid, &bta_gattc_cl_cback)) == 0) {
                status = BTA_GATT_ERROR;
            } else {
                p_cb->cl_rcb[i].in_use = TRUE;
                p_cb->cl_rcb[i].p_cback = p_data->api_reg.p_cback;
                memcpy(&p_cb->cl_rcb[i].app_uuid, p_app_uuid, sizeof(tBT_UUID));

                /* BTA use the same client interface as BTE GATT statck */
                cb_data.reg_oper.client_if = p_cb->cl_rcb[i].client_if;

                if ((p_buf = (tBTA_GATTC_INT_START_IF *) GKI_getbuf(sizeof(tBTA_GATTC_INT_START_IF))) != NULL) {
                    p_buf->hdr.event    = BTA_GATTC_INT_START_IF_EVT;
                    p_buf->client_if    = p_cb->cl_rcb[i].client_if;

                    bta_sys_sendmsg(p_buf);
                    status = BTA_GATT_OK;
                } else {
                    GATT_Deregister(p_cb->cl_rcb[i].client_if);
                    status = BTA_GATT_NO_RESOURCES;
                    memset( &p_cb->cl_rcb[i], 0 , sizeof(tBTA_GATTC_RCB));
                }
                break;
            }
        }
    }

    if (p_data->api_reg.p_cback) {
        if (p_app_uuid != NULL) {
            memcpy(&(cb_data.reg_oper.app_uuid), p_app_uuid,sizeof(tBT_UUID));
        }
        cb_data.reg_oper.status = status;
        (*p_data->api_reg.p_cback)(BTA_GATTC_REG_EVT,  (tBTA_GATTC *)&cb_data);
    }
}

这个函数稍微有点长,不过逻辑很简单,就是在一个for循环中遍历看是否有可用的clientif,如果没有就返回BTA_GATT_NO_RESOURCES,值为128。遍历的时候发现某个槽没有人用就会去调用GATT_Register注册,注册成功就会返回一个clientIf,然后就要开始往java层回调了。在往回走之前,我们先看看GATT_Register的实现:

tGATT_IF GATT_Register (tBT_UUID *p_app_uuid128, tGATT_CBACK *p_cb_info)
{
    tGATT_REG    *p_reg;
    UINT8        i_gatt_if=0;
    tGATT_IF     gatt_if=0;

    for (i_gatt_if = 0, p_reg = gatt_cb.cl_rcb; i_gatt_if < GATT_MAX_APPS; i_gatt_if++, p_reg++)
    {
        if (!p_reg->in_use)
        {
            memset(p_reg, 0 , sizeof(tGATT_REG));
            i_gatt_if++;              /* one based number */
            p_reg->app_uuid128 =  *p_app_uuid128;
            gatt_if            =
            p_reg->gatt_if     = (tGATT_IF)i_gatt_if;
            p_reg->app_cb      = *p_cb_info;
            p_reg->in_use      = TRUE;

            break;
        }
    }
    return gatt_if;
}

这里逻辑很简单,就是看哪个槽没有被人占用,不过注意的是这个槽和上面的槽是不同的,上面的槽是bta_gattc_cb中的cl_rcb,这的槽是gatt_cb的cl_rcb。不过两个槽大小都一样,都是32。这样我们了解到clientIf是有数量限制的,而且是系统全局的,而不是单个APP进程内的限制。每次用完之后要及时释放,否则别的人就没法再用了。

好了接下来我们踏上归途了,看看拿到这个clientIf之后是怎么回调回上层的。回到bta_gattc_register函数中,回调是从这一句开始的

(*p_data->api_reg.p_cback)(BTA_GATTC_REG_EVT,  (tBTA_GATTC *)&cb_data);

这个p_cback是什么呢,注册的时候被封装成消息转手了无数次,但还是给揪出来了,这个指针指向的是bta_gattc_cback,如下:

static void bta_gattc_cback(tBTA_GATTC_EVT event, tBTA_GATTC *p_data) {
    bt_status_t status = btif_transfer_context(btif_gattc_upstreams_evt,
                    (uint16_t) event, (void*) p_data, sizeof(tBTA_GATTC), btapp_gattc_req_data);
}

这里可以理解,毕竟来的路上是怎么切过来的,回去的时候就得怎么切回去。之前是先切到btif task,再切到btu task,现在要从btu task切回到btif task了,回调是btif_gattc_upstreams_evt,事件是BTA_GATTC_REG_EVT,如下:

static void btif_gattc_upstreams_evt(uint16_t event, char* p_param)
{
    tBTA_GATTC *p_data = (tBTA_GATTC*) p_param;
    switch (event)
    {
        case BTA_GATTC_REG_EVT:
        {
            bt_uuid_t app_uuid;
            bta_to_btif_uuid(&app_uuid, &p_data->reg_oper.app_uuid);
            bt_gatt_callbacks->client->register_client_cb(p_data->reg_oper.status, p_data->reg_oper.client_if, &app_uuid);
            break;
        }
        ......
    }
}

这里的register_client_cb最终指向的是btgattc_register_app_cb函数,这已经回到了gatt service的native中了,如下:

void btgattc_register_app_cb(int status, int clientIf, bt_uuid_t *app_uuid)
{
    sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onClientRegistered, status,
        clientIf, UUID_PARAMS(app_uuid));
}

这里调用到了Java层的onClientRegistered函数,返回的正是clientIf和uuid。至此,整个clientIf的注册流程终于走通了,虽然中间有很多代码细节我们没有深究,不过那都不重要了,有了对总体的把握,以后遇到具体问题再细看也不迟。而且代码细节很可能在以后Android升级时有重大改动,但总体思想和大致流程基本不会变的。

总结一下调用流程,App发起的Gatt连接请求被丢到GattService中,分解为两步走,第一步是注册ClientIf,注册成功后再拿着ClientIf建立真正的连接。先看ClientIf的注册,会往下走到GattService的native层中,再往下走到BlueDroid层,注册ClientIf完之后,会回到GattService的native层,再回调到GattService java层,这时如果ClientIf是注册成功的,则继续走Gatt连接流程,否则直接回调失败给用户。

下文我们将继续分析蓝牙真正的Gatt连接流程。

作者:dingjikerbo 发表于2016/8/21 19:32:33 原文链接
阅读:20 评论:0 查看评论

【小项目】简单天气预报项目的实现与流程

$
0
0

 一直有人问我说,一个项目拿到需求以后怎么去实现,还有一个app如何去构思,如何去下手,该从那里去写,这里我来简单的说下,这里我们拿一个最简单的天气预报来说明。

  

宏观上面来看一个项目怎么开发的:

1.首先拿到一个项目后,客户会给你很多他想要的功能和需求,这时候产品经理需要去分析和梳理用户的需求,他会把最终梳理好的需求不断的去和客户沟通确定,最终直到客户的需求明确下来;

2.接着产品经理会将明确下来的需求说给美工,美工进行原型图设计,期间会设计其主题风格,logo,产品流程图等,然后产品经理会再去和客户沟通确定原型图是否符合客户的审美风格,直到客户对所设计的页面等认同,之后美工会给开发人员切图等;

3.所有需求设计图会给项目经理,项目经理再一次细化所有功能需求,并会开会发布新项目的需求,之后会考虑项目的整体架构与研发周期,确定之后将任务下发给程序猿,在开发周期内完成后,在进行不断的项目测试,待测试完成后,与客户交付项目并准备上线,之后的就是维护更新维护更新。

  具体的流程还要看具体的公司,不过大致流程上是这样的,不同的角色考虑不同的任务。

 

程序猿的任务开始

  好了说了那么多了,我们来说说以天气预报为例子,作为一个新手如何来开发吧。

 

首先注意如下几点:

 

1.统一命名,统一书写

  首先新建项目,项目的名字和规定的包名一般公司都是有规定的,如果没有特定的要求,一般都是以“ 域名.公司名.App名字 ”来命名的,当然如果只有你一个人开发的话你可以随意,但是如果有两个人以上时候就需要统一包名了。

  其次统一确定所有的Activity,控件ID,xml布局等命名,不统一的后果会很蛋疼,具体给出一篇博文来参考,仅供参考,你也可以自己来制定:

http://blog.csdn.net/vipzjyno1/article/details/23542617

  除了程序猿之间的命名方式,你还需要要求下美工给你切的图片的名字,明确意思就好。

 

2.选择好的架构

  现在市面上有很多不错的架构,你可以和你的技术团队来选择相应的架构来完成你的项目,但是切记一定要符合,不要随意否则有可能大才小用,很简单的项目使用了很复杂的架构来实现,一定要符合项目,常用的有MCV,MVP

 

3.网络接口(api)的制定

  接口的意思就是说你通过不同的网络请求(常用的get/post)向后台服务器端请求数据,服务器响应后返回给你一些你想要的数据,常用的数据格式有xmljson(解析数据必须要会),这个数据的格式最好来一起制定下;

  还有一种情况可能直接在你开发的时候,后台就给你提供好了数据接口,这时候一定要熟悉开发时候给你的字段,或是哪里的数据不是很好找后台同事去协商处理。

 

4.选择合适你的第三方框架

  很多项目中用到了第三方框架(简单说框架别人封装的一些库,便利开发,或有你想要的服务,例如百度地图api),选择正确的框架还有合适的框架,例如图片框架有些是专注于滑动加载图片的,有的是专注于高质量显示图片的,至于哪个适合你,先了解下再去使用。

 

5.加密保证你的app安全

  现在app的安全越来越值得关注,所以加密的app是至关重要的,几个辛辛苦苦开发的app也许别人只用几秒就KO你的app拿到你的代码了,所以加密至关重要,常用的加密技术有混淆代码,dex加密,第三方加密等,这个具体也要看你的项目,如果有关于政府方面的只能使用混淆等非第三方加密技术,如果公司允许且不涉及政府等项目时候可以使用第三方加密来保证app的安全,常用的有360加固,爱加密;反编译工具有:dex2jarJD-GUI

 

  好了开始来说说天气预报这个简单的小项目吧,也给新人们一个好的思路;

 

 

开始项目

首先项目名,包名等命名假设已经制定好,本项目使用了MVC传统模式进行开发,这样我认为更好理解。

 

SDK最低指定的是4.0  编译版本为7.0

 

需求与原型图:

设计一个天气预报app,显示地区,温度,风力,显示天气信息以及其对应图标,当请求失败时候提示用户失败信息,断网时候提示用户重新连接网络;

原型图如下:


分别为加载完以及未加载状态

 

首先分包如下:

 

 

   如果分包的目录和这种分包不一样可以如下操作

 

  说下分包:

App的入口类是MainActivity我一般不在里面做复杂业务,可以加入闪屏动画处理,其中有些包你可能开发时候根本就用不到,但是最好声明出来,可能在日后用到;

 

Activity:

存放所有的activity

Adapter:

存放所有的适配器

Base:

存放所有的基类,封装所有公共的特性

Bean:

存放所有的实体类

Constant:

存放所有的常量

Net:

存放所有有关网络请求的操作

Utils:

存放所有的工具类

Widget:

存放所有的自定义组件

 

  

编写的思路就是顺序

 

Xml页面 ---------------请求数据------------------显示在页面上

 

写好布局文件以及绑定好控件ID -----编写网络请求工具类--------编写网络请求动作并且解析json数据-----将解析好的json数据显示到xml

 

这里直来讲核心的内容,首先前面的xml编写以及id的绑定就不再阐述,来说下网络以及xml数据获取上面,首先这里我找了一个第三方开源的接口,直接用即可,其链接如下:

 

网络请求 天气预报主站的网址:

 

 http://www.k780.com/api/weather.future

 

天气预报主站给出的示例接口网址:

  http://api.k780.com:88/?app=weather.future&weaid=1&&appkey=10003&sign=b59bc3ef6191eb9f747dd4e83c99f2a4&format=json

 

其中我把网络请求分为网络请求类,网络请求方法类,json数据解析类,其主要代码如下:

 

<span style="white-space:pre">	</span>/**
	 * 解析天气信息工具类
	 * 
	 * @param handler
	 * @throws JSONException
	 */
	public static ArrayList<WeatherBean> parserJsonUtils(String result, Handler handler) throws JSONException {
		ArrayList<WeatherBean> list = new ArrayList<WeatherBean>();
		JSONObject object = new JSONObject(result);
		if (object.getString("success").equals("1")) {// 成功获取
			// 解析开始
			JSONArray res = object.getJSONArray("result");
			for (int i = 0; i < res.length(); i++) {
				WeatherBean bean = new WeatherBean();
				bean.setDays(res.getJSONObject(i).getString("days"));
				bean.setWeek(res.getJSONObject(i).getString("week"));
				bean.setCitynm(res.getJSONObject(i).getString("citynm"));
				bean.setTemperature(res.getJSONObject(i).getString("temperature"));
				bean.setWeather(res.getJSONObject(i).getString("weather"));
				bean.setWeather_icon(res.getJSONObject(i).getString("weather_icon"));
				bean.setWind(res.getJSONObject(i).getString("wind"));
				bean.setWinp(res.getJSONObject(i).getString("winp"));
				list.add(bean);
			}
			Message msg = handler.obtainMessage();
			msg.what = MsgConstant.SUCCESS;// 成功
			msg.obj = list;
			handler.sendMessage(msg);
		} else if (object.getString("success").equals("0")) {
			// 解析失败的原因并返回给WeatherActivity中的Handler
			Message msg = handler.obtainMessage();
			msg.what = MsgConstant.FAILED;// 失败
			msg.obj = object.getString("msg");
			handler.sendMessage(msg);
		} else {
			// 断网等情况
			Message msg = handler.obtainMessage();
			msg.what = MsgConstant.ERROR;// 错误未获取到json数据
			handler.sendMessage(msg);
		}
		return list;
	}
<span style="white-space:pre">		</span>/***
		 * 网络请求 
		 * 1.请求json数据 
		 * 2.解析到ArrayList中 
		 * 3.返回数据显示
		 */
		new Thread(new NetRequest()).start();</span>
<span style="white-space:pre">	</span>/**
	 * 网络请求类
	 * 异步请求
	 * @author WindyStory
	 *
	 */
	class NetRequest implements Runnable {
		@Override
		public void run() {
			try {
				//发起请求
				String json = NetUtils.NetUtil("http://api.k780.com:88/?app=weather.future&weaid=1&&appkey=10003&sign=b59bc3ef6191eb9f747dd4e83c99f2a4&format=json");
				JsonUtils.parserJsonUtils(json, handler);
			} catch (JSONException e) {
				e.printStackTrace();
				// 断网等情况
				Message msg = handler.obtainMessage();
				msg.what = MsgConstant.ERROR;// 错误未获取到json数据
				handler.sendMessage(msg);
			}
		}

	}



 

因为在主线程上不能做耗时操作,所以这里使用ThreadHandler来实现异步请求,首先在Thread中去获取数据,获取数据分为三种有网请求成功SUCCESS状态,有网请求失败FAILED状态(如参数传入不正确),以及无网络时候的ERROR状态,根据三种状态来实现三种不同的状态对应Handler的处理方式。

其中图片的显示,我使用了Glide请求框架,详见这个博文:

http://blog.csdn.net/u011539882/article/details/51954349

注意需要网络权限。

 

 

项目下载地址:

http://download.csdn.net/detail/u011539882/9611691

 

 

 

作者:u011539882 发表于2016/8/24 16:26:04 原文链接
阅读:36 评论:0 查看评论

什么是面向对象编程思想?

$
0
0
那些年,空气中仿佛还能闻到汉唐盛世的余韵,因此你决不允许自己的脸上有油光,时刻保持活力。然而,你一定曾为这些“高深术语”感到过困扰。也许时至今日,你仍对它们一知半解。不过就在今天,这一切都将彻底改变!我将带领你以一种全新的高清视角进入奇妙的编程世界,领略涵泳在这些“高深术语”中的活泼泼的地气,以及翩跹于青萍之末的云水禅心。


·内聚

内聚,通俗的来讲,就是自己的东西自己保管,自己的事情自己做。

经典理论告诉我们,程序的两大要素:一个是数据(data),一个是操作(opration)。而 PASCAL之父Nicklaus Wirth则进一步提出了“程序 = 数据结构 + 算法”的著名公式。虽然提法上有所差异,但是其根本内涵却是一致的,微妙的差别在于,“数据 + 操作”是微观的视域,“数据结构 + 算法”则是中观的视域。而在宏观的视域下,我认为“程序 = 对象 + 消息”。对象是什么?对象就是保管好自己的东西,做好自己的事情的程序模块——这就是内聚!传统的面向过程编程方法由于割裂了数据结构和算法,使得软件的内聚性普遍低迷,曾一度引发了软件危机。试想,大家都自己的东西不好好保管,自己的事情也不好好做,不引发危机才怪呢!当然,对象的内聚只是内聚的一个层次,在不同的尺度下其实都有内聚的要求,比如方法也要讲内聚,架构也要讲内聚。

《周易·彖传》中讲“乾道变化,各正性命,保合太和,乃利贞”,就是要求每一个个体因循着各自的禀赋而努力成就各自的品性,然后各自保全,彼此和合,最终达成宇宙的完满状态。《论语·宪问》中,子路问君子。子曰:“修己以敬。”曰:“如斯而已乎?”曰:“修己以安人”,更是明确的教导我们要不断提高自身的内聚性,最大限度地减少给他人造成的麻烦,从而达到安人、安百姓、安天下的目标。我想,成长的过程就是一个不断提升内聚的过程。“自己的东西自己保管,自己的事情自己做”,这些孩提时代的教诲,放到今天仍能让不少“大人”脸红不已。太多的人保管不好自己的“东西”,保管不好自己的身体,保管不好自己的婚姻,更保管不好自己如蛛丝般震颤飘荡的狂乱的心。至于做好自己的事情,则更是惘然,甚至很多人连自己的事情是什么都搞不清楚,因此浑浑噩噩,饱食终日。内聚,是一个值得我们好好反思的问题。


·依赖·耦合

在面向对象编程中,对象自身是内聚的,是保管好自己的数据,完成好自己的操作的,而对外界呈现出自己的状态和行为。但是,没有绝对的自力更生,对外开放也是必要的!一个对象,往往需要跟其他对象打交道,既包括获知其他对象的状态,也包括仰赖其他对象的行为,而一旦这样的事情发生时,我们便称该对象依赖于另一对象。只要两个对象之间存在一方依赖一方的关系,那么我们就称这两个对象之间存在耦合。 比如妈妈和baby,妈妈要随时关注baby的睡、醒、困、哭、尿等等状态,baby则要仰赖妈妈的喂奶、哄睡、换纸尿裤等行为,从程序的意义上说,二者互相依赖,因此也存在耦合。首先要说,耦合是必要的。我们来看以下这个实验。


【王阳明与山中之花

View Code

由于王阳明这个对象不依赖山花这个对象,又没有其他的方式来获知山花的盛开状态,所以他要么选择不说,要么瞎说,但不说编译是通不过,而瞎说作为王阳明来讲也是通不过的,所以这个系统是无法成立的。要想系统成立,必须要这样写:

   public bool AdmireFlowers()
        {
            return flower.IsBloomed; ; 
        }

无论这个山花对象是怎么来的,作为参数传入还是作为属性设置、还是在内部构造出来,总之,王阳明与山花之间发生了依赖,二者之间产生了耦合。 当然,这是一个很浅显的问题。有趣的是王阳明对此事的看法:“你未看花时,花与你同寂;你来看花,花于你则一时分明起来。可见心外无物!”王阳明讲的是对的!“心外无物”翻译技术语言是这样的:不存在耦合的两个对象必然拿不到对方的引用!


·耦合度·解耦和

耦合的程度就是耦合度,也就是双方依赖的程度。上文所说的妈妈和baby就是强耦合。而你跟快递小哥之间则是弱耦合。一般来说耦合度过高并不是一件好事。就拿作为IT精英的你来说吧,上级随时敦促你的工作进度,新手频繁地需要你指导问题,隔三差五还需要参加酒局饭局,然后还要天天看领导的脸色、关注老婆的心情,然后你还要关注代码中的bug 、bug、bug,和需求的变化、变化、变化,都够焦头烂额了,还猝不及防的要关注眼睛、颈椎、前列腺和头发的状态,然后你再炒个股,这些加起来大概就是个强耦合了。从某种意义上来说,耦合天生就与自由为敌,无论是其他对象依赖于你,还是你依赖其他对象。比如有人嗜烟、酗酒,你有多依赖它们就有多不自由;比如有人家里生了七八个娃,还有年迈的父母、岳父母,他们有多依赖你,你就有多不自由。所以老子这样讲:“五音令人耳聋,五色令人目盲,驰骋狩猎令人心发狂,难得之货令人行妨。”卢梭也是不无悲凉的说“人生而自由,却又无往而不在枷锁中”。因此,要想自由,就必须要降低耦合,而这个过程就叫做解耦和。

·依赖倒置(Dependence Inversion Principle)

解耦和最重要的原则就是依赖倒置原则:

高层模块不应该依赖底层模块,他们都应该依赖抽象。抽象不应该依赖于细节,细节应该依赖于抽象。

《资本论》中都曾阐释依赖倒转原则——在商品经济的萌芽时期,出现了物物交换。假设你要买一个IPhone,卖IPhone的老板让你拿一头猪跟他换,可是你并没有养猪,你只会编程。所以你找到一位养猪户,说给他做一个养猪的APP来换他一头猪,他说换猪可以,但是得用一条金项链来换——所以这里就出现了一连串的对象依赖,从而造成了严重的耦合灾难。解决这个问题的最好的办法就是,买卖双发都依赖于抽象——也就是货币——来进行交换,这样一来耦合度就大为降低了。

再举一个编程中的依赖倒置的例子。我们知道,在通信中,消息的收发和消息的处理往往密不可分。就一般的通信框架而言,消息的收发通常是已经实现了的,而消息的处理则是需要用户来自定义完成的。


先看一个正向依赖的例子:

tcpServerEngine是StriveEngine.dll提供通信引擎,它发布有一个MessageReceived事件。假设我定义了一个CustomizeHandler类来用于消息处理,那么CustomizeHandler的内部需要预定tcpServerEngine的MessageReceived事件,因此customizeHandler依赖于tcpServerEngine,这就是一个普通的依赖关系,也就是高层模块依赖于低层模块。



而  应用了依赖倒转原则。ESFramework定义了一个IcustomizeHandler接口,用户在进行消息处理时,实现该接口,然后将其注入到rapidPassiveEngine客户端通信引擎之中。



View Code

很明显,相比于上一个例子,这里的依赖关系变成了rapidPassiveEngine依赖于customizeHandler,也就是说依赖关系倒置了过来,上层模块不再依赖于底层模块,而是它们共同依赖于抽象。rapidPassiveEngine依赖的是IcustomizeHandler接口类型的参数,customizeHandler同样是以实现的接口的方式依赖于IcustomizeHandler——这就是一个依赖倒置的典范。

·控制反转(Inversion of Control)

控制反转跟依赖倒置是如出一辙的两个概念,当存在依赖倒置的时候往往也存在着控制反转。但是控制反转也有自己的独特内涵。

首先我们要区分两个角色,server 跟 Client,也就是服务方和客户方。提供服务端的一方称为服务方,请求服务的一方称为客户方。我们最熟悉的例子就是分布式应用的C/S架构,服务端和客户端。其实除此之外,C/S关系处处可见。比如在TCP/IP协议栈中,我们知道,每层协议为上一层提供服务,那么这里就是一个C/S关系。当我们使用开发框架时,开发框架就是作为服务方,而我们自己编写的业务应用就是客户方。当Client调用server时,这个叫做一般的控制;而当server调用Client时,就是我们所说的控制反转,同时我们也将这个调用称为“回调”。控制反转跟依赖倒置都是一种编程思想,依赖倒置着眼于调用的形式,而控制反转则着眼于程序流程的控制权。一般来说,程序的控制权属于server,而一旦控制权交到Client,就叫控制反转。比如你去下馆子,你是Client餐馆是server。你点菜,餐馆负责做菜,程序流程的控制权属于server;而如果你去自助餐厅,程序流程的控制权就转到Client了,也就是控制反转。



控制反转的思想体现在诸多领域。比如事件的发布/ 订阅就是一种控制反转,GOF设计模式中也多处体现了控制反转,比如典型的模板方法模式等。而开发框架则是控制反转思想应用的集中体现。比如之前所举的ESFramework通信框架的例子,通信引擎回调用户自定义的消息处理器,这就是一个控制反转。以及ESFramework回调用户自定义的群组关系和好友关系,回调用户自定义的用户管理器以管理在线用户相关状态,回调用户自定义的登陆验证处理,等等不一而足。再比如与ESFramework一脉相承的轻量级通信引擎StriveEngine,通过回调用户自定义的通信协议来实现更加灵活的通信。

由此我们也可以总结出开发框架与类库的区别:使用开发框架时,框架掌握程序流程的控制权,而使用类库时,则是应用程序掌握程序流程的控制权。或者说,使用框架时,程序的主循环位于框架中,而使用类库时,程序的主循环位于应用程序之中。框架会回调应用程序,而类库则不会回调应用程序。ESFramework和StriveEngine中最主要的对象都以engine来命名,我们也可以看出框架对于程序主循环的控制——它会为你把握方向、眼看前方、轻松驾驭!

·依赖注入(Dependency Injection)

  依赖注入与依赖倒置、控制反转的关系仍旧是一本万殊。依赖注入,就其广义而言,即是通过“注入”的方式,来获得依赖。我们知道,A对象依赖于B对象,等价于A对象内部存在对B对象的“调用”,而前提是A对象内部拿到了B对象的引用。B对象的引用的来源无非有以下几种:A对象内部创建(无论是作为字段还是作为临时变量)、构造器注入、属性注入、方法注入。后面三种方式统称为“依赖注入”,而第一种方式我也生造了一个名词,称为“依赖内生”,二者根本的差异即在于,我所依赖的对象的创建工作是否由我自己来完成。当然,这个是广义的依赖注入的概念,而我们一般不会这样来使用。我们通常使用的,是依赖注入的狭义的概念。不过,直接陈述其定义可能会过于诘屈聱牙,我们还是从具体的例子来看。




比如OMCS网络语音视频框架,它实现了多媒体设备(麦克风、摄像头、桌面、电子白板)的采集、编码、网络传送、解码、播放(或显示)等相关的一整套流程,可以快速地开发出视频聊天系统、视频会议系统、远程医疗系统、远程教育系统、网络监控系统等等基于网络多媒体的应用系统。然而,OMCS直接支持的是通用的语音视频设备,而在某些系统中,需要使用网络摄像头或者特殊的视频采集卡作为视频源,或者其它的声音采集设备作为音频源,OMCS则提供了扩展接口——用户自己实现这个扩展的接口,然后以“依赖注入”的方式将对象实例注入到OMCS中,从而完成对音、视频设备的扩展。

“依赖注入”常常用于扩展,尤其是在开发框架的设计中。从某种意义上来说,任何开发框架,天生都是不完整的应用程序。因此,一个优秀的开发框架,不仅要让开发者能够重用这些久经考验的的卓越的解决方案,也要让开发者能够向框架中插入自定义的业务逻辑,从而灵活自由地适应特定的业务场景的需要——也就是说要具备良好的可扩展性。比如上面提到的OMCS网络语音视频框架可应用于音、视频聊天系统、视频会议系统、远程医疗系统、远程教育系统、网络监控系统等等基于网络多媒体的应用系统;以及ESFramework通信框架能够应用于即时通讯系统,大型多人在线游戏、在线网页游戏、文件传送系统、数据采集系统、分布式OA系统等任何需要分布式通信的软件系统中——这种良好的扩展性都与“依赖注入”的使用密不可分!

·面向接口编程

谈到最后,“面向接口编程”已经是呼之欲出。无论是依赖倒置、控制反转、还是依赖注入,都已经蕴含着“面向接口编程”的思想。面向接口,就意味着面向抽象。作为哲学范畴而言,规定性少称为抽象,规定性多称为具体。而接口,就是程序中的一种典型的“抽象”的形式。面向抽象,就意味着面向事物的本质规定性,摆脱感性杂多的牵绊,从而把握住“必然”——而这本身就意味着自由,因为自由就是对必然的认识。

也许以上的这段论述太过“哲学”,但是“一本之理”与“万殊之理”本身就“体用不二”——总结来看,依赖倒置、控制反转、依赖注入都围绕着“解耦和”的问题,而同时自始至终又都是“面向接口编程”的方法——因此,“面向接口编程”天生就是“解耦和”的好办法。由此也印证了从“抽象”到“自由”的这一段范畴的辩证衍化。

“面向对象”与“面向接口”并非两种不同的方法学,“面向接口”其实是“面向对象”的内在要求,是其一部分内涵的集中表述。我们对于理想软件的期待常被概括为“高内聚,低耦合”,这也是整个现代软件开发方法学所追求的目标。面向对象方法学作为现代软件开发方法学的代表,本身就蕴含着“高内聚,低耦合”的思想精髓,从这个意义上来说,“面向对象”这个表述更加侧重于“高内聚”,“面向接口”的表述则更加侧重于“低耦合”——不过是同一事物的不同侧面罢了。

除此之外,我们也能从“面向接口编程”的思想中得到“世俗”的启迪——《论语》里面讲,不患无位,患所以立;不患人之不己知,患其不能也——就是教导我们要面向“我有没有的本事?”、“我有没有能力?”这样的接口,而不是面向“我有没有搞到位子?”、“别人了不了解我?”这样的具体。依我看,这是莫大的教诲!




作者:u011694328 发表于2016/8/24 16:33:31 原文链接
阅读:51 评论:0 查看评论

这一次,我优化了37%的内存

$
0
0

话说,从mta上报的数据上来看,我们的app出现了3起OOM(out of memery):

java.lang.Throwable: java.lang.OutOfMemoryError
	at com.tencent.stat.a.d.(Unknown Source)
	at com.tencent.stat.g.uncaughtException(Unknown Source)
	at java.lang.ThreadGroup.uncaughtException(ThreadGroup.java:693)
	at java.lang.ThreadGroup.uncaughtException(ThreadGroup.java:690)
	at dalvik.system.NativeStart.main(Native Method)

 从错误堆栈是显然是看不出任何问题的,那么问题会出现在什么地方呢?

我们猜测了一下几种可能?

1、存在内存泄露。

2、不需要的bitmap没有被释放。

那么问题是否就是上面这两种情况呢?

带着这个问题,我们首先验证下,内存是否占用真的很大。

首先,开启android studio内存占用图标展示工具,可以看到内存占用77M,

重启应用,各页面点击一下,相关路径踩一踩,不踩不知道,一踩就吓一跳,启动的时候,直观的从图中可以看到,内存占用40M,然而,切换到发现tab,发现内存彪到70M,然后,切换其他页面,如下图:

比如切换到,消息,我的,等等,发现内存依然,居高不下了,滑动发现页的时候,内存占用依然有标高的趋势。

我们使用 dumpsys meminfo com.xxx.xxx (app包名),查看内存占用情况如下图:

cat 一下进程,可以看到最大占用(这里包括虚拟机和原生)如下图:

不用说了,这个的优化,那么怎么办?

依然是寄出我们android studio上的内存分析工具,如下图:

首先,GC一下 ,然后在导出hprof文件,进行分析 。首先分析是否存在内存泄露:

如上图,并没有发现有内存泄露的activity存在,这也归功于我们平常有事没事都会随手分析一下是否有内存泄露,如果有,早就解决了,等不到我。

那么既然没有内存泄露的Activity,那么我们何不看一下对象的内存占用情况呢?

如是:如图

这里,我们很轻易的抓住了第一个凶手,bitmap。

其原因是因为这里做了一个打分的自定义view,这个view的分数是绘制出来的,之所以没有用字体,是因为引入一个字体库会增加包大小(虽然可以有些工具可以抽取仅仅需要的字体元素来缩小字体库,但我们考虑到实现一个打分效果的自定义view的成本也不大,因此并没有考虑抽取字体库),得不偿失,然而,这个自定义view中每个实例,都拥有0到9加上。的bitmap;

然而他被显示到列表的时候,可想而知,会有多少张图,优化起来相当简单,将这些bitmap使用静态变量保存,这样所有实例只会公用一份bitmap列表了:

,好吧,继续走查其他对象的内存占用,我们发现:

PopoFeed对象占用内存也较多:但,一层一层的剥下去,最终发现是PopoFeed对象中的User对象里面的UserTag占用内存较多:

可以想象,一个popofeed如果有20多条评论,10几个人打分,那这样就有30个User对象在popofeed对象中,30*3K,那就是90K,这些对view展示无用的数据吃内存也是非常恐怖的,所以,拉起后台同学,对返回的数据做了优化。同时,我们发现,返回的数据多,GSON转model所需要的内存也较多,所以,服务端对返回数据做清洗还是挺有必要的。

,好吧,到了第三阶段,我们继续走查,发现fragment占用内存较多,其实不难推测,使用fragmentManger管理fragment ,你看到的是一个页面,但其实上,默认是会加载1-2个到缓存中的,从源码中可以看出:

所以,你当前在发现页,实际上,大厅,消息都已经加载进内存了,那么这时候的做法就是在重写setUserVisibleHint

方法,当fragment可见的时候,将数据渲染到view上,当fragment不可见的时候,把view上数据清理掉,不过或许也有更好的方法,如果有,欢迎告诉我~。

然而,还有一个更加可恶的问题,那就是当fragment执行onDestroyView方法后,该fragment并没有释放掉内存,这也就是为什么切换到发现之后,在切换其他fragment内存居高不下的首要原因,我的解决办法是:

因为从引用树上看到:

findFragment被fragmentManger引用着,其在执行onDestroyView的时候,一些该释放的内存得不到释放,因此采取以上办法。

好吧,经过三个小小的优化,我们来看看,内存占用:

对比发现页:

发现页内存占用现在是 48M,同比之前的77M,减少了37%。

然后切换到我的

我们发现内存占用只有28.58M。

对于内存峰值方面的对比,

(165516-120792)/165516 = 27% 

总结这次的优化:

1、当内存中类的多个对象引用的资源不变的时候,请使用静态,这样,这些资源就只有一份,减少不必要的内存占用。

2、json转model是一个很耗时的过程,减少json中不必要的字段,不仅可以加快json转model的时间,还能降低内存占用。

3、fragment不可见的时候,实际上可能还占用着你的内存,要懂得小心释放不必要的内存。


腾讯自主研发,荣获2015年十佳组件第一名的“tMemoryMonitor”内存泄漏分析工具。该腾讯内部工具已经在腾讯WeTest官网内免费开放给用户使用

TMM下载地址http://wetest.qq.com/cloud/index.php/index/TMM

【工具简介】

tMemoryMonitor简称TMM是一款运行时C/C++内存泄漏检测工具。TMM认为在进程退出时,内存中没有被释放且没有指针指向的无主内存块即为内存泄漏,并进而引入垃圾回收机制,在进程退出时检测出堆内存中所有没有被引用的内存单元,因而内存泄漏检测准确率为100%

作者:u010944680 发表于2016/8/24 16:37:50 原文链接
阅读:58 评论:0 查看评论

安卓简单日历的实现

$
0
0
最近无聊在做一个项目,需要用到日历。日历并不需要太复杂,只需要能够简单的查看就行,所以我选用了java提供的Calendar类,和安卓的gridView组合的形式.
先看效果图:
背景什么的先不要管他。只看主体部分,左箭头查看上个月的,右箭头查看下个月的.
接下来开始我们的日历。
首先,需要使用Calendar自己构造一个DateUtils类,这个相当简单。
import java.util.Calendar;

/**
 * Created by 91905 on 2016/8/22 0022.
 */

public class DateUtils {
    /**
     * 获取当前年份
     *
     * @return
     */
    public static int getYear() {
        return Calendar.getInstance().get(Calendar.YEAR);
    }

    /**
     * 获取当前月份
     *
     * @return
     */
    public static int getMonth() {
        return Calendar.getInstance().get(Calendar.MONTH) + 1;
    }

    /**
     * 获取当前日期是该月的第几天
     *
     * @return
     */
    public static int getCurrentDayOfMonth() {
        return Calendar.getInstance().get(Calendar.DAY_OF_MONTH);
    }

    /**
     * 获取当前日期是该周的第几天
     *
     * @return
     */
    public static int getCurrentDayOfWeek() {
        return Calendar.getInstance().get(Calendar.DAY_OF_WEEK);
    }

    /**
     * 获取当前是几点
     */
    public static int getHour() {
        return Calendar.getInstance().get(Calendar.HOUR_OF_DAY);//二十四小时制
    }

    /**
     * 获取当前是几分
     *
     * @return
     */
    public static int getMinute() {
        return Calendar.getInstance().get(Calendar.MINUTE);
    }

    /**
     * 获取当前秒
     *
     * @return
     */
    public static int getSecond() {
        return Calendar.getInstance().get(Calendar.SECOND);
    }

    /**
     * 根据传入的年份和月份,获取当前月份的日历分布
     *
     * @param year
     * @param month
     * @return
     */
    public static int[][] getDayOfMonthFormat(int year, int month) {
        Calendar calendar = Calendar.getInstance();
        calendar.set(year, month - 1, 1);//设置时间为每月的第一天
        //设置日历格式数组,6行7列
        int days[][] = new int[6][7];
        //设置该月的第一天是周几
        int daysOfFirstWeek = calendar.get(Calendar.DAY_OF_WEEK);
        //设置本月有多少天
        int daysOfMonth = getDaysOfMonth(year, month);
        //设置上个月有多少天
        int daysOfLastMonth = getLastDaysOfMonth(year, month);
        int dayNum = 1;
        int nextDayNum = 1;
        //将日期格式填充数组
        for (int i = 0; i < days.length; i++) {
            for (int j = 0; j < days[i].length; j++) {
                if (i == 0 && j < daysOfFirstWeek - 1) {
                    days[i][j] = daysOfLastMonth - daysOfFirstWeek + 2 + j;
                } else if (dayNum <= daysOfMonth) {
                    days[i][j] = dayNum++;
                } else {
                    days[i][j] = nextDayNum++;
                }
            }
        }
        return days;
    }

    /**
     * 根据传入的年份和月份,判断上一个有多少天
     *
     * @param year
     * @param month
     * @return
     */
    public static int getLastDaysOfMonth(int year, int month) {
        int lastDaysOfMonth = 0;
        if (month == 1) {
            lastDaysOfMonth = getDaysOfMonth(year - 1, 12);
        } else {
            lastDaysOfMonth = getDaysOfMonth(year, month - 1);
        }
        return lastDaysOfMonth;
    }

    /**
     * 根据传入的年份和月份,判断当前月有多少天
     *
     * @param year
     * @param month
     * @return
     */
    public static int getDaysOfMonth(int year, int month) {
        switch (month) {
            case 1:
            case 3:
            case 5:
            case 7:
            case 8:
            case 10:
            case 12:
                return 31;
            case 2:
                if (isLeap(year)) {
                    return 29;
                } else {
                    return 28;
                }
            case 4:
            case 6:
            case 9:
            case 11:
                return 30;
        }
        return -1;
    }

    /**
     * 判断是否为闰年
     *
     * @param year
     * @return
     */
    public static boolean isLeap(int year) {
        if ((year % 4 == 0 && year % 100 != 0) || year % 400 == 0) {
            return true;
        }
        return false;
    }
}
其中DateUtils类中的
public static int[][] getDayOfMonthFormat(int year, int month) {
    Calendar calendar = Calendar.getInstance();
    calendar.set(year, month - 1, 1);//设置时间为每月的第一天
    //设置日历格式数组,67    int days[][] = new int[6][7];
    //设置该月的第一天是周几
    int daysOfFirstWeek = calendar.get(Calendar.DAY_OF_WEEK);
    //设置本月有多少天
    int daysOfMonth = getDaysOfMonth(year, month);
    //设置上个月有多少天
    int daysOfLastMonth = getLastDaysOfMonth(year, month);
    int dayNum = 1;
    int nextDayNum = 1;
    //将日期格式填充数组
    for (int i = 0; i < days.length; i++) {
        for (int j = 0; j < days[i].length; j++) {
            if (i == 0 && j < daysOfFirstWeek - 1) {
                days[i][j] = daysOfLastMonth - daysOfFirstWeek + 2 + j;
            } else if (dayNum <= daysOfMonth) {
                days[i][j] = dayNum++;
            } else {
                days[i][j] = nextDayNum++;
            }
        }
    }
    return days;
}
是将当前月份的日期分布保存到数组中返回.
接下来,布局文件
<LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content">

        <LinearLayout
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_gravity="center_vertical"
            android:layout_weight="1"
            android:gravity="center">

            <ImageView
                android:id="@+id/record_left"
                android:layout_width="40dp"
                android:layout_height="40dp"
                android:background="@drawable/record_left_press" />

        </LinearLayout>

        <RelativeLayout
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_gravity="center_vertical"
            android:layout_weight="2"
            android:gravity="center">

            <TextView
                android:id="@+id/record_title"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_centerHorizontal="true"
                android:layout_gravity="center"
                android:maxLines="1"
                android:text="2016年8月21日"
                android:textColor="@color/colorWhite"
                android:textSize="16sp" />
        </RelativeLayout>

        <LinearLayout
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_gravity="center_vertical"
            android:layout_weight="1"
            android:gravity="center">

            <ImageView
                android:id="@+id/record_right"
                android:layout_width="40dp"
                android:layout_height="40dp"
                android:background="@drawable/record_right_press" />
        </LinearLayout>
    </LinearLayout>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1"
        android:orientation="vertical">

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginLeft="20dp"
            android:layout_marginRight="20dp"
            android:layout_marginTop="10dp"
            android:orientation="horizontal">

            <LinearLayout
                android:layout_width="0dp"
                android:layout_height="wrap_content"
                android:layout_weight="1"
                android:gravity="center">

                <TextView
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:text="日"
                    android:textColor="@color/colorWhite"
                    android:textSize="16sp" />
            </LinearLayout>

            <LinearLayout
                android:layout_width="0dp"
                android:layout_height="wrap_content"
                android:layout_weight="1"
                android:gravity="center">

                <TextView
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:text="一"
                    android:textColor="@color/colorWhite"
                    android:textSize="16sp" />
            </LinearLayout>

            <LinearLayout
                android:layout_width="0dp"
                android:layout_height="wrap_content"
                android:layout_weight="1"
                android:gravity="center">

                <TextView
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:text="二"
                    android:textColor="@color/colorWhite"
                    android:textSize="16sp" />
            </LinearLayout>

            <LinearLayout
                android:layout_width="0dp"
                android:layout_height="wrap_content"
                android:layout_weight="1"
                android:gravity="center">

                <TextView
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:text="三"
                    android:textColor="@color/colorWhite"
                    android:textSize="16sp" />
            </LinearLayout>

            <LinearLayout
                android:layout_width="0dp"
                android:layout_height="wrap_content"
                android:layout_weight="1"
                android:gravity="center">

                <TextView
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:text="四"
                    android:textColor="@color/colorWhite"
                    android:textSize="16sp" />
            </LinearLayout>

            <LinearLayout
                android:layout_width="0dp"
                android:layout_height="wrap_content"
                android:layout_weight="1"
                android:gravity="center">

                <TextView
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:text="五"
                    android:textColor="@color/colorWhite"
                    android:textSize="16sp" />
            </LinearLayout>

            <LinearLayout
                android:layout_width="0dp"
                android:layout_height="wrap_content"
                android:layout_weight="1"
                android:gravity="center">

                <TextView
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:text="六"
                    android:textColor="@color/colorWhite"
                    android:textSize="16sp" />
            </LinearLayout>
        </LinearLayout>

        <GridView
            android:id="@+id/record_gridView"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:layout_margin="20dp"
            android:numColumns="7" />
    </LinearLayout>

GridView的item文件
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:gravity="center"
    android:orientation="horizontal">

    <TextView
        android:id="@+id/date_item"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textColor="@color/colorWhite"
        android:textSize="16sp" />
</LinearLayout>

GridView的Adapter文件
import android.content.Context;
import android.graphics.Color;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.TextView;

import com.jiaoml.smallmusic.MusicApp;
import com.jiaoml.smallmusic.R;
import com.jiaoml.smallmusic.music.Record;
import com.lidroid.xutils.db.sqlite.Selector;
import com.lidroid.xutils.db.sqlite.WhereBuilder;
import com.lidroid.xutils.exception.DbException;

/**
 * Created by 91905 on 2016/8/24 0024.
 */

public class DateAdapter extends BaseAdapter {
    private int[] days = new int[42];
    private Context context;
    private int year;
    private int month;

    public DateAdapter(Context context, int[][] days, int year, int month) {
        this.context = context;
        int dayNum = 0;
	//将二维数组转化为一维数组,方便使用
        for (int i = 0; i < days.length; i++) {
            for (int j = 0; j < days[i].length; j++) {
                this.days[dayNum] = days[i][j];
                dayNum++;
            }
        }
        this.year = year;
        this.month = month;
    }

    @Override
    public int getCount() {
        return days.length;
    }

    @Override
    public Object getItem(int i) {
        return days[i];
    }

    @Override
    public long getItemId(int i) {
        return i;
    }

    @Override
    public View getView(int i, View view, ViewGroup viewGroup) {
        ViewHolder viewHolder;
        if (view == null) {
            view = LayoutInflater.from(context).inflate(R.layout.date_item, null);
            viewHolder = new ViewHolder();
            viewHolder.date_item = (TextView) view.findViewById(R.id.date_item);
            view.setTag(viewHolder);
        } else {
            viewHolder = (ViewHolder) view.getTag();
        }
        if (i < 7 && days[i] > 20) {
            viewHolder.date_item.setTextColor(Color.rgb(204, 204, 204));//将上个月的和下个月的设置为灰色
        } else if (i > 20 && days[i] < 15) {
            viewHolder.date_item.setTextColor(Color.rgb(204, 204, 204));
        }
	viewHolder.date_item.setText(days[i] + "");

        return view;
    }

    /**
     * 优化Adapter
     */
    class ViewHolder {
        TextView date_item;
    }

接下来是Activity文件
import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.widget.GridView;
import android.widget.ImageView;
import android.widget.TextView;


import com.jiaoml.smallmusic.R;
import com.jiaoml.smallmusic.adapter.DateAdapter;

/**
 * Created by 91905 on 2016/8/24 0024.
 */

public class MainActivity extends Activity implements View.OnClickListener {
    private GridView record_gridView;//定义gridView
    private DateAdapter dateAdapter;//定义adapter
    private ImageView record_left;//左箭头
    private ImageView record_right;//右箭头
    private TextView record_title;//标题
    private int year;
    private int month;
    private String title;
    private int[][] days = new int[6][7];

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        //初始化日期数据
        initData();
        //初始化组件
        initView();
        //组件点击监听事件
        initEvent();
    }

    private void initData() {
        year = DateUtils.getYear();
        month = DateUtils.getMonth();
    }

    private void initEvent() {
        record_left.setOnClickListener(this);
        record_right.setOnClickListener(this);
    }

    private void initView() {
        /**
         * 以下是初始化GridView
         */
        record_gridView = (GridView) findViewById(R.id.record_gridView);
        days = DateUtils.getDayOfMonthFormat(2016, 8);
        dateAdapter = new DateAdapter(this, days, year, month);//传入当前月的年
        record_gridView.setAdapter(dateAdapter);
        record_gridView.setVerticalSpacing(60);
        record_gridView.setEnabled(false);
        /**
         * 以下是初始化其他组件
         */
        record_left = (ImageView) findViewById(R.id.record_left);
        record_right = (ImageView) findViewById(R.id.record_right);
        record_title = (TextView) findViewById(R.id.record_title);
    }


    /**
     * 下一个月
     *
     * @return
     */
    private int[][] nextMonth() {
        if (month == 12) {
            month = 1;
            year++;
        } else {
            month++;
        }
        days = DateUtils.getDayOfMonthFormat(year, month);
        return days;
    }

    /**
     * 上一个月
     *
     * @return
     */
    private int[][] prevMonth() {
        if (month == 1) {
            month = 12;
            year--;
        } else {
            month--;
        }
        days = DateUtils.getDayOfMonthFormat(year, month);
        return days;
    }

    /**
     * 设置标题
     */
    private void setTile() {
        title = year + "年" + month + "月";
        record_title.setText(title);
    }

    @Override
    public void onClick(View view) {
        switch (view.getId()) {
            case R.id.record_left:
                days = prevMonth();
                dateAdapter = new DateAdapter(this, days, year, month);
                record_gridView.setAdapter(dateAdapter);
                dateAdapter.notifyDataSetChanged();
                setTile();
                break;
            case R.id.record_right:
                days = nextMonth();
                dateAdapter = new DateAdapter(this, days, year, month);
                record_gridView.setAdapter(dateAdapter);
                dateAdapter.notifyDataSetChanged();
                setTile();
                break;
        }
    }
}

OK,一个简单的日历就完成了
作者:jiaomenglei 发表于2016/8/24 17:02:01 原文链接
阅读:16 评论:0 查看评论

android 总结文件相关工具类

$
0
0

总结文件相关工具类,为以后开发简洁使用

package com.blankj.utilcode.utils;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.BufferedReader;
import java.io.Closeable;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.FilenameFilter;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import static com.blankj.utilcode.utils.ConstUtils.KB;

/**
 *     blog  : http://blog.csdn.net/cheweilol
 *     time  : 2016/8/24
 *     desc  : 文件相关工具类
 * 
 */
public class FileUtils {

    private FileUtils() {
        throw new UnsupportedOperationException("u can't fuck me...");
    }

    /**
     * 根据文件路径获取文件
     *
     * @param filePath 文件路径
     * @return 文件
     */
    public static File getFileByPath(String filePath) {
        return StringUtils.isSpace(filePath) ? null : new File(filePath);
    }

    /**
     * 判断文件是否存在
     *
     * @param filePath 文件路径
     * @return {@code true}: 存在<br>{@code false}: 不存在
     */
    public static boolean isFileExists(String filePath) {
        return isFileExists(getFileByPath(filePath));
    }

    /**
     * 判断文件是否存在
     *
     * @param file 文件
     * @return {@code true}: 存在<br>{@code false}: 不存在
     */
    public static boolean isFileExists(File file) {
        return file != null && file.exists();
    }

    /**
     * 判断是否是目录
     *
     * @param dirPath 目录路径
     * @return {@code true}: 是<br>{@code false}: 否
     */
    public static boolean isDir(String dirPath) {
        return isDir(getFileByPath(dirPath));
    }

    /**
     * 判断是否是目录
     *
     * @param file 文件
     * @return {@code true}: 是<br>{@code false}: 否
     */
    public static boolean isDir(File file) {
        return isFileExists(file) && file.isDirectory();
    }

    /**
     * 判断是否是文件
     *
     * @param filePath 文件路径
     * @return {@code true}: 是<br>{@code false}: 否
     */
    public static boolean isFile(String filePath) {
        return isFile(getFileByPath(filePath));
    }

    /**
     * 判断是否是文件
     *
     * @param file 文件
     * @return {@code true}: 是<br>{@code false}: 否
     */
    public static boolean isFile(File file) {
        return isFileExists(file) && file.isFile();
    }

    /**
     * 判断目录是否存在,不存在则判断是否创建成功
     *
     * @param dirPath 文件路径
     * @return {@code true}: 存在或创建成功<br>{@code false}: 不存在或创建失败
     */
    public static boolean createOrExistsDir(String dirPath) {
        return createOrExistsDir(getFileByPath(dirPath));
    }

    /**
     * 判断目录是否存在,不存在则判断是否创建成功
     *
     * @param file 文件
     * @return {@code true}: 存在或创建成功<br>{@code false}: 不存在或创建失败
     */
    public static boolean createOrExistsDir(File file) {
        // 如果存在,是目录则返回true,是文件则返回false,不存在则返回是否创建成功
        return file != null && (file.exists() ? file.isDirectory() : file.mkdirs());
    }

    /**
     * 判断文件是否存在,不存在则判断是否创建成功
     *
     * @param filePath 文件路径
     * @return {@code true}: 存在或创建成功<br>{@code false}: 不存在或创建失败
     */
    public static boolean createOrExistsFile(String filePath) {
        return createOrExistsFile(getFileByPath(filePath));
    }

    /**
     * 判断文件是否存在,不存在则判断是否创建成功
     *
     * @param file 文件
     * @return {@code true}: 存在或创建成功<br>{@code false}: 不存在或创建失败
     */
    public static boolean createOrExistsFile(File file) {
        if (file == null) return false;
        // 如果存在,是文件则返回true,是目录则返回false
        if (file.exists()) return file.isFile();
        if (!createOrExistsDir(file.getParentFile())) return false;
        try {
            return file.createNewFile();
        } catch (IOException e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 判断文件是否存在,存在则在创建之前删除
     *
     * @param filePath 文件路径
     * @return {@code true}: 创建成功<br>{@code false}: 创建失败
     */
    public static boolean createFileByDeleteOldFile(String filePath) {
        return createFileByDeleteOldFile(getFileByPath(filePath));
    }

    /**
     * 判断文件是否存在,存在则在创建之前删除
     *
     * @param file 文件
     * @return {@code true}: 创建成功<br>{@code false}: 创建失败
     */
    public static boolean createFileByDeleteOldFile(File file) {
        if (file == null) return false;
        // 文件存在并且删除失败返回false
        if (file.exists() && file.isFile() && !file.delete()) return false;
        // 创建目录失败返回false
        if (!createOrExistsDir(file.getParentFile())) return false;
        try {
            return file.createNewFile();
        } catch (IOException e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 复制或移动目录
     *
     * @param srcDirPath  源目录路径
     * @param destDirPath 目标目录路径
     * @param isMove      是否移动
     * @return {@code true}: 复制或移动成功<br>{@code false}: 复制或移动失败
     */
    private static boolean copyOrMoveDir(String srcDirPath, String destDirPath, boolean isMove) {
        return copyOrMoveDir(getFileByPath(srcDirPath), getFileByPath(destDirPath), isMove);
    }

    /**
     * 复制或移动目录
     *
     * @param srcDir  源目录
     * @param destDir 目标目录
     * @param isMove  是否移动
     * @return {@code true}: 复制或移动成功<br>{@code false}: 复制或移动失败
     */
    private static boolean copyOrMoveDir(File srcDir, File destDir, boolean isMove) {
        if (srcDir == null || destDir == null) return false;
        // 如果目标目录在源目录中则返回false,看不懂的话好好想想递归怎么结束
        // srcPath : F:\\MyGithub\\AndroidUtilCode\\utilcode\\src\\test\\res
        // destPath: F:\\MyGithub\\AndroidUtilCode\\utilcode\\src\\test\\res1
        // 为防止以上这种情况出现出现误判,须分别在后面加个路径分隔符
        String srcPath = srcDir.getPath() + File.separator;
        String destPath = destDir.getPath() + File.separator;
        if (destPath.contains(srcPath)) return false;
        // 源文件不存在或者不是目录则返回false
        if (!srcDir.exists() || !srcDir.isDirectory()) return false;
        // 目标目录不存在返回false
        if (!createOrExistsDir(destDir)) return false;
        File[] files = srcDir.listFiles();
        for (File file : files) {
            File oneDestFile = new File(destPath + file.getName());
            if (file.isFile()) {
                // 如果操作失败返回false
                if (!copyOrMoveFile(file, oneDestFile, isMove)) return false;
            } else if (file.isDirectory()) {
                // 如果操作失败返回false
                if (!copyOrMoveDir(file, oneDestFile, isMove)) return false;
            }
        }
        return !isMove || deleteDir(srcDir);
    }

    /**
     * 复制或移动文件
     *
     * @param srcFilePath  源文件路径
     * @param destFilePath 目标文件路径
     * @param isMove       是否移动
     * @return {@code true}: 复制或移动成功<br>{@code false}: 复制或移动失败
     */
    private static boolean copyOrMoveFile(String srcFilePath, String destFilePath, boolean isMove) {
        return copyOrMoveFile(getFileByPath(srcFilePath), getFileByPath(destFilePath), isMove);
    }

    /**
     * 复制或移动文件
     *
     * @param srcFile  源文件
     * @param destFile 目标文件
     * @param isMove   是否移动
     * @return {@code true}: 复制或移动成功<br>{@code false}: 复制或移动失败
     */
    private static boolean copyOrMoveFile(File srcFile, File destFile, boolean isMove) {
        if (srcFile == null || destFile == null) return false;
        // 源文件不存在或者不是文件则返回false
        if (!srcFile.exists() || !srcFile.isFile()) return false;
        // 目标文件存在且是文件则返回false
        if (destFile.exists() && destFile.isFile()) return false;
        // 目标目录不存在返回false
        if (!createOrExistsDir(destFile.getParentFile())) return false;
        try {
            return writeFileFromIS(destFile, new FileInputStream(srcFile), false)
                    && !(isMove && !deleteFile(srcFile));
        } catch (FileNotFoundException e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 复制目录
     *
     * @param srcDirPath  源目录路径
     * @param destDirPath 目标目录路径
     * @return {@code true}: 复制成功<br>{@code false}: 复制失败
     */
    public static boolean copyDir(String srcDirPath, String destDirPath) {
        return copyDir(getFileByPath(srcDirPath), getFileByPath(destDirPath));
    }

    /**
     * 复制目录
     *
     * @param srcDir  源目录
     * @param destDir 目标目录
     * @return {@code true}: 复制成功<br>{@code false}: 复制失败
     */
    public static boolean copyDir(File srcDir, File destDir) {
        return copyOrMoveDir(srcDir, destDir, false);
    }

    /**
     * 复制文件
     *
     * @param srcFilePath  源文件路径
     * @param destFilePath 目标文件路径
     * @return {@code true}: 复制成功<br>{@code false}: 复制失败
     */
    public static boolean copyFile(String srcFilePath, String destFilePath) {
        return copyFile(getFileByPath(srcFilePath), getFileByPath(destFilePath));
    }

    /**
     * 复制文件
     *
     * @param srcFile  源文件
     * @param destFile 目标文件
     * @return {@code true}: 复制成功<br>{@code false}: 复制失败
     */
    public static boolean copyFile(File srcFile, File destFile) {
        return copyOrMoveFile(srcFile, destFile, false);
    }

    /**
     * 移动目录
     *
     * @param srcDirPath  源目录路径
     * @param destDirPath 目标目录路径
     * @return {@code true}: 移动成功<br>{@code false}: 移动失败
     */
    public static boolean moveDir(String srcDirPath, String destDirPath) {
        return moveDir(getFileByPath(srcDirPath), getFileByPath(destDirPath));
    }

    /**
     * 移动目录
     *
     * @param srcDir  源目录
     * @param destDir 目标目录
     * @return {@code true}: 移动成功<br>{@code false}: 移动失败
     */
    public static boolean moveDir(File srcDir, File destDir) {
        return copyOrMoveDir(srcDir, destDir, true);
    }

    /**
     * 移动文件
     *
     * @param srcFilePath  源文件路径
     * @param destFilePath 目标文件路径
     * @return {@code true}: 移动成功<br>{@code false}: 移动失败
     */
    public static boolean moveFile(String srcFilePath, String destFilePath) {
        return moveFile(getFileByPath(srcFilePath), getFileByPath(destFilePath));
    }

    /**
     * 移动文件
     *
     * @param srcFile  源文件
     * @param destFile 目标文件
     * @return {@code true}: 移动成功<br>{@code false}: 移动失败
     */
    public static boolean moveFile(File srcFile, File destFile) {
        return copyOrMoveFile(srcFile, destFile, true);
    }

    /**
     * 删除目录
     *
     * @param dirPath 目录路径
     * @return {@code true}: 删除成功<br>{@code false}: 删除失败
     */
    public static boolean deleteDir(String dirPath) {
        return deleteDir(getFileByPath(dirPath));
    }

    /**
     * 删除目录
     *
     * @param dir 目录
     * @return {@code true}: 删除成功<br>{@code false}: 删除失败
     */
    public static boolean deleteDir(File dir) {
        if (dir == null) return false;
        // 目录不存在返回true
        if (!dir.exists()) return true;
        // 不是目录返回false
        if (!dir.isDirectory()) return false;
        // 现在文件存在且是文件夹
        File[] files = dir.listFiles();
        for (File file : files) {
            if (file.isFile()) {
                if (!deleteFile(file)) return false;
            } else if (file.isDirectory()) {
                if (!deleteDir(file)) return false;
            }
        }
        return dir.delete();
    }

    /**
     * 删除文件
     *
     * @param srcFilePath 文件路径
     * @return {@code true}: 删除成功<br>{@code false}: 删除失败
     */
    public static boolean deleteFile(String srcFilePath) {
        return deleteFile(getFileByPath(srcFilePath));
    }

    /**
     * 删除文件
     *
     * @param file 文件
     * @return {@code true}: 删除成功<br>{@code false}: 删除失败
     */
    public static boolean deleteFile(File file) {
        return file != null && (!file.exists() || file.isFile() && file.delete());
    }

    /**
     * 获取目录下所有文件
     *
     * @param dirPath     目录路径
     * @param isRecursive 是否递归进子目录
     * @return 文件链表
     */
    public static List<File> listFilesInDir(String dirPath, boolean isRecursive) {
        return listFilesInDir(getFileByPath(dirPath), isRecursive);
    }

    /**
     * 获取目录下所有文件
     *
     * @param dir         目录
     * @param isRecursive 是否递归进子目录
     * @return 文件链表
     */
    public static List<File> listFilesInDir(File dir, boolean isRecursive) {
        if (isRecursive) return listFilesInDir(dir);
        if (dir == null || !isDir(dir)) return null;
        List<File> list = new ArrayList<>();
        File[] files = dir.listFiles();
        Collections.addAll(list, files);
        return list;
    }

    /**
     * 获取目录下所有文件包括子目录
     *
     * @param dir 目录
     * @return 文件链表
     */
    public static List<File> listFilesInDir(File dir) {
        if (dir == null || !isDir(dir)) return null;
        List<File> list = new ArrayList<>();
        File[] files = dir.listFiles();
        for (File file : files) {
            list.add(file);
            if (file.isDirectory()) {
                list.addAll(listFilesInDir(file));
            }
        }
        return list;
    }

    /**
     * 获取目录下所有后缀名为suffix的文件
     * <p>大小写忽略</p>
     *
     * @param dirPath     目录路径
     * @param isRecursive 是否递归进子目录
     * @return 文件链表
     */
    public static List<File> listFilesInDirWithFilter(String dirPath, String suffix, boolean isRecursive) {
        return listFilesInDirWithFilter(getFileByPath(dirPath), suffix, isRecursive);
    }

    /**
     * 获取目录下所有后缀名为suffix的文件
     * <p>大小写忽略</p>
     *
     * @param dir         目录
     * @param isRecursive 是否递归进子目录
     * @return 文件链表
     */
    public static List<File> listFilesInDirWithFilter(File dir, String suffix, boolean isRecursive) {
        if (isRecursive) return listFilesInDirWithFilter(dir, suffix);
        if (dir == null || !isDir(dir)) return null;
        List<File> list = new ArrayList<>();
        File[] files = dir.listFiles();
        for (File file : files) {
            if (file.getName().toUpperCase().endsWith(suffix.toUpperCase())) {
                list.add(file);
            }
        }
        return list;
    }

    /**
     * 获取目录下所有后缀名为suffix的文件包括子目录
     * <p>大小写忽略</p>
     *
     * @param dirPath 目录路径
     * @return 文件链表
     */
    public static List<File> listFilesInDirWithFilter(String dirPath, String suffix) {
        return listFilesInDirWithFilter(getFileByPath(dirPath), suffix);
    }

    /**
     * 获取目录下所有后缀名为suffix的文件包括子目录
     * <p>大小写忽略</p>
     *
     * @param dir 目录
     * @return 文件链表
     */
    public static List<File> listFilesInDirWithFilter(File dir, String suffix) {
        if (dir == null || !isDir(dir)) return null;
        List<File> list = new ArrayList<>();
        File[] files = dir.listFiles();
        for (File file : files) {
            if (file.getName().toUpperCase().endsWith(suffix.toUpperCase())) {
                list.add(file);
            }
            if (file.isDirectory()) {
                list.addAll(listFilesInDirWithFilter(file, suffix));
            }
        }
        return list;
    }

    /**
     * 获取目录下所有符合filter的文件
     *
     * @param dirPath     目录路径
     * @param isRecursive 是否递归进子目录
     * @return 文件链表
     */
    public static List<File> listFilesInDirWithFilter(String dirPath, FilenameFilter filter, boolean isRecursive) {
        return listFilesInDirWithFilter(getFileByPath(dirPath), filter, isRecursive);
    }

    /**
     * 获取目录下所有符合filter的文件
     *
     * @param dir         目录
     * @param isRecursive 是否递归进子目录
     * @return 文件链表
     */
    public static List<File> listFilesInDirWithFilter(File dir, FilenameFilter filter, boolean isRecursive) {
        if (isRecursive) return listFilesInDirWithFilter(dir, filter);
        if (dir == null || !isDir(dir)) return null;
        List<File> list = new ArrayList<>();
        File[] files = dir.listFiles();
        for (File file : files) {
            if (filter.accept(file.getParentFile(), file.getName())) {
                list.add(file);
            }
        }
        return list;
    }

    /**
     * 获取目录下所有符合filter的文件包括子目录
     *
     * @param dirPath 目录路径
     * @return 文件链表
     */
    public static List<File> listFilesInDirWithFilter(String dirPath, FilenameFilter filter) {
        return listFilesInDirWithFilter(getFileByPath(dirPath), filter);
    }

    /**
     * 获取目录下所有符合filter的文件包括子目录
     *
     * @param dir 目录
     * @return 文件链表
     */
    public static List<File> listFilesInDirWithFilter(File dir, FilenameFilter filter) {
        if (dir == null || !isDir(dir)) return null;
        List<File> list = new ArrayList<>();
        File[] files = dir.listFiles();
        for (File file : files) {
            if (filter.accept(file.getParentFile(), file.getName())) {
                list.add(file);
            }
            if (file.isDirectory()) {
                list.addAll(listFilesInDirWithFilter(file, filter));
            }
        }
        return list;
    }

    /**
     * 获取目录下指定文件名的文件包括子目录
     * <p>大小写忽略</p>
     *
     * @param dirPath 目录路径
     * @return 文件链表
     */
    public static List<File> searchFileInDir(String dirPath, String fileName) {
        return searchFileInDir(getFileByPath(dirPath), fileName);
    }

    /**
     * 获取目录下指定文件名的文件包括子目录
     * <p>大小写忽略</p>
     *
     * @param dir 目录
     * @return 文件链表
     */
    public static List<File> searchFileInDir(File dir, String fileName) {
        if (dir == null || !isDir(dir)) return null;
        List<File> list = new ArrayList<>();
        File[] files = dir.listFiles();
        for (File file : files) {
            if (file.getName().toUpperCase().equals(fileName.toUpperCase())) {
                list.add(file);
            }
            if (file.isDirectory()) {
                list.addAll(listFilesInDirWithFilter(file, fileName));
            }
        }
        return list;
    }

    /**
     * 将输入流写入文件
     *
     * @param filePath 路径
     * @param is       输入流
     * @param append   是否追加在文件末
     * @return {@code true}: 写入成功<br>{@code false}: 写入失败
     */
    public static boolean writeFileFromIS(String filePath, InputStream is, boolean append) {
        return writeFileFromIS(getFileByPath(filePath), is, append);
    }

    /**
     * 将输入流写入文件
     *
     * @param file   文件
     * @param is     输入流
     * @param append 是否追加在文件末
     * @return {@code true}: 写入成功<br>{@code false}: 写入失败
     */
    public static boolean writeFileFromIS(File file, InputStream is, boolean append) {
        if (file == null || is == null) return false;
        if (!createOrExistsFile(file)) return false;
        OutputStream os = null;
        try {
            os = new BufferedOutputStream(new FileOutputStream(file, append));
            byte data[] = new byte[KB];
            int len;
            while ((len = is.read(data)) != -1) {
                os.write(data, 0, len);
            }
            return true;
        } catch (IOException e) {
            e.printStackTrace();
            return false;
        } finally {
            closeIO(is);
            closeIO(os);
        }
    }

    /**
     * 将字符串写入文件
     *
     * @param filePath 文件路径
     * @param content  写入内容
     * @param append   是否追加在文件末
     * @return {@code true}: 写入成功<br>{@code false}: 写入失败
     */
    public static boolean writeFileFromString(String filePath, String content, boolean append) {
        return writeFileFromString(getFileByPath(filePath), content, append);
    }

    /**
     * 将字符串写入文件
     *
     * @param file    文件
     * @param content 写入内容
     * @param append  是否追加在文件末
     * @return {@code true}: 写入成功<br>{@code false}: 写入失败
     */
    public static boolean writeFileFromString(File file, String content, boolean append) {
        if (file == null || content == null) return false;
        if (!createOrExistsFile(file)) return false;
        FileWriter fileWriter = null;
        try {
            fileWriter = new FileWriter(file, append);
            fileWriter.write(content);
            return true;
        } catch (IOException e) {
            e.printStackTrace();
            return false;
        } finally {
            closeIO(fileWriter);
        }
    }

    /**
     * 简单获取文件编码格式
     *
     * @param filePath 文件路径
     * @return 文件编码
     */
    public static String getFileCharsetSimple(String filePath) {
        return getFileCharsetSimple(getFileByPath(filePath));
    }

    /**
     * 简单获取文件编码格式
     *
     * @param file 文件
     * @return 文件编码
     */
    public static String getFileCharsetSimple(File file) {
        int p = 0;
        InputStream is = null;
        try {
            is = new BufferedInputStream(new FileInputStream(file));
            p = (is.read() << 8) + is.read();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            closeIO(is);
        }
        switch (p) {
            case 0xefbb:
                return "UTF-8";
            case 0xfffe:
                return "Unicode";
            case 0xfeff:
                return "UTF-16BE";
            default:
                return "GBK";
        }
    }

    /**
     * 获取文件行数
     *
     * @param filePath 文件路径
     * @return 文件行数
     */
    public static int getFileLines(String filePath) {
        return getFileLines(getFileByPath(filePath));
    }

    /**
     * 获取文件行数
     *
     * @param file 文件
     * @return 文件行数
     */
    public static int getFileLines(File file) {
        int count = 1;
        InputStream is = null;
        try {
            is = new BufferedInputStream(new FileInputStream(file));
            byte[] buffer = new byte[KB];
            int readChars;
            while ((readChars = is.read(buffer)) != -1) {
                for (int i = 0; i < readChars; ++i) {
                    if (buffer[i] == '\n') ++count;
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            closeIO(is);
        }
        return count;
    }

    /**
     * 指定编码按行读取文件到List
     *
     * @param filePath    文件路径
     * @param charsetName 编码格式
     * @return 文件行链表
     */
    public static List<String> readFile2List(String filePath, String charsetName) {
        return readFile2List(getFileByPath(filePath), charsetName);
    }

    /**
     * 指定编码按行读取文件到List
     *
     * @param file        文件
     * @param charsetName 编码格式
     * @return 文件行链表
     */
    public static List<String> readFile2List(File file, String charsetName) {
        return readFile2List(file, 0, 0x7FFFFFFF, charsetName);
    }

    /**
     * 指定编码按行读取文件到List
     *
     * @param filePath    文件路径
     * @param st          需要读取的开始行数
     * @param end         需要读取的结束行数
     * @param charsetName 编码格式
     * @return 包含制定行的list
     */
    public static List<String> readFile2List(String filePath, int st, int end, String
            charsetName) {
        return readFile2List(getFileByPath(filePath), st, end, charsetName);
    }

    /**
     * 指定编码按行读取文件到List
     *
     * @param file        文件
     * @param st          需要读取的开始行数
     * @param end         需要读取的结束行数
     * @param charsetName 编码格式
     * @return 包含从start行到end行的list
     */
    public static List<String> readFile2List(File file, int st, int end, String charsetName) {
        if (file == null) return null;
        if (st > end) return null;
        BufferedReader reader = null;
        try {
            String line;
            int curLine = 1;
            List<String> list = new ArrayList<>();
            if (StringUtils.isSpace(charsetName)) {
                reader = new BufferedReader(new FileReader(file));
            } else {
                reader = new BufferedReader(new InputStreamReader(new FileInputStream(file), charsetName));
            }
            while ((line = reader.readLine()) != null) {
                if (curLine > end) break;
                if (st <= curLine && curLine <= end) list.add(line);
                ++curLine;
            }
            return list;
        } catch (IOException e) {
            e.printStackTrace();
            return null;
        } finally {
            closeIO(reader);
        }
    }

    /**
     * 指定编码按行读取文件到字符串中
     *
     * @param filePath    文件路径
     * @param charsetName 编码格式
     * @return 字符串
     */
    public static String readFile2String(String filePath, String charsetName) {
        return readFile2String(getFileByPath(filePath), charsetName);
    }

    /**
     * 指定编码按行读取文件到字符串中
     *
     * @param file        文件
     * @param charsetName 编码格式
     * @return 字符串
     */
    public static String readFile2String(File file, String charsetName) {
        if (file == null) return null;
        try {
            return ConvertUtils.inputStream2String(new FileInputStream(file), charsetName);
        } catch (FileNotFoundException e) {
            e.printStackTrace();
            return null;
        }
    }

    /**
     * 指定编码按行读取文件到字符串中
     *
     * @param filePath 文件路径
     * @return StringBuilder对象
     */
    public static byte[] readFile2Bytes(String filePath) {
        return readFile2Bytes(getFileByPath(filePath));
    }

    /**
     * 指定编码按行读取文件到字符串中
     *
     * @param file 文件
     * @return StringBuilder对象
     */
    public static byte[] readFile2Bytes(File file) {
        if (file == null) return null;
        try {
            return ConvertUtils.inputStream2Bytes(new FileInputStream(file));
        } catch (FileNotFoundException e) {
            e.printStackTrace();
            return null;
        }
    }

    /**
     * byte单位转换(单位:unit)
     *
     * @param size 大小
     * @param unit <ul>
     *             <li>{@link ConstUtils#BYTE}: 字节</li>
     *             <li>{@link ConstUtils#KB}  : 千字节</li>
     *             <li>{@link ConstUtils#MB}  : 兆</li>
     *             <li>{@link ConstUtils#GB}  : GB</li>
     *             </ul>
     * @return 大小以unit为单位
     */
    public static double byte2Unit(long size, int unit) {
        switch (unit) {
            case ConstUtils.BYTE:
            case ConstUtils.KB:
            case ConstUtils.MB:
            case ConstUtils.GB:
                return (double) size / unit;
        }
        return -1;
    }

    /**
     * 获取文件大小
     * <p>例如:getFileSize(filePath, ConstUtils.MB); 返回文件大小单位为MB</p>
     *
     * @param filePath 文件路径
     * @param unit     <ul>
     *                 <li>{@link ConstUtils#BYTE}: 字节</li>
     *                 <li>{@link ConstUtils#KB}  : 千字节</li>
     *                 <li>{@link ConstUtils#MB}  : 兆</li>
     *                 <li>{@link ConstUtils#GB}  : GB</li>
     *                 </ul>
     * @return 文件大小以unit为单位
     */
    public static double getFileSize(String filePath, int unit) {
        return getFileSize(getFileByPath(filePath), unit);
    }

    /**
     * 获取文件大小
     * <p>例如:getFileSize(file, ConstUtils.MB); 返回文件大小单位为MB</p>
     *
     * @param file 文件
     * @param unit <ul>
     *             <li>{@link ConstUtils#BYTE}: 字节</li>
     *             <li>{@link ConstUtils#KB}  : 千字节</li>
     *             <li>{@link ConstUtils#MB}  : 兆</li>
     *             <li>{@link ConstUtils#GB}  : GB</li>
     *             </ul>
     * @return 文件大小以unit为单位
     */
    public static double getFileSize(File file, int unit) {
        if (!isFileExists(file)) return -1;
        return byte2Unit(file.length(), unit);
    }

    /**
     * 关闭IO
     *
     * @param closeable closeable
     */
    public static void closeIO(Closeable closeable) {
        if (closeable == null) return;
        try {
            closeable.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * 获取全路径中的最长目录
     *
     * @param file 文件
     * @return filePath最长目录
     */
    public static String getDirName(File file) {
        if (!isFileExists(file)) return "";
        return getDirName(file.getPath());
    }

    /**
     * 获取全路径中的最长目录
     *
     * @param filePath 文件路径
     * @return filePath最长目录
     */
    public static String getDirName(String filePath) {
        if (StringUtils.isSpace(filePath)) return filePath;
        int lastSep = filePath.lastIndexOf(File.separator);
        return lastSep == -1 ? "" : filePath.substring(0, lastSep + 1);
    }

    /**
     * 获取全路径中的文件名
     *
     * @param file 文件
     * @return 文件名
     */
    public static String getFileName(File file) {
        if (!isFileExists(file)) return "";
        return getFileName(file.getPath());
    }

    /**
     * 获取全路径中的文件名
     *
     * @param filePath 文件路径
     * @return 文件名
     */
    public static String getFileName(String filePath) {
        if (StringUtils.isSpace(filePath)) return filePath;
        int lastSep = filePath.lastIndexOf(File.separator);
        return lastSep == -1 ? filePath : filePath.substring(lastSep + 1);
    }

    /**
     * 获取全路径中的不带拓展名的文件名
     *
     * @param file 文件
     * @return 不带拓展名的文件名
     */
    public static String getFileNameNoExtension(File file) {
        if (!isFileExists(file)) return "";
        return getFileNameNoExtension(file.getPath());
    }

    /**
     * 获取全路径中的不带拓展名的文件名
     *
     * @param filePath 文件路径
     * @return 不带拓展名的文件名
     */
    public static String getFileNameNoExtension(String filePath) {
        if (StringUtils.isSpace(filePath)) return filePath;
        int lastPoi = filePath.lastIndexOf('.');
        int lastSep = filePath.lastIndexOf(File.separator);
        if (lastSep == -1) {
            return (lastPoi == -1 ? filePath : filePath.substring(0, lastPoi));
        }
        if (lastPoi == -1 || lastSep > lastPoi) {
            return filePath.substring(lastSep + 1);
        }
        return filePath.substring(lastSep + 1, lastPoi);
    }


    /**
     * 获取全路径中的文件拓展名
     *
     * @param file 文件
     * @return 文件拓展名
     */
    public static String getFileExtension(File file) {
        if (!isFileExists(file)) return "";
        return getFileExtension(file.getPath());
    }

    /**
     * 获取全路径中的文件拓展名
     *
     * @param filePath 文件路径
     * @return 文件拓展名
     */
    public static String getFileExtension(String filePath) {
        if (StringUtils.isSpace(filePath)) return filePath;
        int lastPoi = filePath.lastIndexOf('.');
        int lastSep = filePath.lastIndexOf(File.separator);
        if (lastPoi == -1 || lastSep >= lastPoi) return "";
        return filePath.substring(lastPoi + 1);
    }
}

作者:cheweilol 发表于2016/8/24 17:14:28 原文链接
阅读:20 评论:0 查看评论

Fresco-加载图片基础[详细图解Fresco的使用]

$
0
0

Fresco简单的使用—SimpleDraweeView

转自:http://blog.csdn.net/y1scp/article/details/49245535
  • 百学须先立志—学前须知:

    在我们平时加载图片(不管是下载还是加载本地图片…..)的时候,我们经常会遇到这样一个需求,那就是当图片正在加载时应该呈现正在加载时的图像,当图片加载失败时应该呈现图片加载时的图像,当我们重新加载这张图片时,应该呈现重试时图像,直到这张图片加载完成。这些繁琐并且重复的如果得不到简化的话,那将是一个开发人员的噩梦,现在好了,我们用 Facebook 出品的一个强大的图片加载组件 Fresco 几行代码就可以搞定以上问题了。

  • 尽信书,不如无书—能学到什么?

    1、SimpleDraweeView最基本的使用
    2、SimpleDraweeView的圆形图
    3、SimpleDraweeView的圆角图
    4、SimpleDraweeView的缩放类型

  • 工欲善其事必先利其器—下载Fresco并导入到项目

    Fresco中文说明:http://www.fresco-cn.org/

    Fresco项目GitHub地址:https://github.com/facebook/fresco

    第一步进入 Fresco项目GitHub地址

    第一步

    第二步添加Fresco到项目工程:

    使用说明

    第三步服务及权限:

    <code class="hljs xml has-numbering"><span class="hljs-comment"><!-- 访问网络的权限 --></span>
    <span class="hljs-tag"><<span class="hljs-title">uses-permission</span> <span class="hljs-attribute">android:name</span>=<span class="hljs-value">"android.permission.INTERNET"</span>/></span></code><ul class="pre-numbering"><li>1</li><li>2</li></ul><div class="save_code tracking-ad" data-mod="popu_249"><a target=_blank href="javascript:;" target="_blank"><img src="http://static.blog.csdn.net/images/save_snippets.png" alt="" /></a></div><ul class="pre-numbering"><li>1</li><li>2</li></ul>

    服务及权限

  • 常见问题:

    初次使用,我们就先简单书写我们的 activity_main.xml

    <code class="hljs avrasm has-numbering"><RelativeLayout xmlns:android=<span class="hljs-string">"http://schemas.android.com/apk/res/android"</span>
        android:layout_width=<span class="hljs-string">"match_parent"</span>
        android:layout_height=<span class="hljs-string">"match_parent"</span>>
    
        <<span class="hljs-keyword">com</span><span class="hljs-preprocessor">.facebook</span><span class="hljs-preprocessor">.drawee</span><span class="hljs-preprocessor">.view</span><span class="hljs-preprocessor">.SimpleDraweeView</span>
            android:id=<span class="hljs-string">"@+id/main_adv"</span>
            android:layout_width=<span class="hljs-string">"300dp"</span>
            android:layout_height=<span class="hljs-string">"300dp"</span>
            android:layout_centerInParent=<span class="hljs-string">"true"</span>
            ></<span class="hljs-keyword">com</span><span class="hljs-preprocessor">.facebook</span><span class="hljs-preprocessor">.drawee</span><span class="hljs-preprocessor">.view</span><span class="hljs-preprocessor">.SimpleDraweeView</span>>
    
    </RelativeLayout></code><ul class="pre-numbering"><li>1</li><li>2</li><li>3</li><li>4</li><li>5</li><li>6</li><li>7</li><li>8</li><li>9</li><li>10</li><li>11</li><li>12</li></ul><div class="save_code tracking-ad" data-mod="popu_249"><a target=_blank href="javascript:;" target="_blank"><img src="http://static.blog.csdn.net/images/save_snippets.png" alt="" /></a></div><ul class="pre-numbering"><li>1</li><li>2</li><li>3</li><li>4</li><li>5</li><li>6</li><li>7</li><li>8</li><li>9</li><li>10</li><li>11</li><li>12</li></ul>

    代码分析:

    初次加载

    MainActivity 实现代码:

    <code class="hljs java has-numbering"><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">MainActivity</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">AppCompatActivity</span> {</span>
    
        <span class="hljs-annotation">@Override</span>
        <span class="hljs-keyword">protected</span> <span class="hljs-keyword">void</span> <span class="hljs-title">onCreate</span>(Bundle savedInstanceState) {
            <span class="hljs-keyword">super</span>.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
        }
    }</code><ul class="pre-numbering"><li>1</li><li>2</li><li>3</li><li>4</li><li>5</li><li>6</li><li>7</li><li>8</li></ul><div class="save_code tracking-ad" data-mod="popu_249"><a target=_blank href="javascript:;" target="_blank"><img src="http://static.blog.csdn.net/images/save_snippets.png" alt="" /></a></div><ul class="pre-numbering"><li>1</li><li>2</li><li>3</li><li>4</li><li>5</li><li>6</li><li>7</li><li>8</li></ul>

    简单的书写完布局和实现代码之后我们来运行一下。

    <code class="hljs avrasm has-numbering">        java<span class="hljs-preprocessor">.lang</span><span class="hljs-preprocessor">.RuntimeException</span>: Unable to start activity ComponentInfo{scp<span class="hljs-preprocessor">.com</span><span class="hljs-preprocessor">.frescodemo</span>/scp<span class="hljs-preprocessor">.com</span><span class="hljs-preprocessor">.frescodemo</span><span class="hljs-preprocessor">.MainActivity</span>}: 
    
    <span class="hljs-label">android.view.InflateException:</span> Binary XML file line <span class="hljs-preprocessor">#5: Error inflating class com.facebook.drawee.view.SimpleDraweeView</span>
                    at android<span class="hljs-preprocessor">.app</span><span class="hljs-preprocessor">.ActivityThread</span><span class="hljs-preprocessor">.performLaunchActivity</span>(ActivityThread<span class="hljs-preprocessor">.java</span>:<span class="hljs-number">2264</span>)
                    at android<span class="hljs-preprocessor">.app</span><span class="hljs-preprocessor">.ActivityThread</span><span class="hljs-preprocessor">.handleLaunchActivity</span>(ActivityThread<span class="hljs-preprocessor">.java</span>:<span class="hljs-number">2313</span>)
                    at android<span class="hljs-preprocessor">.app</span><span class="hljs-preprocessor">.ActivityThread</span><span class="hljs-preprocessor">.access</span>$1100(ActivityThread<span class="hljs-preprocessor">.java</span>:<span class="hljs-number">141</span>)
                    at android<span class="hljs-preprocessor">.app</span><span class="hljs-preprocessor">.ActivityThread</span>$H<span class="hljs-preprocessor">.handleMessage</span>(ActivityThread<span class="hljs-preprocessor">.java</span>:<span class="hljs-number">1238</span>)
                    at android<span class="hljs-preprocessor">.os</span><span class="hljs-preprocessor">.Handler</span><span class="hljs-preprocessor">.dispatchMessage</span>(Handler<span class="hljs-preprocessor">.java</span>:<span class="hljs-number">102</span>)
                    at android<span class="hljs-preprocessor">.os</span><span class="hljs-preprocessor">.Looper</span><span class="hljs-preprocessor">.loop</span>(Looper<span class="hljs-preprocessor">.java</span>:<span class="hljs-number">136</span>)
                    at android<span class="hljs-preprocessor">.app</span><span class="hljs-preprocessor">.ActivityThread</span><span class="hljs-preprocessor">.main</span>(ActivityThread<span class="hljs-preprocessor">.java</span>:<span class="hljs-number">5336</span>)
                    at java<span class="hljs-preprocessor">.lang</span><span class="hljs-preprocessor">.reflect</span><span class="hljs-preprocessor">.Method</span><span class="hljs-preprocessor">.invokeNative</span>(Native Method)
                    at java<span class="hljs-preprocessor">.lang</span><span class="hljs-preprocessor">.reflect</span><span class="hljs-preprocessor">.Method</span><span class="hljs-preprocessor">.invoke</span>(Method<span class="hljs-preprocessor">.java</span>:<span class="hljs-number">515</span>)
                    at <span class="hljs-keyword">com</span><span class="hljs-preprocessor">.android</span><span class="hljs-preprocessor">.internal</span><span class="hljs-preprocessor">.os</span><span class="hljs-preprocessor">.ZygoteInit</span>$MethodAndArgsCaller<span class="hljs-preprocessor">.run</span>(ZygoteInit<span class="hljs-preprocessor">.java</span>:<span class="hljs-number">871</span>)
                    at <span class="hljs-keyword">com</span><span class="hljs-preprocessor">.android</span><span class="hljs-preprocessor">.internal</span><span class="hljs-preprocessor">.os</span><span class="hljs-preprocessor">.ZygoteInit</span><span class="hljs-preprocessor">.main</span>(ZygoteInit<span class="hljs-preprocessor">.java</span>:<span class="hljs-number">687</span>)
                    at dalvik<span class="hljs-preprocessor">.system</span><span class="hljs-preprocessor">.NativeStart</span><span class="hljs-preprocessor">.main</span>(Native Method)
             Caused by: android<span class="hljs-preprocessor">.view</span><span class="hljs-preprocessor">.InflateException</span>: Binary XML file line <span class="hljs-preprocessor">#5: Error inflating class com.facebook.drawee.view.SimpleDraweeView</span></code><ul class="pre-numbering"><li>1</li><li>2</li><li>3</li><li>4</li><li>5</li><li>6</li><li>7</li><li>8</li><li>9</li><li>10</li><li>11</li><li>12</li><li>13</li><li>14</li><li>15</li><li>16</li></ul><div class="save_code tracking-ad" data-mod="popu_249"><a target=_blank href="javascript:;" target="_blank"><img src="http://static.blog.csdn.net/images/save_snippets.png" alt="" /></a></div><ul class="pre-numbering"><li>1</li><li>2</li><li>3</li><li>4</li><li>5</li><li>6</li><li>7</li><li>8</li><li>9</li><li>10</li><li>11</li><li>12</li><li>13</li><li>14</li><li>15</li><li>16</li></ul>

    报错

    运行报错了!怎么回事呢?这里啊,是因为我们没有在应用调用 setContentView() 之前进行初始化Fresco造成的;解决办法:

    修改我们的 MainActivity 里代码:

    <code class="hljs java has-numbering"><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">MainActivity</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">AppCompatActivity</span> {</span>
    
        <span class="hljs-annotation">@Override</span>
        <span class="hljs-keyword">protected</span> <span class="hljs-keyword">void</span> <span class="hljs-title">onCreate</span>(Bundle savedInstanceState) {
            <span class="hljs-keyword">super</span>.onCreate(savedInstanceState);
            Fresco.initialize(<span class="hljs-keyword">this</span>);
            setContentView(R.layout.activity_main);
        }
    }</code><ul class="pre-numbering"><li>1</li><li>2</li><li>3</li><li>4</li><li>5</li><li>6</li><li>7</li><li>8</li><li>9</li></ul><div class="save_code tracking-ad" data-mod="popu_249"><a target=_blank href="javascript:;" target="_blank"><img src="http://static.blog.csdn.net/images/save_snippets.png" alt="" /></a></div><ul class="pre-numbering"><li>1</li><li>2</li><li>3</li><li>4</li><li>5</li><li>6</li><li>7</li><li>8</li><li>9</li></ul>

    解决办法

    再运行就没有错误发生了。

  • 占位图—placeholderImage:

    在此之前我们需要一张占位图 icon_placeholder.png 大家右键另存为即可:

    icon_placeholder

    修改我们的 activity_main.xml

    <code class="hljs avrasm has-numbering"><RelativeLayout xmlns:android=<span class="hljs-string">"http://schemas.android.com/apk/res/android"</span>
        xmlns:fresco=<span class="hljs-string">"http://schemas.android.com/apk/res-auto"</span>
        android:layout_width=<span class="hljs-string">"match_parent"</span>
        android:layout_height=<span class="hljs-string">"match_parent"</span>>
    
        <<span class="hljs-keyword">com</span><span class="hljs-preprocessor">.facebook</span><span class="hljs-preprocessor">.drawee</span><span class="hljs-preprocessor">.view</span><span class="hljs-preprocessor">.SimpleDraweeView</span>
            android:id=<span class="hljs-string">"@+id/main_adv"</span>
            android:layout_width=<span class="hljs-string">"100dp"</span>
            android:layout_height=<span class="hljs-string">"100dp"</span>
            android:layout_centerInParent=<span class="hljs-string">"true"</span>
            fresco:placeholderImage=<span class="hljs-string">"@mipmap/icon_placeholder"</span>
            fresco:placeholderImageScaleType=<span class="hljs-string">"fitCenter"</span>
            ></<span class="hljs-keyword">com</span><span class="hljs-preprocessor">.facebook</span><span class="hljs-preprocessor">.drawee</span><span class="hljs-preprocessor">.view</span><span class="hljs-preprocessor">.SimpleDraweeView</span>>
    
    </RelativeLayout></code><ul class="pre-numbering"><li>1</li><li>2</li><li>3</li><li>4</li><li>5</li><li>6</li><li>7</li><li>8</li><li>9</li><li>10</li><li>11</li><li>12</li><li>13</li><li>14</li><li>15</li></ul><div class="save_code tracking-ad" data-mod="popu_249"><a target=_blank href="javascript:;" target="_blank"><img src="http://static.blog.csdn.net/images/save_snippets.png" alt="" /></a></div><ul class="pre-numbering"><li>1</li><li>2</li><li>3</li><li>4</li><li>5</li><li>6</li><li>7</li><li>8</li><li>9</li><li>10</li><li>11</li><li>12</li><li>13</li><li>14</li><li>15</li></ul>

    代码说明:

    占位符

    MainActivity 无需修改,运行一下:

    占位符

  • 正在加载图—progressBarImage:

    在此之前我们需要一张正在加载图 icon_progress_bar.png 大家右键另存为即可:

    正在加载图

    修改我们刚刚书写的 activity_main.xml

    <code class="hljs avrasm has-numbering"><RelativeLayout xmlns:android=<span class="hljs-string">"http://schemas.android.com/apk/res/android"</span>
        xmlns:fresco=<span class="hljs-string">"http://schemas.android.com/apk/res-auto"</span>
        android:layout_width=<span class="hljs-string">"match_parent"</span>
        android:layout_height=<span class="hljs-string">"match_parent"</span>>
    
        <<span class="hljs-keyword">com</span><span class="hljs-preprocessor">.facebook</span><span class="hljs-preprocessor">.drawee</span><span class="hljs-preprocessor">.view</span><span class="hljs-preprocessor">.SimpleDraweeView</span>
            android:id=<span class="hljs-string">"@+id/main_sdv"</span>
            android:layout_width=<span class="hljs-string">"100dp"</span>
            android:layout_height=<span class="hljs-string">"100dp"</span>
            android:layout_centerInParent=<span class="hljs-string">"true"</span>
            fresco:actualImageScaleType=<span class="hljs-string">"focusCrop"</span>
            fresco:placeholderImage=<span class="hljs-string">"@mipmap/icon_placeholder"</span>
            fresco:placeholderImageScaleType=<span class="hljs-string">"fitCenter"</span>
            fresco:progressBarImage=<span class="hljs-string">"@mipmap/icon_progress_bar"</span>
            fresco:progressBarImageScaleType=<span class="hljs-string">"centerInside"</span>
            fresco:progressBarAutoRotateInterval=<span class="hljs-string">"5000"</span>
            ></<span class="hljs-keyword">com</span><span class="hljs-preprocessor">.facebook</span><span class="hljs-preprocessor">.drawee</span><span class="hljs-preprocessor">.view</span><span class="hljs-preprocessor">.SimpleDraweeView</span>>
    
    </RelativeLayout></code><ul class="pre-numbering"><li>1</li><li>2</li><li>3</li><li>4</li><li>5</li><li>6</li><li>7</li><li>8</li><li>9</li><li>10</li><li>11</li><li>12</li><li>13</li><li>14</li><li>15</li><li>16</li><li>17</li><li>18</li><li>19</li></ul><div class="save_code tracking-ad" data-mod="popu_249"><a target=_blank href="javascript:;" target="_blank"><img src="http://static.blog.csdn.net/images/save_snippets.png" alt="" /></a></div><ul class="pre-numbering"><li>1</li><li>2</li><li>3</li><li>4</li><li>5</li><li>6</li><li>7</li><li>8</li><li>9</li><li>10</li><li>11</li><li>12</li><li>13</li><li>14</li><li>15</li><li>16</li><li>17</li><li>18</li><li>19</li></ul>

    代码说明:

    正在加载图

    更改我们的 MainActivity 里代码:

    <code class="hljs java has-numbering"><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">MainActivity</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">AppCompatActivity</span> {</span>
    
        <span class="hljs-keyword">private</span> SimpleDraweeView simpleDraweeView;
    
        <span class="hljs-annotation">@Override</span>
        <span class="hljs-keyword">protected</span> <span class="hljs-keyword">void</span> <span class="hljs-title">onCreate</span>(Bundle savedInstanceState) {
            <span class="hljs-keyword">super</span>.onCreate(savedInstanceState);
            Fresco.initialize(<span class="hljs-keyword">this</span>);
            setContentView(R.layout.activity_main);
            initView();
        }
    
        <span class="hljs-keyword">private</span> <span class="hljs-keyword">void</span> <span class="hljs-title">initView</span>() {
            <span class="hljs-comment">//创建SimpleDraweeView对象</span>
            simpleDraweeView = (SimpleDraweeView) findViewById(R.id.main_sdv);
            <span class="hljs-comment">//创建将要下载的图片的URI</span>
            Uri imageUri = Uri.parse(<span class="hljs-string">"http://my.csdn.net/uploads/avatar/4/E/8/1_y1scp.jpg"</span>);
            <span class="hljs-comment">//开始下载</span>
            simpleDraweeView.setImageURI(imageUri);
        }
    }</code><ul class="pre-numbering"><li>1</li><li>2</li><li>3</li><li>4</li><li>5</li><li>6</li><li>7</li><li>8</li><li>9</li><li>10</li><li>11</li><li>12</li><li>13</li><li>14</li><li>15</li><li>16</li><li>17</li><li>18</li><li>19</li><li>20</li><li>21</li></ul><div class="save_code tracking-ad" data-mod="popu_249"><a target=_blank href="javascript:;" target="_blank"><img src="http://static.blog.csdn.net/images/save_snippets.png" alt="" /></a></div><ul class="pre-numbering"><li>1</li><li>2</li><li>3</li><li>4</li><li>5</li><li>6</li><li>7</li><li>8</li><li>9</li><li>10</li><li>11</li><li>12</li><li>13</li><li>14</li><li>15</li><li>16</li><li>17</li><li>18</li><li>19</li><li>20</li><li>21</li></ul>

    代码分析:

    正在加载图

    运行效果图:

    正在加载图

    正在加载图本身是不可以转的,但是呢,加上了 fresco:progressBarAutoRotateInterval="5000" 属性,那么它就可以旋转了;可以看到,正在加载图一闪而过,这是因为我们加载的图片很小,网络也很好。

  • 失败图—failureImage:

    在此之前我们需要一张正在加载图 icon_failure.png 大家右键另存为即可:

    icon_failure

    修改我们刚刚书写的 activity_main.xml

    <code class="hljs avrasm has-numbering"><RelativeLayout xmlns:android=<span class="hljs-string">"http://schemas.android.com/apk/res/android"</span>
        xmlns:fresco=<span class="hljs-string">"http://schemas.android.com/apk/res-auto"</span>
        android:layout_width=<span class="hljs-string">"match_parent"</span>
        android:layout_height=<span class="hljs-string">"match_parent"</span>>
    
        <<span class="hljs-keyword">com</span><span class="hljs-preprocessor">.facebook</span><span class="hljs-preprocessor">.drawee</span><span class="hljs-preprocessor">.view</span><span class="hljs-preprocessor">.SimpleDraweeView</span>
            android:id=<span class="hljs-string">"@+id/main_sdv"</span>
            android:layout_width=<span class="hljs-string">"100dp"</span>
            android:layout_height=<span class="hljs-string">"100dp"</span>
            android:layout_centerInParent=<span class="hljs-string">"true"</span>
            fresco:actualImageScaleType=<span class="hljs-string">"focusCrop"</span>
            fresco:placeholderImage=<span class="hljs-string">"@mipmap/icon_placeholder"</span>
            fresco:placeholderImageScaleType=<span class="hljs-string">"fitCenter"</span>
            fresco:progressBarImage=<span class="hljs-string">"@mipmap/icon_progress_bar"</span>
            fresco:progressBarImageScaleType=<span class="hljs-string">"centerInside"</span>
            fresco:progressBarAutoRotateInterval=<span class="hljs-string">"5000"</span>
            fresco:failureImage=<span class="hljs-string">"@mipmap/icon_failure"</span>
            fresco:failureImageScaleType=<span class="hljs-string">"centerInside"</span>
            ></<span class="hljs-keyword">com</span><span class="hljs-preprocessor">.facebook</span><span class="hljs-preprocessor">.drawee</span><span class="hljs-preprocessor">.view</span><span class="hljs-preprocessor">.SimpleDraweeView</span>>
    
    </RelativeLayout></code><ul class="pre-numbering"><li>1</li><li>2</li><li>3</li><li>4</li><li>5</li><li>6</li><li>7</li><li>8</li><li>9</li><li>10</li><li>11</li><li>12</li><li>13</li><li>14</li><li>15</li><li>16</li><li>17</li><li>18</li><li>19</li><li>20</li><li>21</li></ul><div class="save_code tracking-ad" data-mod="popu_249"><a target=_blank href="javascript:;" target="_blank"><img src="http://static.blog.csdn.net/images/save_snippets.png" alt="" /></a></div><ul class="pre-numbering"><li>1</li><li>2</li><li>3</li><li>4</li><li>5</li><li>6</li><li>7</li><li>8</li><li>9</li><li>10</li><li>11</li><li>12</li><li>13</li><li>14</li><li>15</li><li>16</li><li>17</li><li>18</li><li>19</li><li>20</li><li>21</li></ul>

    代码分析:

    失败图代码分析

    修改我们的 MainActivity 里代码:

    <code class="hljs java has-numbering"><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">MainActivity</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">AppCompatActivity</span> {</span>
    
        <span class="hljs-keyword">private</span> SimpleDraweeView simpleDraweeView;
    
        <span class="hljs-annotation">@Override</span>
        <span class="hljs-keyword">protected</span> <span class="hljs-keyword">void</span> <span class="hljs-title">onCreate</span>(Bundle savedInstanceState) {
            <span class="hljs-keyword">super</span>.onCreate(savedInstanceState);
            Fresco.initialize(<span class="hljs-keyword">this</span>);
            setContentView(R.layout.activity_main);
            initView();
        }
    
        <span class="hljs-keyword">private</span> <span class="hljs-keyword">void</span> <span class="hljs-title">initView</span>() {
            <span class="hljs-comment">//创建SimpleDraweeView对象</span>
            simpleDraweeView = (SimpleDraweeView) findViewById(R.id.main_sdv);
            <span class="hljs-comment">//创建将要下载的图片的URI</span>
            Uri imageUri = Uri.parse(<span class="hljs-string">"http://my.csdn.net/uploads/avatar_y1scp.jpg"</span>);
            <span class="hljs-comment">//开始下载</span>
            simpleDraweeView.setImageURI(imageUri);
        }
    }</code><ul class="pre-numbering"><li>1</li><li>2</li><li>3</li><li>4</li><li>5</li><li>6</li><li>7</li><li>8</li><li>9</li><li>10</li><li>11</li><li>12</li><li>13</li><li>14</li><li>15</li><li>16</li><li>17</li><li>18</li><li>19</li><li>20</li><li>21</li></ul><div class="save_code tracking-ad" data-mod="popu_249"><a target=_blank href="javascript:;" target="_blank"><img src="http://static.blog.csdn.net/images/save_snippets.png" alt="" /></a></div><ul class="pre-numbering"><li>1</li><li>2</li><li>3</li><li>4</li><li>5</li><li>6</li><li>7</li><li>8</li><li>9</li><li>10</li><li>11</li><li>12</li><li>13</li><li>14</li><li>15</li><li>16</li><li>17</li><li>18</li><li>19</li><li>20</li><li>21</li></ul>

    代码说明:

    错误的网络图片地址

    运行效果:

    failure

  • 重试图—retryImage:

    在此之前我们需要一张正在加载图 icon_retry.png 大家右键另存为即可:

    icon_retry

    修改我们刚刚书写的 activity_main.xml

    <code class="hljs avrasm has-numbering"><RelativeLayout xmlns:android=<span class="hljs-string">"http://schemas.android.com/apk/res/android"</span>
        xmlns:fresco=<span class="hljs-string">"http://schemas.android.com/apk/res-auto"</span>
        android:layout_width=<span class="hljs-string">"match_parent"</span>
        android:layout_height=<span class="hljs-string">"match_parent"</span>>
    
        <<span class="hljs-keyword">com</span><span class="hljs-preprocessor">.facebook</span><span class="hljs-preprocessor">.drawee</span><span class="hljs-preprocessor">.view</span><span class="hljs-preprocessor">.SimpleDraweeView</span>
            android:id=<span class="hljs-string">"@+id/main_sdv"</span>
            android:layout_width=<span class="hljs-string">"100dp"</span>
            android:layout_height=<span class="hljs-string">"100dp"</span>
            android:layout_centerInParent=<span class="hljs-string">"true"</span>
            fresco:actualImageScaleType=<span class="hljs-string">"focusCrop"</span>
            fresco:placeholderImage=<span class="hljs-string">"@mipmap/icon_placeholder"</span>
            fresco:placeholderImageScaleType=<span class="hljs-string">"fitCenter"</span>
            fresco:progressBarImage=<span class="hljs-string">"@mipmap/icon_progress_bar"</span>
            fresco:progressBarImageScaleType=<span class="hljs-string">"centerInside"</span>
            fresco:progressBarAutoRotateInterval=<span class="hljs-string">"5000"</span>
            fresco:failureImage=<span class="hljs-string">"@mipmap/icon_failure"</span>
            fresco:failureImageScaleType=<span class="hljs-string">"centerInside"</span>
            fresco:retryImage=<span class="hljs-string">"@mipmap/icon_retry"</span>
            fresco:retryImageScaleType=<span class="hljs-string">"centerCrop"</span>
            ></<span class="hljs-keyword">com</span><span class="hljs-preprocessor">.facebook</span><span class="hljs-preprocessor">.drawee</span><span class="hljs-preprocessor">.view</span><span class="hljs-preprocessor">.SimpleDraweeView</span>>
    
    </RelativeLayout></code><ul class="pre-numbering"><li>1</li><li>2</li><li>3</li><li>4</li><li>5</li><li>6</li><li>7</li><li>8</li><li>9</li><li>10</li><li>11</li><li>12</li><li>13</li><li>14</li><li>15</li><li>16</li><li>17</li><li>18</li><li>19</li><li>20</li><li>21</li><li>22</li><li>23</li></ul><div class="save_code tracking-ad" data-mod="popu_249"><a target=_blank href="javascript:;" target="_blank"><img src="http://static.blog.csdn.net/images/save_snippets.png" alt="" /></a></div><ul class="pre-numbering"><li>1</li><li>2</li><li>3</li><li>4</li><li>5</li><li>6</li><li>7</li><li>8</li><li>9</li><li>10</li><li>11</li><li>12</li><li>13</li><li>14</li><li>15</li><li>16</li><li>17</li><li>18</li><li>19</li><li>20</li><li>21</li><li>22</li><li>23</li></ul>

    代码分析:

    重试图

    修改我们的 MainActivity 里代码:

    <code class="hljs java has-numbering"><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">MainActivity</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">AppCompatActivity</span> {</span>
    
        <span class="hljs-keyword">private</span> SimpleDraweeView simpleDraweeView;
    
        <span class="hljs-annotation">@Override</span>
        <span class="hljs-keyword">protected</span> <span class="hljs-keyword">void</span> <span class="hljs-title">onCreate</span>(Bundle savedInstanceState) {
            <span class="hljs-keyword">super</span>.onCreate(savedInstanceState);
            Fresco.initialize(<span class="hljs-keyword">this</span>);
            setContentView(R.layout.activity_main);
            initView();
        }
    
        <span class="hljs-keyword">private</span> <span class="hljs-keyword">void</span> <span class="hljs-title">initView</span>() {
            <span class="hljs-comment">//创建SimpleDraweeView对象</span>
            simpleDraweeView = (SimpleDraweeView) findViewById(R.id.main_sdv);
            <span class="hljs-comment">//创建将要下载的图片的URI</span>
            Uri imageUri = Uri.parse(<span class="hljs-string">"http://my.csdn.net/uploads/avatar_y1scp.jpg"</span>);
            <span class="hljs-comment">//开始下载</span>
            simpleDraweeView.setImageURI(imageUri);
    
            <span class="hljs-comment">//创建DraweeController</span>
            DraweeController controller = Fresco.newDraweeControllerBuilder()
                    <span class="hljs-comment">//加载的图片URI地址</span>
                    .setUri(imageUri)
                    <span class="hljs-comment">//设置点击重试是否开启</span>
                    .setTapToRetryEnabled(<span class="hljs-keyword">true</span>)
                    <span class="hljs-comment">//设置旧的Controller</span>
                    .setOldController(simpleDraweeView.getController())
                    <span class="hljs-comment">//构建</span>
                    .build();
    
            <span class="hljs-comment">//设置DraweeController</span>
            simpleDraweeView.setController(controller);
        }
    }</code><ul class="pre-numbering"><li>1</li><li>2</li><li>3</li><li>4</li><li>5</li><li>6</li><li>7</li><li>8</li><li>9</li><li>10</li><li>11</li><li>12</li><li>13</li><li>14</li><li>15</li><li>16</li><li>17</li><li>18</li><li>19</li><li>20</li><li>21</li><li>22</li><li>23</li><li>24</li><li>25</li><li>26</li><li>27</li><li>28</li><li>29</li><li>30</li><li>31</li><li>32</li><li>33</li><li>34</li><li>35</li></ul><div class="save_code tracking-ad" data-mod="popu_249"><a target=_blank href="javascript:;" target="_blank"><img src="http://static.blog.csdn.net/images/save_snippets.png" alt="" /></a></div><ul class="pre-numbering"><li>1</li><li>2</li><li>3</li><li>4</li><li>5</li><li>6</li><li>7</li><li>8</li><li>9</li><li>10</li><li>11</li><li>12</li><li>13</li><li>14</li><li>15</li><li>16</li><li>17</li><li>18</li><li>19</li><li>20</li><li>21</li><li>22</li><li>23</li><li>24</li><li>25</li><li>26</li><li>27</li><li>28</li><li>29</li><li>30</li><li>31</li><li>32</li><li>33</li><li>34</li><li>35</li></ul>

    代码说明:

    重试图片

    运行效果:

    重试图效果

    注意:

    重复加载4次还是没有加载出来的时候才会显示 failureImage(失败图) 的图片

  • 淡入淡出动画—fadeDuration:

    修改我们刚刚书写的 activity_main.xml

    <code class="hljs avrasm has-numbering"><RelativeLayout xmlns:android=<span class="hljs-string">"http://schemas.android.com/apk/res/android"</span>
        xmlns:fresco=<span class="hljs-string">"http://schemas.android.com/apk/res-auto"</span>
        android:layout_width=<span class="hljs-string">"match_parent"</span>
        android:layout_height=<span class="hljs-string">"match_parent"</span>>
    
        <<span class="hljs-keyword">com</span><span class="hljs-preprocessor">.facebook</span><span class="hljs-preprocessor">.drawee</span><span class="hljs-preprocessor">.view</span><span class="hljs-preprocessor">.SimpleDraweeView</span>
            android:id=<span class="hljs-string">"@+id/main_sdv"</span>
            android:layout_width=<span class="hljs-string">"100dp"</span>
            android:layout_height=<span class="hljs-string">"100dp"</span>
            android:layout_centerInParent=<span class="hljs-string">"true"</span>
            fresco:actualImageScaleType=<span class="hljs-string">"focusCrop"</span>
            fresco:placeholderImage=<span class="hljs-string">"@mipmap/icon_placeholder"</span>
            fresco:placeholderImageScaleType=<span class="hljs-string">"fitCenter"</span>
            fresco:progressBarImage=<span class="hljs-string">"@mipmap/icon_progress_bar"</span>
            fresco:progressBarImageScaleType=<span class="hljs-string">"centerInside"</span>
            fresco:progressBarAutoRotateInterval=<span class="hljs-string">"5000"</span>
            fresco:failureImage=<span class="hljs-string">"@mipmap/icon_failure"</span>
            fresco:failureImageScaleType=<span class="hljs-string">"centerInside"</span>
            fresco:retryImage=<span class="hljs-string">"@mipmap/icon_retry"</span>
            fresco:retryImageScaleType=<span class="hljs-string">"centerCrop"</span>
            fresco:fadeDuration=<span class="hljs-string">"5000"</span>
            ></<span class="hljs-keyword">com</span><span class="hljs-preprocessor">.facebook</span><span class="hljs-preprocessor">.drawee</span><span class="hljs-preprocessor">.view</span><span class="hljs-preprocessor">.SimpleDraweeView</span>>
    
    </RelativeLayout></code><ul class="pre-numbering"><li>1</li><li>2</li><li>3</li><li>4</li><li>5</li><li>6</li><li>7</li><li>8</li><li>9</li><li>10</li><li>11</li><li>12</li><li>13</li><li>14</li><li>15</li><li>16</li><li>17</li><li>18</li><li>19</li><li>20</li><li>21</li><li>22</li><li>23</li><li>24</li></ul><div class="save_code tracking-ad" data-mod="popu_249"><a target=_blank href="javascript:;" target="_blank"><img src="http://static.blog.csdn.net/images/save_snippets.png" alt="" /></a></div><ul class="pre-numbering"><li>1</li><li>2</li><li>3</li><li>4</li><li>5</li><li>6</li><li>7</li><li>8</li><li>9</li><li>10</li><li>11</li><li>12</li><li>13</li><li>14</li><li>15</li><li>16</li><li>17</li><li>18</li><li>19</li><li>20</li><li>21</li><li>22</li><li>23</li><li>24</li></ul>

    代码说明:

    淡入淡出动画

    MainActivity 中的代码无需修改。

    运行效果:

    重试+进度图+失败图 进度图+正确图
    淡入淡出动画 正确图
  • 背景图—backgroundImage:

    这里呢,我们的背景图采用的是一个系统所提供的颜色中的一种。

    修改我们刚刚书写的 activity_main.xml

    <code class="hljs avrasm has-numbering"><RelativeLayout xmlns:android=<span class="hljs-string">"http://schemas.android.com/apk/res/android"</span>
        xmlns:fresco=<span class="hljs-string">"http://schemas.android.com/apk/res-auto"</span>
        android:layout_width=<span class="hljs-string">"match_parent"</span>
        android:layout_height=<span class="hljs-string">"match_parent"</span>>
    
        <<span class="hljs-keyword">com</span><span class="hljs-preprocessor">.facebook</span><span class="hljs-preprocessor">.drawee</span><span class="hljs-preprocessor">.view</span><span class="hljs-preprocessor">.SimpleDraweeView</span>
            android:id=<span class="hljs-string">"@+id/main_sdv"</span>
            android:layout_width=<span class="hljs-string">"100dp"</span>
            android:layout_height=<span class="hljs-string">"100dp"</span>
            android:layout_centerInParent=<span class="hljs-string">"true"</span>
            fresco:actualImageScaleType=<span class="hljs-string">"focusCrop"</span>
            fresco:fadeDuration=<span class="hljs-string">"5000"</span>
            fresco:backgroundImage=<span class="hljs-string">"@android:color/holo_orange_light"</span>
            ></<span class="hljs-keyword">com</span><span class="hljs-preprocessor">.facebook</span><span class="hljs-preprocessor">.drawee</span><span class="hljs-preprocessor">.view</span><span class="hljs-preprocessor">.SimpleDraweeView</span>>
    
    </RelativeLayout></code><ul class="pre-numbering"><li>1</li><li>2</li><li>3</li><li>4</li><li>5</li><li>6</li><li>7</li><li>8</li><li>9</li><li>10</li><li>11</li><li>12</li><li>13</li><li>14</li><li>15</li><li>16</li></ul><div class="save_code tracking-ad" data-mod="popu_249"><a target=_blank href="javascript:;" target="_blank"><img src="http://static.blog.csdn.net/images/save_snippets.png" alt="" /></a></div><ul class="pre-numbering"><li>1</li><li>2</li><li>3</li><li>4</li><li>5</li><li>6</li><li>7</li><li>8</li><li>9</li><li>10</li><li>11</li><li>12</li><li>13</li><li>14</li><li>15</li><li>16</li></ul>

    代码说明:

    背景图

    MainActivity 中的代码无需修改,运行效果:

    背景效果

  • 叠加图—overlayImage:

    这里呢,我们的背景图采用的是一个系统所提供的颜色中的一种。

    修改我们刚刚书写的 activity_main.xml

    <code class="hljs avrasm has-numbering"><RelativeLayout xmlns:android=<span class="hljs-string">"http://schemas.android.com/apk/res/android"</span>
        xmlns:fresco=<span class="hljs-string">"http://schemas.android.com/apk/res-auto"</span>
        android:layout_width=<span class="hljs-string">"match_parent"</span>
        android:layout_height=<span class="hljs-string">"match_parent"</span>>
    
        <<span class="hljs-keyword">com</span><span class="hljs-preprocessor">.facebook</span><span class="hljs-preprocessor">.drawee</span><span class="hljs-preprocessor">.view</span><span class="hljs-preprocessor">.SimpleDraweeView</span>
            android:id=<span class="hljs-string">"@+id/main_sdv"</span>
            android:layout_width=<span class="hljs-string">"100dp"</span>
            android:layout_height=<span class="hljs-string">"100dp"</span>
            android:layout_centerInParent=<span class="hljs-string">"true"</span>
            fresco:actualImageScaleType=<span class="hljs-string">"focusCrop"</span>
            fresco:placeholderImage=<span class="hljs-string">"@mipmap/icon_placeholder"</span>
            fresco:placeholderImageScaleType=<span class="hljs-string">"fitCenter"</span>
            fresco:progressBarImage=<span class="hljs-string">"@mipmap/icon_progress_bar"</span>
            fresco:progressBarImageScaleType=<span class="hljs-string">"centerInside"</span>
            fresco:progressBarAutoRotateInterval=<span class="hljs-string">"5000"</span>
            fresco:failureImage=<span class="hljs-string">"@mipmap/icon_failure"</span>
            fresco:failureImageScaleType=<span class="hljs-string">"centerInside"</span>
            fresco:retryImage=<span class="hljs-string">"@mipmap/icon_retry"</span>
            fresco:retryImageScaleType=<span class="hljs-string">"centerCrop"</span>
            fresco:fadeDuration=<span class="hljs-string">"5000"</span>
            fresco:backgroundImage=<span class="hljs-string">"@android:color/holo_orange_light"</span>
            fresco:pressedStateOverlayImage=<span class="hljs-string">"@android:color/holo_green_dark"</span>
            fresco:overlayImage=<span class="hljs-string">"@android:color/black"</span>
            ></<span class="hljs-keyword">com</span><span class="hljs-preprocessor">.facebook</span><span class="hljs-preprocessor">.drawee</span><span class="hljs-preprocessor">.view</span><span class="hljs-preprocessor">.SimpleDraweeView</span>>
    
    </RelativeLayout></code><ul class="pre-numbering"><li>1</li><li>2</li><li>3</li><li>4</li><li>5</li><li>6</li><li>7</li><li>8</li><li>9</li><li>10</li><li>11</li><li>12</li><li>13</li><li>14</li><li>15</li><li>16</li><li>17</li><li>18</li><li>19</li><li>20</li><li>21</li><li>22</li><li>23</li><li>24</li><li>25</li><li>26</li><li>27</li></ul><div class="save_code tracking-ad" data-mod="popu_249"><a target=_blank href="javascript:;" target="_blank"><img src="http://static.blog.csdn.net/images/save_snippets.png" alt="" /></a></div><ul class="pre-numbering"><li>1</li><li>2</li><li>3</li><li>4</li><li>5</li><li>6</li><li>7</li><li>8</li><li>9</li><li>10</li><li>11</li><li>12</li><li>13</li><li>14</li><li>15</li><li>16</li><li>17</li><li>18</li><li>19</li><li>20</li><li>21</li><li>22</li><li>23</li><li>24</li><li>25</li><li>26</li><li>27</li></ul>

    代码说明:

    叠加图

    MainActivity 中的代码无需修改。

    运行效果:

    叠加图

    从运行效果来看,叠加图在最上面,覆盖了下面的图。

  • 圆形图—roundAsCircle:

    一行代码搞定圆形图:设置roundAsCircle为true;

    修改我们刚刚书写的 activity_main.xml

    <code class="hljs avrasm has-numbering"><RelativeLayout xmlns:android=<span class="hljs-string">"http://schemas.android.com/apk/res/android"</span>
        xmlns:fresco=<span class="hljs-string">"http://schemas.android.com/apk/res-auto"</span>
        android:layout_width=<span class="hljs-string">"match_parent"</span>
        android:layout_height=<span class="hljs-string">"match_parent"</span>>
    
        <<span class="hljs-keyword">com</span><span class="hljs-preprocessor">.facebook</span><span class="hljs-preprocessor">.drawee</span><span class="hljs-preprocessor">.view</span><span class="hljs-preprocessor">.SimpleDraweeView</span>
            android:id=<span class="hljs-string">"@+id/main_sdv"</span>
            android:layout_width=<span class="hljs-string">"100dp"</span>
            android:layout_height=<span class="hljs-string">"100dp"</span>
            android:layout_centerInParent=<span class="hljs-string">"true"</span>
            fresco:actualImageScaleType=<span class="hljs-string">"focusCrop"</span>
            fresco:placeholderImage=<span class="hljs-string">"@mipmap/icon_placeholder"</span>
            fresco:placeholderImageScaleType=<span class="hljs-string">"fitCenter"</span>
            fresco:progressBarImage=<span class="hljs-string">"@mipmap/icon_progress_bar"</span>
            fresco:progressBarImageScaleType=<span class="hljs-string">"centerInside"</span>
            fresco:progressBarAutoRotateInterval=<span class="hljs-string">"5000"</span>
            fresco:failureImage=<span class="hljs-string">"@mipmap/icon_failure"</span>
            fresco:failureImageScaleType=<span class="hljs-string">"centerInside"</span>
            fresco:retryImage=<span class="hljs-string">"@mipmap/icon_retry"</span>
            fresco:retryImageScaleType=<span class="hljs-string">"centerCrop"</span>
            fresco:fadeDuration=<span class="hljs-string">"5000"</span>
            fresco:backgroundImage=<span class="hljs-string">"@android:color/holo_orange_light"</span>
            fresco:roundAsCircle=<span class="hljs-string">"true"</span>
            ></<span class="hljs-keyword">com</span><span class="hljs-preprocessor">.facebook</span><span class="hljs-preprocessor">.drawee</span><span class="hljs-preprocessor">.view</span><span class="hljs-preprocessor">.SimpleDraweeView</span>>
    
    </RelativeLayout></code><ul class="pre-numbering"><li>1</li><li>2</li><li>3</li><li>4</li><li>5</li><li>6</li><li>7</li><li>8</li><li>9</li><li>10</li><li>11</li><li>12</li><li>13</li><li>14</li><li>15</li><li>16</li><li>17</li><li>18</li><li>19</li><li>20</li><li>21</li><li>22</li><li>23</li><li>24</li><li>25</li><li>26</li></ul><div class="save_code tracking-ad" data-mod="popu_249"><a target=_blank href="javascript:;" target="_blank"><img src="http://static.blog.csdn.net/images/save_snippets.png" alt="" /></a></div><ul class="pre-numbering"><li>1</li><li>2</li><li>3</li><li>4</li><li>5</li><li>6</li><li>7</li><li>8</li><li>9</li><li>10</li><li>11</li><li>12</li><li>13</li><li>14</li><li>15</li><li>16</li><li>17</li><li>18</li><li>19</li><li>20</li><li>21</li><li>22</li><li>23</li><li>24</li><li>25</li><li>26</li></ul>

    代码说明:

    圆形图

    MainActivity 中的代码无需修改。

    运行效果:

    circle

    可以看到,从图片开始加载一直到图片下载完毕,整个图像都是圆形的。

  • 圆角图—roundedCornerRadius:

    修改我们刚刚书写的 activity_main.xml

    <code class="hljs avrasm has-numbering"><RelativeLayout xmlns:android=<span class="hljs-string">"http://schemas.android.com/apk/res/android"</span>
        xmlns:fresco=<span class="hljs-string">"http://schemas.android.com/apk/res-auto"</span>
        android:layout_width=<span class="hljs-string">"match_parent"</span>
        android:layout_height=<span class="hljs-string">"match_parent"</span>>
    
        <<span class="hljs-keyword">com</span><span class="hljs-preprocessor">.facebook</span><span class="hljs-preprocessor">.drawee</span><span class="hljs-preprocessor">.view</span><span class="hljs-preprocessor">.SimpleDraweeView</span>
            android:id=<span class="hljs-string">"@+id/main_sdv"</span>
            android:layout_width=<span class="hljs-string">"100dp"</span>
            android:layout_height=<span class="hljs-string">"100dp"</span>
            android:layout_centerInParent=<span class="hljs-string">"true"</span>
            fresco:actualImageScaleType=<span class="hljs-string">"focusCrop"</span>
            fresco:placeholderImage=<span class="hljs-string">"@mipmap/icon_placeholder"</span>
            fresco:placeholderImageScaleType=<span class="hljs-string">"fitCenter"</span>
            fresco:progressBarImage=<span class="hljs-string">"@mipmap/icon_progress_bar"</span>
            fresco:progressBarImageScaleType=<span class="hljs-string">"centerInside"</span>
            fresco:progressBarAutoRotateInterval=<span class="hljs-string">"5000"</span>
            fresco:failureImage=<span class="hljs-string">"@mipmap/icon_failure"</span>
            fresco:failureImageScaleType=<span class="hljs-string">"centerInside"</span>
            fresco:retryImage=<span class="hljs-string">"@mipmap/icon_retry"</span>
            fresco:retryImageScaleType=<span class="hljs-string">"centerCrop"</span>
            fresco:fadeDuration=<span class="hljs-string">"5000"</span>
            fresco:backgroundImage=<span class="hljs-string">"@android:color/holo_orange_light"</span>
            fresco:roundedCornerRadius=<span class="hljs-string">"30dp"</span>
            ></<span class="hljs-keyword">com</span><span class="hljs-preprocessor">.facebook</span><span class="hljs-preprocessor">.drawee</span><span class="hljs-preprocessor">.view</span><span class="hljs-preprocessor">.SimpleDraweeView</span>>
    
    </RelativeLayout></code><ul class="pre-numbering"><li>1</li><li>2</li><li>3</li><li>4</li><li>5</li><li>6</li><li>7</li><li>8</li><li>9</li><li>10</li><li>11</li><li>12</li><li>13</li><li>14</li><li>15</li><li>16</li><li>17</li><li>18</li><li>19</li><li>20</li><li>21</li><li>22</li><li>23</li><li>24</li><li>25</li><li>26</li></ul><div class="save_code tracking-ad" data-mod="popu_249"><a target=_blank href="javascript:;" target="_blank"><img src="http://static.blog.csdn.net/images/save_snippets.png" alt="" /></a></div><ul class="pre-numbering"><li>1</li><li>2</li><li>3</li><li>4</li><li>5</li><li>6</li><li>7</li><li>8</li><li>9</li><li>10</li><li>11</li><li>12</li><li>13</li><li>14</li><li>15</li><li>16</li><li>17</li><li>18</li><li>19</li><li>20</li><li>21</li><li>22</li><li>23</li><li>24</li><li>25</li><li>26</li></ul>

    代码说明:

    圆角

    MainActivity 中的代码无需修改。

    运行效果:

    圆角运行图

    可以看到,从图片开始加载一直到图片下载完毕,整个图像都是圆角的。

    圆角属性 圆角属性
    左上角是否为圆角fresco:roundTopLeft="false" 右上角是否为圆角fresco:roundTopRight="false"
    topLeft topRight
    bottomLeft bottomRight
    左下角是否为圆角fresco:roundBottomLeft="false" 右下角是否为圆角fresco:roundBottomRight="false"

    注意:

    当我们同时设置图像显示为圆形图像和圆角图像时,只会显示为圆形图像。

    同时设置圆形圆角

  • 圆形圆角边框宽度及颜色—roundingBorder:

    修改我们刚刚书写的 activity_main.xml

    <code class="hljs avrasm has-numbering"><RelativeLayout xmlns:android=<span class="hljs-string">"http://schemas.android.com/apk/res/android"</span>
        xmlns:fresco=<span class="hljs-string">"http://schemas.android.com/apk/res-auto"</span>
        android:layout_width=<span class="hljs-string">"match_parent"</span>
        android:layout_height=<span class="hljs-string">"match_parent"</span>>
    
        <<span class="hljs-keyword">com</span><span class="hljs-preprocessor">.facebook</span><span class="hljs-preprocessor">.drawee</span><span class="hljs-preprocessor">.view</span><span class="hljs-preprocessor">.SimpleDraweeView</span>
            android:id=<span class="hljs-string">"@+id/main_sdv"</span>
            android:layout_width=<span class="hljs-string">"100dp"</span>
            android:layout_height=<span class="hljs-string">"100dp"</span>
            android:layout_centerInParent=<span class="hljs-string">"true"</span>
            fresco:actualImageScaleType=<span class="hljs-string">"focusCrop"</span>
            fresco:placeholderImage=<span class="hljs-string">"@mipmap/icon_placeholder"</span>
            fresco:placeholderImageScaleType=<span class="hljs-string">"focusCrop"</span>
            fresco:progressBarImage=<span class="hljs-string">"@mipmap/icon_progress_bar"</span>
            fresco:progressBarImageScaleType=<span class="hljs-string">"focusCrop"</span>
            fresco:progressBarAutoRotateInterval=<span class="hljs-string">"5000"</span>
            fresco:failureImage=<span class="hljs-string">"@mipmap/icon_failure"</span>
            fresco:failureImageScaleType=<span class="hljs-string">"focusCrop"</span>
            fresco:retryImage=<span class="hljs-string">"@mipmap/icon_retry"</span>
            fresco:retryImageScaleType=<span class="hljs-string">"focusCrop"</span>
            fresco:fadeDuration=<span class="hljs-string">"5000"</span>
            fresco:backgroundImage=<span class="hljs-string">"@android:color/holo_orange_light"</span>
            fresco:roundAsCircle=<span class="hljs-string">"true"</span>
            fresco:roundedCornerRadius=<span class="hljs-string">"30dp"</span>
            fresco:roundTopLeft=<span class="hljs-string">"true"</span>
            fresco:roundTopRight=<span class="hljs-string">"true"</span>
            fresco:roundBottomLeft=<span class="hljs-string">"true"</span>
            fresco:roundBottomRight=<span class="hljs-string">"true"</span>
            fresco:roundingBorderWidth=<span class="hljs-string">"10dp"</span>
            fresco:roundingBorderColor=<span class="hljs-string">"@android:color/black"</span>
            ></<span class="hljs-keyword">com</span><span class="hljs-preprocessor">.facebook</span><span class="hljs-preprocessor">.drawee</span><span class="hljs-preprocessor">.view</span><span class="hljs-preprocessor">.SimpleDraweeView</span>>
    
    </RelativeLayout></code><ul class="pre-numbering"><li>1</li><li>2</li><li>3</li><li>4</li><li>5</li><li>6</li><li>7</li><li>8</li><li>9</li><li>10</li><li>11</li><li>12</li><li>13</li><li>14</li><li>15</li><li>16</li><li>17</li><li>18</li><li>19</li><li>20</li><li>21</li><li>22</li><li>23</li><li>24</li><li>25</li><li>26</li><li>27</li><li>28</li><li>29</li><li>30</li><li>31</li><li>32</li><li>33</li></ul><div class="save_code tracking-ad" data-mod="popu_249"><a target=_blank href="javascript:;" target="_blank"><img src="http://static.blog.csdn.net/images/save_snippets.png" alt="" /></a></div><ul class="pre-numbering"><li>1</li><li>2</li><li>3</li><li>4</li><li>5</li><li>6</li><li>7</li><li>8</li><li>9</li><li>10</li><li>11</li><li>12</li><li>13</li><li>14</li><li>15</li><li>16</li><li>17</li><li>18</li><li>19</li><li>20</li><li>21</li><li>22</li><li>23</li><li>24</li><li>25</li><li>26</li><li>27</li><li>28</li><li>29</li><li>30</li><li>31</li><li>32</li><li>33</li></ul>

    代码说明:

    圆形圆角边框

    MainActivity 中的代码无需修改。

    运行效果(左边显示的是带边框的圆形图像,右边显示的是带边框的圆角图像):

    圆形边框圆角边框

  • 圆形或圆角图像底下的叠加颜色—roundWithOverlayColor:

    修改我们刚刚书写的 activity_main.xml

    <code class="hljs avrasm has-numbering"><RelativeLayout xmlns:android=<span class="hljs-string">"http://schemas.android.com/apk/res/android"</span>
        xmlns:fresco=<span class="hljs-string">"http://schemas.android.com/apk/res-auto"</span>
        android:layout_width=<span class="hljs-string">"match_parent"</span>
        android:layout_height=<span class="hljs-string">"match_parent"</span>>
    
        <<span class="hljs-keyword">com</span><span class="hljs-preprocessor">.facebook</span><span class="hljs-preprocessor">.drawee</span><span class="hljs-preprocessor">.view</span><span class="hljs-preprocessor">.SimpleDraweeView</span>
            android:id=<span class="hljs-string">"@+id/main_sdv"</span>
            android:layout_width=<span class="hljs-string">"100dp"</span>
            android:layout_height=<span class="hljs-string">"100dp"</span>
            android:layout_centerInParent=<span class="hljs-string">"true"</span>
            fresco:actualImageScaleType=<span class="hljs-string">"focusCrop"</span>
            fresco:placeholderImage=<span class="hljs-string">"@mipmap/icon_placeholder"</span>
            fresco:placeholderImageScaleType=<span class="hljs-string">"focusCrop"</span>
            fresco:progressBarImage=<span class="hljs-string">"@mipmap/icon_progress_bar"</span>
            fresco:progressBarImageScaleType=<span class="hljs-string">"focusCrop"</span>
            fresco:progressBarAutoRotateInterval=<span class="hljs-string">"5000"</span>
            fresco:failureImage=<span class="hljs-string">"@mipmap/icon_failure"</span>
            fresco:failureImageScaleType=<span class="hljs-string">"focusCrop"</span>
            fresco:retryImage=<span class="hljs-string">"@mipmap/icon_retry"</span>
            fresco:retryImageScaleType=<span class="hljs-string">"focusCrop"</span>
            fresco:fadeDuration=<span class="hljs-string">"5000"</span>
            fresco:backgroundImage=<span class="hljs-string">"@android:color/holo_orange_light"</span>
            fresco:roundWithOverlayColor=<span class="hljs-string">"@android:color/darker_gray"</span>
            fresco:roundAsCircle=<span class="hljs-string">"true"</span>
            ></<span class="hljs-keyword">com</span><span class="hljs-preprocessor">.facebook</span><span class="hljs-preprocessor">.drawee</span><span class="hljs-preprocessor">.view</span><span class="hljs-preprocessor">.SimpleDraweeView</span>>
    
    </RelativeLayout></code><ul class="pre-numbering"><li>1</li><li>2</li><li>3</li><li>4</li><li>5</li><li>6</li><li>7</li><li>8</li><li>9</li><li>10</li><li>11</li><li>12</li><li>13</li><li>14</li><li>15</li><li>16</li><li>17</li><li>18</li><li>19</li><li>20</li><li>21</li><li>22</li><li>23</li><li>24</li><li>25</li><li>26</li><li>27</li></ul><div class="save_code tracking-ad" data-mod="popu_249"><a target=_blank href="javascript:;" target="_blank"><img src="http://static.blog.csdn.net/images/save_snippets.png" alt="" /></a></div><ul class="pre-numbering"><li>1</li><li>2</li><li>3</li><li>4</li><li>5</li><li>6</li><li>7</li><li>8</li><li>9</li><li>10</li><li>11</li><li>12</li><li>13</li><li>14</li><li>15</li><li>16</li><li>17</li><li>18</li><li>19</li><li>20</li><li>21</li><li>22</li><li>23</li><li>24</li><li>25</li><li>26</li><li>27</li></ul>

    代码说明:

    底下的颜色

    MainActivity 中的代码无需修改。

    运行效果(左边为圆形效果,右边为圆角效果):

    圆形效果圆角效果

  • 缩放类型—ScaleType:

    类型 描述
    center 居中,无缩放
    centerCrop 保持宽高比缩小或放大,使得两边都大于或等于显示边界。居中显示。
    focusCrop 同centerCrop, 但居中点不是中点,而是指定的某个点
    centerInside 使两边都在显示边界内,居中显示。如果图尺寸大于显示边界,则保持长宽比缩小图片。
    fitCenter 保持宽高比,缩小或者放大,使得图片完全显示在显示边界内。居中显示
    fitStart 同上。但不居中,和显示边界左上对齐
    fitEnd 同fitCenter, 但不居中,和显示边界右下对齐
    fitXY 不保存宽高比,填充满显示边界
    none 如要使用tile mode显示, 需要设置为none

    推荐使用:focusCrop 类型

    Fresco中文说明对这一点也有详情的说明: 缩放

  • 总结:

    XML属性 意义
    fadeDuration 淡入淡出动画持续时间(单位:毫秒ms)
    actualImageScaleType 实际图像的缩放类型
    placeholderImage 占位图
    placeholderImageScaleType 占位图的缩放类型
    progressBarImage 进度图
    progressBarImageScaleType 进度图的缩放类型
    progressBarAutoRotateInterval 进度图自动旋转间隔时间(单位:毫秒ms)
    failureImage 失败图
    failureImageScaleType 失败图的缩放类型
    retryImage 重试图
    retryImageScaleType 重试图的缩放类型
    backgroundImage 背景图
    overlayImage 叠加图
    pressedStateOverlayImage 按压状态下所显示的叠加图
    roundAsCircle 设置为圆形图
    roundedCornerRadius 圆角半径
    roundTopLeft 左上角是否为圆角
    roundTopRight 右上角是否为圆角
    roundBottomLeft 左下角是否为圆角
    roundBottomRight 右下角是否为圆角
    roundingBorderWidth 圆形或者圆角图边框的宽度
    roundingBorderColor 圆形或者圆角图边框的颜色
    roundWithOverlayColor 圆形或者圆角图底下的叠加颜色(只能设置颜色)
    viewAspectRatio 控件纵横比
  • GitHub:

    本教程最终项目GitHub地址:https://github.com/scp504677840/Fresco

作者:ai_yong_jie 发表于2016/8/24 17:23:11 原文链接
阅读:32 评论:0 查看评论

Android Studio NDK 入门教程(5)--Java对象的传递与修改

$
0
0

概述

本文主要Java与C++之间的对象传递与取值。包括传递Java对象、返回Java对象、修改Java对象、以及性能对比。

通过JNIEnv完成数据转换

Java对象是存在于JVM虚拟机中的,而C++是脱离JVM而运行的,如果在C++中访问和使用Java中的对象,必然会使用JNIEnv这个桥梁。其实通过下面的代码很容易看出,这种访问方式和Java中的反射十分雷同。

这里定义一个简单Java对象用于下文测试:

package com.example.wastrel.hellojni;
/**
 * Created by wastrel on 2016/8/24.
 */
public class Bean {
    private String msg;
    private int what;

    public Bean(String msg,int what)
    {
        this.msg=msg;
        this.what=what;
    }


    public String getMsg() {
        return msg;
    }

    public void setMsg(String msg) {
        this.msg = msg;
    }

    public int getWhat() {
        return what;
    }

    public void setWhat(int what) {
        this.what = what;
    }

    @Override
    public String toString() {
        return "Msg:"+msg+";What:"+what;
    }
}

从C++中创建一个Java对象并返回

    //Java中的native方法声明
    public native Bean newBean(String msg,int what);
//C++中的方法实现
JNIEXPORT jobject JNICALL Java_com_example_wastrel_hellojni_HelloJNI_newBean
        (JNIEnv *env, jobject obj, jstring msg,jint what){
    //先找到class
    jclass bean_clz=env->FindClass("com/example/wastrel/hellojni/Bean");
    //在实际应用中应该确保你的class、method、field存在。减少此类判断。
    if(bean_clz==NULL)
    {
        LOGE("can't find class");
        return NULL;
    }
    //获取构造函数。构造函数的返回值是void,因此这里方法签名最后为V
    jmethodID bean_init=env->GetMethodID(bean_clz,"<init>","(Ljava/lang/String;I)V");
    if(bean_init==NULL)
    {
        LOGE("can't find init function");
        return NULL;
    }
    //然后调用构造函数获得bean
    jobject bean=env->NewObject(bean_clz,bean_init,msg,what);
    return bean;
}

注:如果提示找不到NULL 请include<stddef.h>

C++中解析Java对象

//java方法Native声明
public native String getString(Bean bean);
//C++中的方法实现
JNIEXPORT jstring JNICALL Java_com_example_wastrel_hellojni_HelloJNI_getString
        (JNIEnv *env, jobject obj,jobject bean){
    jclass bean_clz=env->FindClass("com/example/wastrel/hellojni/Bean");

//这部分是通过get函数去获取对应的值 
//    jmethodID bean_getmsg=env->GetMethodID(bean_clz,"getMsg","()Ljava/lang/String;");
//    jmethodID bean_getwhat=env->GetMethodID(bean_clz,"getWhat","()I");
//    jstring jmsg=(jstring)env->CallObjectMethod(bean,bean_getmsg);
//    jint what=env->CallIntMethod(bean,bean_getwhat);

//这部分是通过类的成员变量直接取获取值,你可能注意到在Java中定义的变量都是private修饰的,但在反射的调用下是毫无作用的。
    jfieldID bean_fmsg=env->GetFieldID(bean_clz,"msg","Ljava/lang/String;");
    jfieldID bean_fwhat=env->GetFieldID(bean_clz,"what","I");
    jstring jmsg=(jstring)env->GetObjectField(bean,bean_fmsg);
    jint  what=env->GetIntField(bean,bean_fwhat);

//将拿到的值拼装一个String返回回去
    const char * msg=env->GetStringUTFChars(jmsg,NULL);
    char *str=new char[1024];
    sprintf(str,"Msg:%s;What:%d(From C++)",msg,what);
    jstring rs=env->NewStringUTF(str);
    delete  []str;
    env->ReleaseStringUTFChars(jmsg,msg);
    return rs;
}

注:sprintf函数包含在stdio.h头文件中

C++中修改Java对象属性值

//java方法Native声明
public native void ModifyBean(Bean bean);
//C++实现
JNIEXPORT void JNICALL Java_com_example_wastrel_hellojni_HelloJNI_ModifyBean
        (JNIEnv *env, jobject obj,jobject bean){
    jclass bean_clz=env->FindClass("com/example/wastrel/hellojni/Bean");
    jfieldID bean_fmsg=env->GetFieldID(bean_clz,"msg","Ljava/lang/String;");
    jfieldID bean_fwhat=env->GetFieldID(bean_clz,"what","I");
    jstring msg=env->NewStringUTF("Modify in C++");
    //重新设置属性
    env->SetObjectField(bean,bean_fmsg,msg);
    env->SetIntField(bean,bean_fwhat,20);
    return;
}

结果图

//java中调用代码
        HelloJNI helloJNI=new HelloJNI();
        Bean bean=helloJNI.newBean("This is from C++ bean",10);
        tv.setText(bean.toString());
        bean=new Bean("This is from Java bean",15);
        tv.append("\n"+helloJNI.getString(bean));
        helloJNI.ModifyBean(bean);
        tv.append("\n"+bean.toString());

这里写图片描述

Java中new Object和C++中new Object的性能对比

下面我们通过一个测试函数来比较通过两种方式的性能,这里可以毫无疑问的告诉你,Java一定比C++的快。那么这个对比的意义就在于,使用C++创建Java对象的时候会不会造成不可接受的卡顿。
这里使用的测试机是华为Mate7,具体硬件配置可自行百度。
测试函数如下:

     void Test(int count)
    {
        long startTime=System.currentTimeMillis();
        for (int i=0;i<count;i++)
        {
            new Bean("123",i);
        }
        long endTime=System.currentTimeMillis();
        Log.e("Java","Java new "+count+"s waste "+(endTime-startTime)+"ms");

        HelloJNI helloJNI=new HelloJNI();
       startTime=System.currentTimeMillis();
        for (int i=0;i<count;i++)
        {
            helloJNI.newBean("123",i);
        }
        endTime=System.currentTimeMillis();
        Log.e("C++","C++ new "+count+"s waste "+(endTime-startTime)+"ms");
    }

测试结果:

Java: Java new 5000s waste 3ms
C++: C++ new 5000s waste 38ms

Java: Java new 10000s waste 6ms
C++: C++ new 10000s waste 79ms

Java: Java new 50000s waste 56ms
C++: C++ new 50000s waste 338ms

Java: Java new 100000s waste 60ms
C++: C++ new 100000s waste 687ms

通过结果可以看出,通过C++来new对象比Java慢了足足10倍左右。但是从时间上来讲,如果只是在C++中new一个Java对象,几个微秒的时间差距完全是可以忽略不计的。

也许有人就会说,C++慢那么多是因为每次都在FindClass,GetMethodId,而在程序运行过程中这两个值是不会改变的。听起来确实有这样一个原因,下面我们将C++中的代码稍作修改缓存jclass和jmethodId。
修改后的newBean函数:

//用静态变量缓存
static jclass bean_clz=NULL;
static jmethodID bean_init=NULL;
JNIEXPORT jobject JNICALL Java_com_example_wastrel_hellojni_HelloJNI_newBean
        (JNIEnv *env, jobject obj, jstring str,jint what){
    //先找到class
    if(bean_clz==NULL)
    {
        jclass  _bean_clz=env->FindClass("com/example/wastrel/hellojni/Bean");
        bean_clz=(jclass)env->NewGlobalRef(_bean_clz);
    }
    //获取构造函数。构造函数的返回值是void,因此这里方法签名最后为V
    if(bean_init==NULL)
    {
        bean_init=env->GetMethodID(bean_clz,"<init>","(Ljava/lang/String;I)V");
    }
    //然后调用构造函数获得bean
    jobject bean=env->NewObject(bean_clz,bean_init,str,what);
    return bean;
}

 你可能发现了缓存方法ID和缓存jclass似乎不一样。那是因为jclass其实是java.lang.Class对象,而方法ID是JNI中定义的一个结构体。如果这里不使用env—>NewGlobalRef()函数声明其是一个全局引用的话,在运行的时候可能就会报错:JNI ERROR (app bug): accessed stale local reference 0x5900021;表明在Jvm中该对象已经被回收了,引用已经失效了。而NewGlobalRef的作用就在于告诉JVM,C++中一直持有该引用,请不要回收。显然这又引发了另外一个问题,你需要在你不需要该引用的时候告诉JVM,那么就需要调用env->DelGlobalRef()。当然你也可以不调用,那么该Java对象将在你的程序关闭的时候被回收。

测试结果:

Java: Java new 5000s waste 3ms
C++: C++ new 5000s waste 18ms

Java: Java new 10000s waste 5ms
C++: C++ new 10000s waste 24ms

Java: Java new 50000s waste 44ms
C++: C++ new 50000s waste 121ms

Java: Java new 100000s waste 65ms
C++: C++ new 100000s waste 259ms

这次的结果表明,如果缓存方法ID和jclass能缩短一半的时间。但仍然不如Java快。这也很好理解,C++创建Java对象最终还是通过Java创建的,反复的通过反射去创建自然不如自身创建来得快。

总结

  • JNI中想访问Java Object方法签名、类名和变量名十分重要,一旦确定了就不要轻易单方面修改Java中的定义。因为这会导致JNI找不到相关的方法或类等,而引发JNI错误。
  • 虽然JNI提供了各种方法来完成Java的反射操作,但是请酌情使用,因为这会让Java代码与C++代码之间过度依赖。
  • 当你需要返回C++中的结构体数据的时候,可以考虑把结构体转换成对应的Java对象返回。
作者:venusic 发表于2016/8/24 17:34:09 原文链接
阅读:30 评论:0 查看评论

Android适配器视图与适配器AdapterView & Adapter

$
0
0

一、适配器视图与适配器AdapterView& Adapter

        适配器视图AdapterView继承自视图组ViewGroup (一个包含其他子视图的容器),它是需要适配器的视图容器,常用的适配器视图有 Spinner、ListView、GridView、Gallery、ViewPager。

        适配器视图是一种特殊类型的视图组。与其他的视图组类型一样,适配器视图的主要用途是表示一个包含子视图的视图。因此,适配器视图确定了其子视图布局的表现形式。此外,它还在关系中扮演了其他一些特殊角色:适配器视图控制屏幕上所显示的项目数量:这是一个非常关键的认识。虽然适配器对需要显示的数据绑定视图的移交进行控制,但却由适配器视图来告诉适配器应该生成多少视图。这一点在数据绑定过程中至关重要,因为它可以考虑到各种屏幕大小,同时帮助内存管理。后面将介绍更多这方面的内容。

        适配器视图包含对项目选择响应事件,以及请求绑定数据实体的机制:当使用适配器视图时,可以根据用户交互简单地构建响应布局。此外,还可以非常容易地访问相应的数据项,而不必通过极端方法来查找原始数据项。

        适配器视图可以支持使其子视图具有动画效果的逻辑:当使用诸如垂直或者水平滚动列表之类的控件时,如果可以引入一些平滑的动画效果那就更好了,这样可以改善用户的体验。可从ViewGroup类中继承该操作。

        适配器在视图与数据之间扮演了一个桥梁的作用,它将数据中的每一项数据转化为适配器视图可以使用的每一个视图项。

适配器视图与适配器—类图:

        该图中,左侧类图是关于适配器Adapter的,可以看到BaseAdapter抽象类实现了ListAdapter和SpinnerAdapter这两个接口,在我们自定义的适配器中,就是要继承BaseAdapter这个类。右侧类图是关于适配器视图AdapterView的,最下面的那四个具体类Spinner、Gallery、ListView、GridView就是常用的适配器视图。

下面讲解几个实例,练习使用Spinner和ListView适配器视图。

Spinner (下拉列表)有两种定义方式

  1. 使用静态资源

                使用资源文件中的字符串数组,数据是固定的;

                这是最常用、最简单的方式。

         2.使用适配器

                使用数组适配器(ArrayAdapter),数据长度可变。

二、实例--Spinner使用静态资源

1.在 strings.xml 中定义字符串数据,用作静态资源。

2.可以在 Java 中使用以下方法获得资源中的数组数据

        String[] skills =getResources().getStringArray(R.array.skills);

3.在布局文件中,定义Spinner适配器视图,并引用字符串静态资源:

4.逻辑部分的实现:

        在主活动Java代码中,通过id获取到XML布局文件定义的Spinner适配器视图控件,并给它设置下拉列表项被选择监听事件,spinner.setOnItemSelectedListener(),将用户选择的下拉选项,显示在一个TextView上。

 

三、实例--Spinner使用适配器

        在主活动.java文件中,先声明适配器视图Spinner、数据和适配器,创建好适配器,并给Spinner设置适配器,适配器将视图和数据一项一项的联系起来。上图可见,数据是一个ArrayList,此次数据是可以变化的,初始化的时候,给它add()了几个数据,设置该数据可以从网络上获得。

XML布局文件--显示效果如图:

实现功能:从下拉列表中,可以删除选中的项,也可以增加新项。

(1)XML布局文件:activity_spinner2.xml

<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"
   
android:paddingLeft="@dimen/activity_horizontal_margin"
   
android:paddingRight="@dimen/activity_horizontal_margin"
   
android:paddingTop="@dimen/activity_vertical_margin"
   
android:paddingBottom="@dimen/activity_vertical_margin"
   
tools:context=".MainActivity"
>

    <TextView
       
android:layout_width="wrap_content"
       
android:layout_height="wrap_content"
       
android:text="专业技能"
       
android:id="@+id/textView"
       
android:layout_alignParentTop="true"
       
android:layout_alignBottom="@+id/spinner"
       
android:gravity="center_vertical"
       
android:layout_marginRight="16dp"
/>

    <Spinner
       
android:layout_width="wrap_content"
       
android:layout_height="wrap_content"
       
android:id="@+id/spinner"
       
android:layout_alignParentTop="true"
       
android:layout_toRightOf="@+id/button_remove"
       
android:layout_toEndOf="@+id/button_remove"
/>

    <Button
       
android:layout_width="wrap_content"
       
android:layout_height="wrap_content"
       
android:text="删除选中项"
       
android:id="@+id/button_remove"
       
android:layout_below="@+id/spinner"
       
android:onClick="doRemove"
/>

    <EditText
       
android:layout_width="wrap_content"
       
android:layout_height="wrap_content"
       
android:id="@+id/editText"
       
android:singleLine="true"
       
android:hint="已掌握的的技能"
       
android:layout_below="@+id/button_remove"
       
android:layout_toRightOf="@+id/button_add"
       
android:layout_toEndOf="@+id/button_add"
/>

    <Button
       
android:layout_width="wrap_content"
       
android:layout_height="wrap_content"
       
android:text="添加新选项"
       
android:id="@+id/button_add"
       
android:layout_alignBottom="@+id/editText"
       
android:onClick="doAdd"
/>

</RelativeLayout>

(2)活动类文件Spinner2Activity.java

package com.example.administrator.adapterdemo;

import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.widget.ArrayAdapter;
import android.widget.EditText;
import android.widget.Spinner;
import android.widget.Toast;
import java.util.ArrayList;

/**
 * 使用Spinner适配器视图
 * 动态进行数据的处理操作
 */
public class Spinner2Activity extends AppCompatActivity {

    // 视图
    private Spinner spinner;

    // 数据
    private ArrayList<String> data;

    // 适配器:视图与数据之间的桥
    private ArrayAdapter<String> adapter;

    private EditText editText;

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

        spinner = (Spinner) findViewById(R.id.spinner);
        editText = (EditText) findViewById(R.id.editText);

        initView();//初始化
    }

    private void initView() {

        // 初始化数据:可以数据源或网络获取
        data = new ArrayList<>();
        data.add("Android");
        data.add("iOS");
        data.add("Java");
        data.add("C++");

        // 参数一:上下文(Context)
        // 参数二:系统中的布局资源 android.R.layout.simple_spinner_dropdown_item
        // 参数三:数据,可以是字符串数据【长度不可变】,也可以是 ArrayList【长度可变】
        adapter = new ArrayAdapter<String>(this, android.R.layout.simple_spinner_dropdown_item, data);

        // 将适配器设置为视图
        spinner.setAdapter(adapter);
    }

    /**
     * Spinner 中删除一项
     *
     * @param v button_remove
     */
    public void doRemove(View v) {

        // 获得选中项的位置;若无,则返回 -1
        int position = spinner.getSelectedItemPosition();

        if (-1 == position) {
            Toast.makeText(this, "无选中项", Toast.LENGTH_SHORT).show();
        } else {

            // 从数据中删除当前选中项
            data.remove(position);

            // 适配器通知【视图】数据集已改变,视图会重绘
            adapter.notifyDataSetChanged();
        }
    }

    /**
     * Spinner 中新增一项
     *
     * @param v button_add
     */
    public void doAdd(View v) {
        // 获得输入内容
        String input = editText.getText().toString();

        if (input.equals("")) {
            return;       //防止添加空行”
        }

        editText.setText("");

        // 在数据中添加新内容
        data.add(input);

        // 获得列表的适配器中数据项的总数
        int size = spinner.getAdapter().getCount();
        //size = data.size();

        // 设置选中选的位置
        spinner.setSelection(size - 1);
    }

}

四、ListView实例

1.ListView列表视图:

       由多行构成

       每行一个视图项

       行数由数据决定

       可单选、多选

       是最常用的适配器视图

2.ListView设置选择模式(setChoiceMode)

模式

描述

CHOICE_MODE_NONE

普通模式

CHOICE_MODE_SINGLE

单选模式

CHOICE_MODE_MULTIPLE

多选模式

CHOICE_MODE_MULTIPLE_MODAL

Contextual ActionBar(CAB)

长按进入的多选模式

(暂不使用,具体见菜单章节)

效果图如下:

3.系统布局模版

系统提供的布局模版文件

描述

android.R.layout.simple_list_item_1

包含一个控件(TextView)

android.R.id.text1

android.R.layout.simple_list_item_2

包含两个控件(TextView)

android.R.id.text1

android.R.id.text2

android.R.layout.simple_list_item_activated_1

包含一个控件,可激活(高亮显示)的模版

android.R.layout.simple_list_item_checked

可选择(单选、多选均可)

android.R.layout.simple_list_item_single_choice

可单选(RadioButton)

android.R.layout.simple_list_item_multiple_choice

可多选(CheckBox)

4.实例功能

(1)实现如图“联系人”显示。

XML布局文件代码: activity_list_view2.xml

<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">

    <ListView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:id="@+id/listView"
        android:layout_alignParentTop="true"
        android:layout_above="@+id/button" />

    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="选中的内容"
        android:id="@+id/button"
        android:layout_alignParentBottom="true"
        android:layout_centerHorizontal="true"
        android:onClick="doClick"/>
</RelativeLayout>

(2)活动类—代码:ListView2Activity.java

package com.example.administrator.adapterdemo;

import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.util.SparseBooleanArray;
import android.view.View;
import android.widget.ListView;
import android.widget.SimpleAdapter;
import android.widget.Toast;

import java.util.ArrayList;
import java.util.HashMap;

public class ListView2Activity extends AppCompatActivity {

    // 数据的标签(KEY)
    private static final String KEY_NAME = "name";
    private static final String KEY_PHONE = "phone";

    // 视图
    private ListView listView;

    // 数据:由键值对构成的列表【供简单适配器使用】
    private ArrayList<HashMap<String, Object>> data;

    // 适配器:简单适配器【一行可以显示多个控件】
    private SimpleAdapter adapter;

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

        listView = (ListView) findViewById(R.id.listView);

        initView();//初始化
    }

    private void initView() {
        // 初始化数据
        data = new ArrayList<>();
        for (int i = 0; i < 30; i++) {
            HashMap<String, Object> item = new HashMap<String, Object>();
            item.put(KEY_NAME, "联系人 " + i);
            item.put(KEY_PHONE, "号码 " + i);

            data.add(item);
        }

        // 数据的 KEY 构成的数组
        String[] from = {KEY_NAME, KEY_PHONE};

        // 控件的 ID 构成的数组
        int[] to = {android.R.id.text1, android.R.id.text2};

        // 参数一:上下文
        // 参数二:数据
        // 参数三:系统布局模版【activated 代表可高亮显示,2 代表有两个控件】
        // 参数四:数据的 KEY 构成的数组
        // 参数五:控件的 ID 构成的数组
        adapter = new SimpleAdapter(
                getApplicationContext(),
                data,
                android.R.layout.simple_list_item_activated_2,
                from,
                to);

        // 设置为多选模式
        listView.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE);

        // 设置适配器
        listView.setAdapter(adapter);
    }

    /**
     * 显示选中内容
     *
     * @param v button
     */
    public void doClick(View v) {

        // 获得选中项的总数
        int count = listView.getCheckedItemCount();

        // 获得选中项的标识【使用SQLite及游标时可用】
        long[] ids = listView.getCheckedItemIds();

        // 获得选中的位置【单选】
        int position = listView.getCheckedItemPosition();

        // 获得选中的位置【多选,获得一个稀疏矩阵】
        SparseBooleanArray array = listView.getCheckedItemPositions();

        // 显示结果
        Toast.makeText(this, array.toString(), Toast.LENGTH_SHORT).show();
    }

}

 

 

完整工程:https://github.com/ljheee/AdapterDemo

工程名AdapterDemo,工程下有4个活动(Activity),启动运行主活动,演示Spinner使用静态数据;主活动界面下有三个Button,点击可实现向其他活动的跳转。左下角Button跳转到Spinner2Activity演示Spinner动态处理数据;中间是演示简单ListView的使用;右下角跳转到最后一个ListView的演示。

作者:ljheee 发表于2016/8/24 17:51:09 原文链接
阅读:40 评论:0 查看评论

Android Binder机制理解

$
0
0

Android Binder机制理解

一、说明
Android系统中应用很广泛的但也是最难理解的就是Binder机制了,从APP组被调到系统平台组后感觉需要学的东西更多了,思考的方式也有所改变,从是什么转变到为什么(背后的设计思想和实现细节),初次接触Framework感觉就是在于各种服务打交道,例如ActivityManagerService、WindowManagerService、PowerManagerService等等。要想短时间内就把这些都屡清楚是挺困难的一件事。但是万变不离其宗,它们背后的设计思想应该差异不大,所以由简入繁,先攻克其中较为简单的一点,其他的就可以迎刃而解了。下面就以MediaService为例展开源码(基于Android M 6.0版本)的分析,理解了Binder就能够在很大程度上理解程序的运行流程。先简单理解一下Binder通信模型,Binder框架定义了四个角色:Server,Client,ServiceManager(简称SMgr)以及Binder驱动。其中Server,Client,SMgr运行于用户空间,驱动运行于内核空间。这四个角色的关系和互联网类似:Server是服务器,Client是客户终端,SMgr是域名服务器(DNS),驱动是路由器。

二、MediaService探究
MediaService是一个应用程序,其中我们只分析MediaPlayerService。
MediaService的源码文件在:frameworks/av/media/mediaserver/main_mediaserver.cpp

int main(int argc __unused, char** argv)
{
    ......
    sp<ProcessState> proc(ProcessState::self());//获得一个ProcessState实例
    sp<IServiceManager> sm = defaultServiceManager();//得到一个ServiceManager对象
    ALOGI("ServiceManager: %p", sm.get());
    AudioFlinger::instantiate();
    MediaPlayerService::instantiate();//初始化MediaPlayerService服务
    ResourceManagerService::instantiate();
    ......
    ProcessState::self()->startThreadPool();//像是启动Process的线程池?
    IPCThreadState::self()->joinThreadPool();//然后将自己加入到刚才的线程池?
}

sp,究竟是smart pointer还是strong pointer呢?就把它当做一个普通的指针看待,即sp<IServiceManager> –>IServiceManager*。sp是google为了方便C/C++程序员管理指针的分配和释放的一套方法,类似JAVA的什么WeakReference之类的。以后的分析中,sp就看成是XXX*就可以了。

2.1 ProcessState
第一个调用的函数是ProcessState::self(),然后赋值给了proc变量,程序运行完,proc会自动delete内部的内容,所以就自动释放了先前分配的资源。

ProcessState位置在frameworks/native/libs/binder/ProcessState.cpp

sp<ProcessState> ProcessState::self()
{
    Mutex::Autolock _l(gProcessMutex);//锁保护
    if (gProcess != NULL) {
        return gProcess;//第一次进来肯定不走这儿
    }
    gProcess = new ProcessState;//创建一个ProcessState对象
    return gProcess;//这里返回的是指针,但是函数返回的是sp<xxx>,所以把sp<xxx>看成是XXX*是可以的
}

//再来看ProcessState构造函数,这个构造函数看来很重要
ProcessState::ProcessState()
    : mDriverFD(open_driver())//Android很多代码都是这么写的,函数很重要,在下面分析。
    , mVMStart(MAP_FAILED)//映射内存的起始地址
    , mThreadCountLock(PTHREAD_MUTEX_INITIALIZER)
    , mThreadCountDecrement(PTHREAD_COND_INITIALIZER)
    , mExecutingThreadsCount(0)
    , mMaxThreads(DEFAULT_MAX_BINDER_THREADS)
    , mManagesContexts(false)
    , mBinderContextCheckFunc(NULL)
    , mBinderContextUserData(NULL)
    , mThreadPoolStarted(false)
    , mThreadPoolSeq(1)
{
    if (mDriverFD >= 0) {
        // XXX Ideally, there should be a specific define for whether we
        // have mmap (or whether we could possibly have the kernel module
        // availabla).
#if !defined(HAVE_WIN32_IPC)
        // 为这个Binder提供一个虚拟地址空间块来接收处理
        //BINDER_VM_SIZE 定义为((2*1024*1024) - (4096 *2)) 2M-8K
        //将fd映射为内存,这样内存的memcpy等操作就相当于write/read(fd)了
        mVMStart = mmap(0, BINDER_VM_SIZE, PROT_READ, MAP_PRIVATE | MAP_NORESERVE, mDriverFD, 0);
        if (mVMStart == MAP_FAILED) {
            // *sigh*
            ALOGE("Using /dev/binder failed: unable to mmap transaction memory.\n");
            close(mDriverFD);
            mDriverFD = -1;
        }
#else
        mDriverFD = -1;
#endif
    }

    LOG_ALWAYS_FATAL_IF(mDriverFD < 0, "Binder driver could not be opened.  Terminating.");
}

//open_driver,就是打开/dev/binder这个设备,这个是android在内核中搞的一个专门用于完成
//进程间通讯而设置的一个虚拟的设备。BTW,说白了就是内核的提供的一个机制,这个和我们用socket加NET_LINK方式和内核通讯是一个道理。
static int open_driver()
{
    int fd = open("/dev/binder", O_RDWR);
    if (fd >= 0) {
        fcntl(fd, F_SETFD, FD_CLOEXEC);
        int vers = 0;
        status_t result = ioctl(fd, BINDER_VERSION, &vers);
        if (result == -1) {
            ALOGE("Binder ioctl to obtain version failed: %s", strerror(errno));
            close(fd);
            fd = -1;
        }
        if (result != 0 || vers != BINDER_CURRENT_PROTOCOL_VERSION) {
            ALOGE("Binder driver protocol does not match user space protocol!");
            close(fd);
            fd = -1;
        }
        //#define DEFAULT_MAX_BINDER_THREADS 15
        size_t maxThreads = DEFAULT_MAX_BINDER_THREADS;
        //通过ioctl方式告诉内核,这个fd支持最大线程数是15个。
        result = ioctl(fd, BINDER_SET_MAX_THREADS, &maxThreads);
        if (result == -1) {
            ALOGE("Binder ioctl to set max threads failed: %s", strerror(errno));
        }
    } else {
        ALOGW("Opening '/dev/binder' failed: %s\n", strerror(errno));
    }
    return fd;
}

到这里Process::self就分析完了,这里主要做了两件事情:
(1)、打开/dev/binder设备,这样的话就相当于和内核binder机制有了交互的通道
(2)、映射fd到内存,设备的fd传进去后,估计这块内存是和binder设备共享的

接下来,就到调用defaultServiceManager()地方了。
2.2 defaultServiceManager
defaultServiceManager位置在frameworks/native/libs/binder/IServiceManager.cpp中

sp<IServiceManager> defaultServiceManager()
{
    if (gDefaultServiceManager != NULL) return gDefaultServiceManager;
    //又是一个单例,设计模式中叫singleton。
    {
        AutoMutex _l(gDefaultServiceManagerLock);
        while (gDefaultServiceManager == NULL) {
            //真正的gDefaultServiceManager是在这里创建的
            gDefaultServiceManager = interface_cast<IServiceManager>(
                ProcessState::self()->getContextObject(NULL));
            if (gDefaultServiceManager == NULL)
                sleep(1);
        }
    }

    return gDefaultServiceManager;
}
//注:
/*gDefaultServiceManager = interface_cast<IServiceManager>(

                ProcessState::self()->getContextObject(NULL));

ProcessState::self,肯定返回的是刚才创建的gProcess,然后调用它的getContextObject,注意传进去的是NULL,即0*/

//回到ProcessState类,

sp<IBinder> ProcessState::getContextObject(const sp<IBinder>& /*caller*/)
{
    return getStrongProxyForHandle(0);
}
//进入到getStrongProxyForHandle
//注意这个参数的命名,handle。搞过windows的应该比较熟悉这个名字,
//这是对资源的一种标示,其实说白了就是某个数据结构,保存在数组中,然后handle是它在这个数组中的索引。
sp<IBinder> ProcessState::getStrongProxyForHandle(int32_t handle)
{
    sp<IBinder> result;

    AutoMutex _l(mLock);
    //确实,从数组中查找对应索引的资源,lookupHandleLocked这个就不说了,内部会返回一个handle_entry
    handle_entry* e = lookupHandleLocked(handle);

    //下面是 handle_entry 的结构
    /*
    struct handle_entry {
         IBinder* binder;
         RefBase::weakref_type* refs;//这个不知道是什么
                };
     */
    if (e != NULL) {
        //如果目前没有我们所查找的,就创建一个新的bpbinder,
        //或者我们无法获得这个当前的弱引用。可以在getweakproxyforhandle()中查看更多关于这个的信息。
        IBinder* b = e->binder;//第一次进来,肯定为空
        if (b == NULL || !e->refs->attemptIncWeak(this)) {
            if (handle == 0) {
                //上下文管理器的特殊情况
                //上下文管理器是我们创建一个BpBinder proxy代理在没有持有一个引用的情况下的唯一对象。
                //进行一次虚拟处理来确保上下文管理器在我们创建第一个本地引用之前已经被注册。
                //如果上下文管理器不是当前这个就已经为BpBinder创建了一个本地引用,
                //那么这个驱动将不会提供该引用给这个上下文管理器,并且这个驱动API不会返回任何状态
                // Note that this is not race-free if the context manager
                // dies while this code runs.
                //
                // TODO: add a driver API to wait for context manager, or
                // stop special casing handle 0 for context manager and add
                // a driver API to get a handle to the context manager with
                // proper reference counting.

                Parcel data;
                status_t status = IPCThreadState::self()->transact(
                        0, IBinder::PING_TRANSACTION, data, NULL, 0);
                if (status == DEAD_OBJECT)
                   return NULL;
            }

            b = new BpBinder(handle); //在这里创建了一个新的BpBinder
            e->binder = b;
            if (b) e->refs = b->getWeakRefs();
            result = b;
        } else {
            // This little bit of nastyness is to allow us to add a primary
            // reference to the remote proxy when this team doesn't have one
            // but another team is sending the handle to us.
            result.force_set(b);
            e->refs->decWeak(this);
        }
    }

    return result;//返回刚才创建的BpBinder。
}

//到这里,是不是有点迷糊了?函数调用太深的时候,就容易忘记。

/*我们是从gDefaultServiceManager = interface_cast<IServiceManager>(
                ProcessState::self()->getContextObject(NULL));
开始搞的,现在,这个函数调用将变成
gDefaultServiceManager = interface_cast<IServiceManager>(new BpBinder(0));*/

BpBinder又是个什么东东?Android名字起得太眼花缭乱了。
还是继续把层层深入的函数调用栈化繁为简。先看看BpBinder的构造函数。

2.3 BpBinder
BpBinder位置在frameworks/native/libs/binder/BpBinder.cpp中。

BpBinder::BpBinder(int32_t handle)
    : mHandle(handle)//注意,接上述内容,这里调用的时候传入的是0, 
/*SMgr提供的Binder比较特殊,它没有名字也不需要注册,
其次这个Binder的引用在所有Client中都固定为0而无须通过其它手段获得。
也就是说,一个Server若要向SMgr注册自己Binder就必需通过0这个引用号和SMgr的Binder通信。
类比网络通信,0号引用就好比域名服务器的地址,你必须预先手工或动态配置好。
要注意这里说的Client是相对SMgr而言的,一个应用程序可能是个提供服务的Server,但对SMgr来说它仍然是个Client。*/
    , mAlive(1)
    , mObitsSent(0)
    , mObituaries(NULL)
{
    ALOGV("Creating BpBinder %p handle %d\n", this, mHandle);

    extendObjectLifetime(OBJECT_LIFETIME_WEAK);
    IPCThreadState::self()->incWeakHandle(handle);//竟然到IPCThreadState::self()
}

//这里一块说说,IPCThreadState::self估计应该又是一个singleton吧?
//该文件位置在frameworks/native/libs/binder/IPCThreadState.cpp

IPCThreadState* IPCThreadState::self()
{
    if (gHaveTLS) {//第一次进来为false
restart:
        const pthread_key_t k = gTLS;
        //TLS是Thread Local Storage的意思,不懂得自己去google下它的作用吧。这里只需要
        //知道这种空间每个线程有一个,而且线程间不共享这些空间,好处是?
        //就可以免去同步了。在这个线程,我就用这个线程的东西,反正别的线程获取不到其他线程TLS中的数据。
        //从线程本地存储空间中获得保存在其中的IPCThreadState对象
        //这段代码写法很晦涩,只有pthread_getspecific,那么肯定有地方调用pthread_setspecific。
        IPCThreadState* st = (IPCThreadState*)pthread_getspecific(k);
        if (st) return st;
        return new IPCThreadState;
    }

#ifdef _MTK_ENG_BUILD_
    if (gShutdown) {
        IPCThreadState* st = (IPCThreadState*)pthread_getspecific(gTLS);
        ALOGD("IPCThreadState 0x%p, gTLS:%d gHaveTLS:%d\n", &st, gTLS, gHaveTLS);
        return NULL;
    }
#else
    if (gShutdown) return NULL;
#endif

    pthread_mutex_lock(&gTLSMutex);
    if (!gHaveTLS) {
        if (pthread_key_create(&gTLS, threadDestructor) != 0) {
            pthread_mutex_unlock(&gTLSMutex);
            return NULL;
        }
        gHaveTLS = true;
    }
    pthread_mutex_unlock(&gTLSMutex);
    goto restart;//少见的goto语句,直接跳转到restart 去创建IPCThreadState
}

//这里是构造函数,在构造函数里边pthread_setspecific
IPCThreadState::IPCThreadState()
    : mProcess(ProcessState::self()),
      mMyThreadId(gettid()),
      mStrictModePolicy(0),
      mLastTransactionBinderFlags(0)
{
    pthread_setspecific(gTLS, this);
    clearCaller();
    mIn.setDataCapacity(256);
    //mIn,mOut是两个Parcel,干嘛用的啊?把它看成是命令的buffer吧。
    mOut.setDataCapacity(256);
}

new BpBinder就算完了。到这里,我们创建了些什么呢?
(1)ProcessState有了。
(2)IPCThreadState有了,而且是主线程的。
(3)BpBinder有了,内部handle值为0
gDefaultServiceManager = interface_cast<IServiceManager>(new BpBinder(0));
终于回到原点了!

interface_cast,我第一次接触的时候,把它看做类似的static_cast一样的东西,然后死活也搞不明白 BpBinder*指针怎么能强转为IServiceManager*,跟踪进入interface_cast

IInterface.h位于frameworks/native/include/binder/IInterface.h

template<typename INTERFACE>
inline sp<INTERFACE> interface_cast(const sp<IBinder>& obj)
{
    return INTERFACE::asInterface(obj);
}
所以,上面等价于:
inline sp<IServiceManager> interface_cast(const sp<IBinder>& obj)
{
    return IServiceManager::asInterface(obj);
}

看来,只能跟到IServiceManager了。
IServiceManager.h位于frameworks/native/include/binder/IServiceManager.h

看看它是如何定义的:

2.4 IServiceManager

class IServiceManager : public IInterface
{
//ServiceManager,字面上理解就是Service管理类,管理什么?增加服务,查询服务等
//这里仅列出增加服务addService函数
public:
    DECLARE_META_INTERFACE(ServiceManager);

    virtual status_t            addService( const String16& name,
                                            const sp<IBinder>& service,
                                            bool allowIsolated = false) = 0;

/*DECLARE_META_INTERFACE(ServiceManager)??
和MFC类似,有DELCARE肯定有IMPLEMENT,
这两个宏DECLARE_META_INTERFACE和IMPLEMENT_META_INTERFACE(INTERFACE, NAME)都在刚才的IInterface.h中定义。
我们先看看DECLARE_META_INTERFACE这个宏往IServiceManager加了什么?*/

//下面是DECLARE宏
#define DECLARE_META_INTERFACE(INTERFACE)                               \
    static const android::String16 descriptor;                          \
    static android::sp<I##INTERFACE> asInterface(                       \
            const android::sp<android::IBinder>& obj);                  \
    virtual const android::String16& getInterfaceDescriptor() const;    \
    I##INTERFACE();                                                     \
    virtual ~I##INTERFACE();  

//我们把它兑现到IServiceManager就是:
static const android::String16 descriptor;//增加一个描述字符串
static android::sp< IServiceManager > asInterface(const android::sp<android::IBinder>&
obj) //增加一个asInterface函数
virtual const android::String16& getInterfaceDescriptor() const;//增加一个get函数估计其返回值就是descriptor这个字符串
IServiceManager ();                                                     \
virtual ~IServiceManager();//增加构造和虚析购函数...

//下面是IMPLEMENT宏的定义
#define IMPLEMENT_META_INTERFACE(INTERFACE, NAME)                       \
    const android::String16 I##INTERFACE::descriptor(NAME);             \
    const android::String16&                                            \
            I##INTERFACE::getInterfaceDescriptor() const {              \
        return I##INTERFACE::descriptor;                                \
    }                                                                   \
    android::sp<I##INTERFACE> I##INTERFACE::asInterface(                \
            const android::sp<android::IBinder>& obj)                   \
    {                                                                   \
        android::sp<I##INTERFACE> intr;                                 \
        if (obj != NULL) {                                              \
            intr = static_cast<I##INTERFACE*>(                          \
                obj->queryLocalInterface(                               \
                        I##INTERFACE::descriptor).get());               \
            if (intr == NULL) {                                         \
                intr = new Bp##INTERFACE(obj);                          \
            }                                                           \
        }                                                               \
        return intr;                                                    \
    }                                                                   \
    I##INTERFACE::I##INTERFACE() { }                                    \
    I##INTERFACE::~I##INTERFACE() { }   

//IMPLEMENT宏兑现到IServiceManager
见IServiceManager.cpp。位于frameworks/native/libs/binder/IServiceManager.cpp

//下面是这个宏的定义的使用
IMPLEMENT_META_INTERFACE(ServiceManager, "android.os.IServiceManager");
//赶紧兑现一下IMPLEMENT宏吧
const
android::String16 IServiceManager::descriptor(“android.os.IServiceManager”);
const android::String16& IServiceManager::getInterfaceDescriptor() const
{  
    return IServiceManager::descriptor;//返回上面那个android.os.IServiceManager
   }                                                                      
   android::sp<IServiceManager> IServiceManager::asInterface(
            const android::sp<android::IBinder>& obj)
    {
        android::sp<IServiceManager> intr;
        if (obj != NULL) {                                             
            intr = static_cast<IServiceManager *>(                         
                obj->queryLocalInterface(IServiceManager::descriptor).get());              
            if (intr == NULL) {                                         
                intr = new BpServiceManager(obj);                         
            }                                                          
        }                                                               
        return intr;                                                   
    }                                                                 
    IServiceManager::IServiceManager () { }                                   
    IServiceManager::~ IServiceManager() { }

    /*asInterface是这么搞的,赶紧分析下吧,还是不知道interface_cast怎么把BpBinder*转成了IServiceManager
我们刚才解析过的interface_cast<IServiceManager>(new BpBinder(0)),
原来就是调用asInterface(new BpBinder(0))*/
android::sp<IServiceManager> IServiceManager::asInterface(
            const android::sp<android::IBinder>& obj)
    {
        android::sp<IServiceManager> intr;
        if (obj != NULL) {                                             
            ....                                      
                intr = new BpServiceManager(obj);
            //终于看到和IServiceManager相关的东西了,看来实际返回的是BpServiceManager(new BpBinder(0));                         
            }                                                          
        }                                                              
        return intr;                                                   
}                     

BpServiceManager是个什么东东?p是什么个意思?

2.5 BpServiceManager
p是proxy即代理的意思,Bp就是BinderProxy,BpServiceManager,就是SM的Binder代理。既然是代理,那肯定希望对用户是透明的,那就是说头文件里边不会有这个Bp的定义,接下来看。
BpServiceManager就在刚才的IServiceManager.cpp中定义。

class BpServiceManager : public BpInterface<IServiceManager>
{
//这种继承方式,表示同时继承BpInterface和IServiceManager,
//这样IServiceManger的addService必然在这个类中实现
public:
//注意构造函数参数的命名 impl,难道这里使用了Bridge模式?真正完成操作的是impl对象?
//这里传入的impl就是new BpBinder(0)
    BpServiceManager(const sp<IBinder>& impl)
        : BpInterface<IServiceManager>(impl)
    {
    }
    ......
    virtual status_t addService(const String16& name, const sp<IBinder>& service,
            bool allowIsolated)
    {
        //下面再讲
        Parcel data, reply;
        data.writeInterfaceToken(IServiceManager::getInterfaceDescriptor());
        data.writeString16(name);
        data.writeStrongBinder(service);
        data.writeInt32(allowIsolated ? 1 : 0);
        status_t err = remote()->transact(ADD_SERVICE_TRANSACTION, data, &reply);
        return err == NO_ERROR ? reply.readExceptionCode() : err;
    }
    ......
    //基类BpInterface的构造函数见frameworks/native/include/binder/IInterface.h
    //(经过兑现后)这里的参数又叫remote,真是害人不浅。
inline BpInterface<IServiceManager>::BpInterface(const sp<IBinder>& remote)
    : BpRefBase(remote)
{
}

};

Binder.cpp位于frameworks/native/libs/binder/Binder.cpp

BpRefBase::BpRefBase(const sp<IBinder>& o)
    : mRemote(o.get()), mRefs(NULL), mState(0)
//o.get(),这个是sp类的获取实际数据指针的一个方法,你只要知道
//它返回的是sp<xxxx>中xxx* 指针就行
{
    extendObjectLifetime(OBJECT_LIFETIME_WEAK);
    //mRemote就是刚才的BpBinder(0)
    if (mRemote) {
        mRemote->incStrong(this);           // Removed on first IncStrong().
        mRefs = mRemote->createWeak(this);  // Held for our entire lifetime.
    }
}

好了,到这里,我们知道了:
sp<IServiceManager> sm = defaultServiceManager(); 返回的实际是BpServiceManager,它的remote对象是BpBinder,传入的那个handle参数是0。

现在重新回到MediaService。

int main(int argc __unused, char** argv)
{
    ......
    sp<ProcessState> proc(ProcessState::self());//获得一个ProcessState实例
    sp<IServiceManager> sm = defaultServiceManager();//得到一个ServiceManager对象
    //上面的讲解已经完了
    MediaPlayerService::instantiate();//初始化MediaPlayerService服务
    //这里值得推敲!
    ......
    ProcessState::self()->startThreadPool();
    IPCThreadState::self()->joinThreadPool();
}

到这里,我们把binder设备打开了,得到一个BpServiceManager对象,这表明我们可以和SM打交道了。

2.6 MediaPlayerService
那下面我们看看后续又干了什么?以MediaPlayerService为例。

它位于frameworks/av/media/libmediaplayerservice/MediaPlayerService.cpp


void MediaPlayerService::instantiate() {
    defaultServiceManager()->addService(
            String16("media.player"), new MediaPlayerService());
                /*传进去服务的名字,传进去new出来的对象*/
}
//defaultServiceManager返回的是刚才创建的BpServiceManager调用它的addService函数。

MediaPlayerService::MediaPlayerService()
{
    ALOGV("MediaPlayerService created");
    MM_LOGI("created");

    mNextConnId = 1;

    mBatteryAudio.refCount = 0;
    for (int i = 0; i < NUM_AUDIO_DEVICES; i++) {
        mBatteryAudio.deviceOn[i] = 0;
        mBatteryAudio.lastTime[i] = 0;
        mBatteryAudio.totalTime[i] = 0;
    }
    // speaker is on by default
    mBatteryAudio.deviceOn[SPEAKER] = 1;

    // reset battery stats
    // if the mediaserver has crashed, battery stats could be left
    // in bad state, reset the state upon service start.
    BatteryNotifier& notifier(BatteryNotifier::getInstance());
    notifier.noteResetVideo();
    notifier.noteResetAudio();

    MediaPlayerFactory::registerBuiltinFactories();
}

MediaPlayerService从BnMediaPlayerService派生
见frameworks/av/media/libmediaplayerservice/MediaPlayerService.h

class MediaPlayerService : public BnMediaPlayerService

MediaPlayerService从BnMediaPlayerService派生,BnXXX,BpXXX,快晕了。

Bn 是Binder Native的含义,是和Bp相对的,Bp的p是proxy代理的意思,那么另一端一定有一个和代理打交道的东西,这个就是Bn。
讲到这里会有点乱喔。先分析下,到目前为止都构造出来了什么。

BpServiceManager
BnMediaPlayerService

这两个东西不是相对的两端,从BnXXX就可以判断,BpServiceManager对应的应该是BnServiceManager,BnMediaPlayerService对应的应该是BpMediaPlayerService。
我们现在是创建了BnMediaPlayerService,想把它加入到系统的中去。

我们创建一个新的Service—BnMediaPlayerService,想把它告诉ServiceManager。
那我怎么和ServiceManager通讯呢?利用BpServiceManager。所以嘛,我们调用了BpServiceManager的addService函数!

为什么要搞个ServiceManager来呢?这个和Android机制有关系。所有Service都需要加入到ServiceManager来管理。同时也方便了Client来查询系统存在哪些Service,ServiceManager就相当于我们的DNS域名服务器。没看见我们传入了字符串吗?”media.player”就相当于我们的网络域名,这样就可以通过Human Readable的字符串来查找Service了。

2.7 addService
addService是调用的BpServiceManager的函数。前面略去没讲,现在我们看看。

virtual status_t addService(const String16& name, const sp<IBinder>& service,
            bool allowIsolated)
    {
        Parcel data, reply;
        //data是发送到BnServiceManager的命令包
        //先把Interface名字写进去,也就是什么android.os.IServiceManager
        data.writeInterfaceToken(IServiceManager::getInterfaceDescriptor());

        //再把新service的名字写进去 叫media.player
        data.writeString16(name);

        //把新服务service—>就是MediaPlayerService写到命令中
        data.writeStrongBinder(service);
        data.writeInt32(allowIsolated ? 1 : 0);

        //调用remote的transact函数
        status_t err = remote()->transact(ADD_SERVICE_TRANSACTION, data, &reply);
        return err == NO_ERROR ? reply.readExceptionCode() : err;
    }

remote(){ return mRemote; }
见frameworks/native/include/binder/Binder.h

class BpRefBase : public virtual RefBase
{
protected:
                            BpRefBase(const sp<IBinder>& o);
    virtual                 ~BpRefBase();
    virtual void            onFirstRef();
    virtual void            onLastStrongRef(const void* id);
    virtual bool            onIncStrongAttempted(uint32_t flags, const void* id);

    inline  IBinder*        remote()                { return mRemote; }
    inline  IBinder*        remote() const          { return mRemote; }

private:
                            BpRefBase(const BpRefBase& o);
    BpRefBase&              operator=(const BpRefBase& o);

    IBinder* const          mRemote;
    RefBase::weakref_type*  mRefs;
    volatile int32_t        mState;
};

}; 

这里的mRemote就是最初创建的BpBinder..
到那里去看看:

//BpBinder的位置在frameworks/native/libs/binder/BpBinder.cpp
status_t BpBinder::transact(
    uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags)
{
    // Once a binder has died, it will never come back to life.
    if (mAlive) {
    //又绕回去了,调用IPCThreadState的transact。
    //注意:这里的mHandle为0,code是ADD_SERVICE_TRANSACTION,data是命令包
    //reply是回复包,flags=0
        status_t status = IPCThreadState::self()->transact(
            mHandle, code, data, reply, flags);
        if (status == DEAD_OBJECT) mAlive = 0;
        return status;
    }

    return DEAD_OBJECT;
}

//再看看IPCThreadState的transact函数吧
//见frameworks/native/libs/binder/IPCThreadState.cpp
status_t IPCThreadState::transact(int32_t handle,
                                  uint32_t code, const Parcel& data,
                                  Parcel* reply, uint32_t flags)
{
    status_t err = data.errorCheck();

    flags |= TF_ACCEPT_FDS;

    //......

    if (err == NO_ERROR) {
        //调用writeTransactionData 发送数据
        err = writeTransactionData(BC_TRANSACTION, flags, handle, code, data, NULL);
    }

    if (err != NO_ERROR) {
        if (reply) reply->setError(err);

        return (mLastError = err);
    }

    if ((flags & TF_ONE_WAY) == 0) {

        if (reply) {
            err = waitForResponse(reply);
        } else {
            Parcel fakeReply;
            err = waitForResponse(&fakeReply);
        }


    } else {
        //......等回复
        err = waitForResponse(NULL, NULL);
    }

    ......
    return err;
}

//再进一步,瞧瞧writeTransactionData 
status_t IPCThreadState::writeTransactionData(int32_t cmd, uint32_t binderFlags,
    int32_t handle, uint32_t code, const Parcel& data, status_t* statusBuffer)
{
    binder_transaction_data tr;

    tr.target.ptr = 0; /* Don't pass uninitialized stack data to a remote process */
    tr.target.handle = handle;
    tr.code = code;
    tr.flags = binderFlags;
    tr.cookie = 0;
    tr.sender_pid = 0;
    tr.sender_euid = 0;

    const status_t err = data.errorCheck();
    if (err == NO_ERROR) {
        tr.data_size = data.ipcDataSize();
        tr.data.ptr.buffer = data.ipcData();
        tr.offsets_size = data.ipcObjectsCount()*sizeof(binder_size_t);
        tr.data.ptr.offsets = data.ipcObjects();
    } 
    ....

    //上面把命令数据封装成binder_transaction_data,然后

    //写到mOut中,mOut是命令的缓冲区,也是一个Parcel
    mOut.writeInt32(cmd);
    mOut.write(&tr, sizeof(tr));
    //仅仅写到了Parcel中,Parcel好像没和/dev/binder设备有什么关联啊?
    //那只能在另外一个地方写到binder设备中去了。
    return NO_ERROR;
}

//就是在waitForResponse中
status_t IPCThreadState::waitForResponse(Parcel *reply, status_t *acquireResult)
{
    uint32_t cmd;
    int32_t err;

    while (1) {
        //talkWithDriver,应该是这里了
        if ((err=talkWithDriver()) < NO_ERROR) break;
        err = mIn.errorCheck();
        if (err < NO_ERROR) break;
        if (mIn.dataAvail() == 0) continue;
            //这里开始操作mIn了,看来talkWithDriver中
            //把mOut发出去,然后从driver中读到数据放到mIn中了。
        cmd = (uint32_t)mIn.readInt32();

        }

        switch (cmd) {
        case BR_TRANSACTION_COMPLETE:
            if (!reply && !acquireResult) goto finish;
            break;

       ......

    return err;
}

status_t IPCThreadState::talkWithDriver(bool doReceive)
{
    binder_write_read bwr;
    ......

    //中间东西太复杂了,不就是把mOut数据和mIn接收数据的处理后赋值给bwr吗?
    status_t err;
    do {
        ......
        //用ioctl来读写
        if (ioctl(mProcess->mDriverFD, BINDER_WRITE_READ, &bwr) >= 0)
            err = NO_ERROR;
        else {
            err = -errno;
        }

        if (mProcess->mDriverFD <= 0) {
            err = -EBADF;
        }
    } while (err == -EINTR);

    if (err >= NO_ERROR) {
        ......
        //到这里,回复数据就在bwr中了,bwr接收回复数据的buffer就是mIn提供的
        if (bwr.read_consumed > 0) {
            mIn.setDataSize(bwr.read_consumed);
            mIn.setDataPosition(0);
        }
        return NO_ERROR;
    }

    return err;
}

好了,到这里,我们发送addService的流程就彻底走完了。
BpServiceManager发送了一个addService命令到BnServiceManager,然后收到回复。
先继续我们的main函数。


int main(int argc __unused, char** argv)
{
    ......
    sp<ProcessState> proc(ProcessState::self());//获得一个ProcessState实例
    sp<IServiceManager> sm = defaultServiceManager();//得到一个ServiceManager对象

    //该函数内部调用addService,把MediaPlayerService信息 add到ServiceManager中
    MediaPlayerService::instantiate();//初始化MediaPlayerService服务
    ......
    ProcessState::self()->startThreadPool();
    IPCThreadState::self()->joinThreadPool();
}

这里有个容易搞晕的地方:
MediaPlayerService是一个BnMediaPlayerService,那么它是不是应该等着
BpMediaPlayerService来和他交互呢?但是我们没看见MediaPlayerService有打开binder设备的操作啊!
这个嘛,到底是继续addService操作的另一端BnServiceManager还是先说
BnMediaPlayerService呢?
还是先说BnServiceManager吧。顺便把系统的Binder架构说说。

2.8 BnServiceManager
上面说了,defaultServiceManager返回的是一个BpServiceManager,通过它可以把命令请求发送到binder设备,而且handle的值为0。那么,系统的另外一端肯定有个接收命令的,那又是谁呢?
很可惜啊,BnServiceManager不存在,但确实有一个程序完成了BnServiceManager的工作,那就是service.exe(如果在windows上一定有exe后缀,叫service的名字太多了,这里加exe就表明它是一个程序)

位置在frameworks/native/cmds/servicemanager/service_manager.c中。

int main(int argc, char **argv)
{
    struct binder_state *bs;
    //应该是打开binder设备吧?
    bs = binder_open(128*1024);
    if (!bs) {
        ALOGE("failed to open binder driver\n");
        return -1;
    }
    //成为manager
    if (binder_become_context_manager(bs)) {
        ALOGE("cannot become context manager (%s)\n", strerror(errno));
        return -1;
    }
    ......

    //处理BpServiceManager发过来的命令
    binder_loop(bs, svcmgr_handler);

    return 0;
}
//看看binder_open是不是和我们猜得一样?
//见frameworks/native/cmds/servicemanager/binder.c
struct binder_state *binder_open(size_t mapsize)
{
    struct binder_state *bs;
    struct binder_version vers;

    bs = malloc(sizeof(*bs));
    ......
    bs->fd = open("/dev/binder", O_RDWR);//确实如此
    ......

    bs->mapsize = mapsize;
    bs->mapped = mmap(NULL, mapsize, PROT_READ, MAP_PRIVATE, bs->fd, 0);
    ......
    return bs;
fail_map:
    close(bs->fd);
fail_open:
    free(bs);
    return NULL;
}

//再看看binder_become_context_manager
int binder_become_context_manager(struct binder_state *bs)
{
    return ioctl(bs->fd, BINDER_SET_CONTEXT_MGR, 0);//把自己设为MANAGER
}

//binder_loop 肯定是从binder设备中读请求,写回复的这么一个循环吧?
void binder_loop(struct binder_state *bs, binder_handler func)
{
    int res;
    struct binder_write_read bwr;
    uint32_t readbuf[32];

    bwr.write_size = 0;
    bwr.write_consumed = 0;
    bwr.write_buffer = 0;

    readbuf[0] = BC_ENTER_LOOPER;
    binder_write(bs, readbuf, sizeof(uint32_t));

    for (;;) {//果然是循环
        bwr.read_size = sizeof(readbuf);
        bwr.read_consumed = 0;
        bwr.read_buffer = (uintptr_t) readbuf;

        res = ioctl(bs->fd, BINDER_WRITE_READ, &bwr);

        if (res < 0) {
            ALOGE("binder_loop: ioctl failed (%s)\n", strerror(errno));
            break;
        }
        //收到请求了,解析命令
        res = binder_parse(bs, 0, (uintptr_t) readbuf, bwr.read_consumed, func);
        ......
    }
}
//这个...后面还要说吗??
//恩,最后有一个类似handleMessage的地方处理各种各样的命令。这个就是
//svcmgr_handler,就在在frameworks/native/cmds/servicemanager/service_manager.c中
int svcmgr_handler(struct binder_state *bs,
                   struct binder_transaction_data *txn,
                   struct binder_io *msg,
                   struct binder_io *reply)
{
    struct svcinfo *si;
    uint16_t *s;
    size_t len;
    uint32_t handle;
    uint32_t strict_policy;
    int allow_isolated;

    ......
    s = bio_get_string16(msg, &len);
    ......
    switch(txn->code) {
    ......
    case SVC_MGR_ADD_SERVICE:
        s = bio_get_string16(msg, &len);
        if (s == NULL) {
            return -1;
        }
        handle = bio_get_ref(msg);
        allow_isolated = bio_get_uint32(msg) ? 1 : 0;
        if (do_add_service(bs, s, len, handle, txn->sender_euid,
            allow_isolated, txn->sender_pid))
            return -1;
        break;
    ......    
}
//其中,do_add_service真正添加BnMediaService信息
int do_add_service(struct binder_state *bs,
                   const uint16_t *s, size_t len,
                   uint32_t handle, uid_t uid, int allow_isolated,
                   pid_t spid)
{
    struct svcinfo *si;
    ......
    si = find_svc(s, len);//s是一个list
    if (si) {
        if (si->handle) {
            ALOGE("add_service('%s',%x) uid=%d - ALREADY REGISTERED, OVERRIDE\n",
                 str8(s, len), handle, uid);
            svcinfo_death(bs, si);
        }
        si->handle = handle;
    } else {
        si = malloc(sizeof(*si) + (len + 1) * sizeof(uint16_t));
        if (!si) {
            ALOGE("add_service('%s',%x) uid=%d - OUT OF MEMORY\n",
                 str8(s, len), handle, uid);
            return -1;
        }
        si->handle = handle;
        si->len = len;
        memcpy(si->name, s, (len + 1) * sizeof(uint16_t));
        si->name[len] = '\0';
        si->death.func = (void*) svcinfo_death;
        si->death.ptr = si;
        si->allow_isolated = allow_isolated;
        si->next = svclist;
        svclist = si; //这个svclist是一个列表,保存了当前注册到ServiceManager中的信息
    }

    binder_acquire(bs, handle);
    //这个吗。当这个Service退出后,我希望系统通知我一下,好释放上面malloc出来的资源。大概就是干这个事情的。
    binder_link_to_death(bs, handle, &si->death);
    return 0;
}

对于addService来说,看来ServiceManager把信息加入到自己维护的一个服务列表中了。

2.9 ServiceManager存在的意义
为何需要一个这样的东西呢?

原来,Android系统中Service信息都是先add到ServiceManager中,由ServiceManager来集中管理,这样就可以查询当前系统有哪些服务。而且,Android系统中某个服务例如MediaPlayerService的客户端想要和MediaPlayerService通讯的话,必须先向ServiceManager查询MediaPlayerService的信息,然后通过ServiceManager返回的东西再来和MediaPlayerService交互。

毕竟,要是MediaPlayerService身体不好,老是挂掉的话,客户的代码就麻烦了,就不知道后续新生的MediaPlayerService的信息了,所以只能这样:

  • MediaPlayerService向SM注册

  • MediaPlayerClient查询当前注册在SM中的MediaPlayerService的信息

  • 根据这个信息,MediaPlayerClient和MediaPlayerService交互

另外,ServiceManager的handle标示是0,所以只要往handle是0的服务发送消息了,最终都会被传递到ServiceManager中去。

三、 MediaService的运行

上一节的知识,我们知道了:

  • defaultServiceManager得到了BpServiceManager,然后MediaPlayerService 实例化后,调用BpServiceManager的addService函数

  • 这个过程中,是service_manager收到addService的请求,然后把对应信息放到自己保存的一个服务list中

到这儿,我们可看到,service_manager有一个binder_looper函数,专门等着从binder中接收请求。虽然service_manager没有从BnServiceManager中派生,但是它肯定完成了BnServiceManager的功能。

同样,我们创建了MediaPlayerService即BnMediaPlayerService,那它也应该:

  • 打开binder设备

  • 也搞一个looper循环,然后坐等请求service

service,这个和网络编程中的监听socket的工作很像嘛!
好吧,既然MediaPlayerService的构造函数没有看到显示的打开binder设备,那么我们看看它的父类即BnXXX又到底干了些什么呢?

3.1 MediaPlayerService打开binder
见frameworks/av/media/libmediaplayerservice/MediaPlayerService.h

class MediaPlayerService : public BnMediaPlayerService
{
    // MediaPlayerService从BnMediaPlayerService派生

    ......
}
//而BnMediaPlayerService从BnInterface和IMediaPlayerService同时派生

见frameworks/av/include/media/IMediaPlayerService.h

class BnMediaPlayerService: public BnInterface<IMediaPlayerService>
{
public:
    virtual status_t    onTransact( uint32_t code,
                                    const Parcel& data,
                                    Parcel* reply,
                                    uint32_t flags = 0);
};

//看起来,BnInterface似乎更加和打开设备相关。
//见frameworks/native/include/binder/IInterface.h
template<typename INTERFACE>
class BnInterface : public INTERFACE, public BBinder
{
public:
    virtual sp<IInterface>      queryLocalInterface(const String16& _descriptor);
    virtual const String16&     getInterfaceDescriptor() const;

protected:
    virtual IBinder*            onAsBinder();
};

//兑现后变成
class BnInterface : public IMediaPlayerService, public BBinder

BBinder?BpBinder?是不是和BnXXX以及BpXXX对应的呢?如果是,为什么又叫BBinder呢?

BBinder::BBinder()

    : mExtras(NULL)

{

//没有打开设备的地方啊?

}

回想下,我们的Main_MediaService程序,有哪里打开过binder吗?

int main(int argc __unused, char** argv)
{
    ......
    //对啊,我在ProcessState中不是打开过binder了吗?
    sp<ProcessState> proc(ProcessState::self());
    sp<IServiceManager> sm = defaultServiceManager();

    MediaPlayerService::instantiate();

    ......
}

3.2 looper
原来打开binder设备的地方是和进程相关的,一个进程打开一个就可以了。那么,我在哪里进行类似的消息循环looper操作呢?

......
//难道是下面两个?
ProcessState::self()->startThreadPool();//像是启动Process的线程池?
IPCThreadState::self()->joinThreadPool();//然后将自己加入到刚才的线程池?
//看看startThreadPool吧
void ProcessState::startThreadPool()
{
    AutoMutex _l(mLock);
    if (!mThreadPoolStarted) {
        mThreadPoolStarted = true;
        spawnPooledThread(true);
    }
}

void ProcessState::spawnPooledThread(bool isMain)
{
    if (mThreadPoolStarted) {
        String8 name = makeBinderThreadName();
        ALOGV("Spawning new pooled thread, name=%s\n", name.string());
        sp<Thread> t = new PoolThread(isMain);//isMain是TRUE
        //创建线程池,然后run起来,和java的Thread何其像也。
        t->run(name.string());
    }
}
//PoolThread从Thread类中派生,那么此时会产生一个线程吗?看看PoolThread和Thread的构造吧
class PoolThread : public Thread
{
public:
    PoolThread(bool isMain)
        : mIsMain(isMain)
    {
    }
    ......
};

//Thread的构造见system/core/libutils/Threads.cpp
Thread::Thread(bool canCallJava)//canCallJava默认值是true
    :   mCanCallJava(canCallJava),
        mThread(thread_id_t(-1)),
        mLock("Thread::mLock"),
        mStatus(NO_ERROR),
        mExitPending(false), mRunning(false)
#ifdef HAVE_ANDROID_OS
        , mTid(-1)
#endif
{
}
//这个时候还没有创建线程呢。然后调用PoolThread::run,实际调用了基类的run。
status_t Thread::run(const char* name, int32_t priority, size_t stack)
{
    ......

    bool res;
    if (mCanCallJava) {
        res = createThreadEtc(_threadLoop,
                this, name, priority, stack, &mThread);
    } else {
        res = androidCreateRawThreadEtc(_threadLoop,
                this, name, priority, stack, &mThread);
    }
    ......

    return NO_ERROR;
}

//createThreadEtc见system/core/include/utils/AndroidThreads.h
inline bool createThreadEtc(thread_func_t entryFunction,
                            void *userData,
                            const char* threadName = "android:unnamed_thread",
                            int32_t threadPriority = PRIORITY_DEFAULT,
                            size_t threadStackSize = 0,
                            thread_id_t *threadId = 0)
{
    return androidCreateThreadEtc(entryFunction, userData, threadName,
        threadPriority, threadStackSize, threadId) ? true : false;
}

//终于,在run函数中,创建线程了。从此主线程执行
IPCThreadState::self()->joinThreadPool();

//新开的线程执行_threadLoop
//我们先看看_threadLoop
int Thread::_threadLoop(void* user)
{
    Thread* const self = static_cast<Thread*>(user);

    sp<Thread> strong(self->mHoldSelf);
    wp<Thread> weak(strong);
    self->mHoldSelf.clear();
    ......
    do {
        bool result;
        ......

            if (result && !self->exitPending()) {

                result = self->threadLoop();//调用自己的threadLoop
            }
        } else {
            result = self->threadLoop();
        }
    ......      
    return 0;
}
//我们是PoolThread对象,所以调用PoolThread的threadLoop函数
virtual bool threadLoop()
{
    IPCThreadState* ipc = IPCThreadState::self();
//mIsMain为true。
//而且注意,这是一个新的线程,所以必然会创建一个
//新的IPCThreadState对象(记得线程本地存储吗?TLS),然后      
    if(ipc)
        ipc->joinThreadPool(mIsMain);
    //IPCThreadState::self()->joinThreadPool(mIsMain);
    return false;
}

//主线程和工作线程都调用了joinThreadPool,看看这个干嘛了!
void IPCThreadState::joinThreadPool(bool isMain)
{
    mOut.writeInt32(isMain ? BC_ENTER_LOOPER : BC_REGISTER_LOOPER);

    set_sched_policy(mMyThreadId, SP_FOREGROUND);   
    status_t result;
    do {
        processPendingDerefs();
        // now get the next command to be processed, waiting if necessary
        result = getAndExecuteCommand();
        ......
        // Let this thread exit the thread pool if it is no longer
        // needed and it is not the main process thread.
        if(result == TIMED_OUT && !isMain) {
            break;
        }
    } while (result != -ECONNREFUSED && result != -EBADF);

    mOut.writeInt32(BC_EXIT_LOOPER);
    talkWithDriver(false);
}
//看到没?有loop了,但是好像是有两个线程都执行了这个!这里有两个消息循环?
//getAndExecuteCommand中调用了executeCommand
status_t IPCThreadState::getAndExecuteCommand()
{
    status_t result;
    int32_t cmd;

    result = talkWithDriver();
    if (result >= NO_ERROR) {
        size_t IN = mIn.dataAvail();
        if (IN < sizeof(int32_t)) return result;
        cmd = mIn.readInt32();

        pthread_mutex_lock(&mProcess->mThreadCountLock);
        mProcess->mExecutingThreadsCount++;
        pthread_mutex_unlock(&mProcess->mThreadCountLock);

        result = executeCommand(cmd);

        pthread_mutex_lock(&mProcess->mThreadCountLock);
        mProcess->mExecutingThreadsCount--;
        pthread_cond_broadcast(&mProcess->mThreadCountDecrement);
        pthread_mutex_unlock(&mProcess->mThreadCountLock);

        set_sched_policy(mMyThreadId, SP_FOREGROUND);
    }

    return result;
}
//下面看看executeCommand
status_t IPCThreadState::executeCommand(int32_t cmd)
{
    BBinder* obj;
    RefBase::weakref_type* refs;
    status_t result = NO_ERROR;

    switch ((uint32_t)cmd) {
    case BR_TRANSACTION:
        {
            binder_transaction_data tr;
            result = mIn.read(&tr, sizeof(tr));
            //来了一个命令,解析成BR_TRANSACTION,然后读取后续的信息
            Parcel reply;
            status_t error;

            if (tr.target.ptr) {
                //这里用的是BBinder。
                if (reinterpret_cast<RefBase::weakref_type*>(
                        tr.target.ptr)->attemptIncStrong(this)) {
                    error = reinterpret_cast<BBinder*>(tr.cookie)->transact(tr.code, buffer,
                            &reply, tr.flags);
                    reinterpret_cast<BBinder*>(tr.cookie)->decStrong(this);
                } else {
                    error = UNKNOWN_TRANSACTION;
                }

            }
}
//让我们看看BBinder的transact函数干嘛了
//见frameworks/native/libs/binder/Binder.cpp
status_t BBinder::transact(
    uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags)
{
    data.setDataPosition(0);

    status_t err = NO_ERROR;
    switch (code) {
        case PING_TRANSACTION:
            reply->writeInt32(pingBinder());
            break;
        default:
            //就是调用自己的onTransact函数嘛
            err = onTransact(code, data, reply, flags);
            break;
    }

    if (reply != NULL) {
        reply->setDataPosition(0);
    }

    return err;
}
//BnMediaPlayerService从BnInterface派生,BnInterface从BBinder派生,所以会调用到它的onTransact函数 
//终于水落石出了,让我们看看BnMediaPlayerServcice的onTransact函数。
status_t BnMediaPlayerService::onTransact(
    uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags)
{
    // BnMediaPlayerService从BBinder和IMediaPlayerService派生,所有IMediaPlayerService
    //看到下面的switch没?所有IMediaPlayerService提供的函数都通过命令类型来区分
    switch (code) {
        case CREATE: {
                CHECK_INTERFACE(IMediaPlayerService, data, reply);
                //create是一个虚函数,由MediaPlayerService来实现!!
                //见frameworks/av/media/libmediaplayerservice/MediaPlayerService.h
                //MediaPlayerService从BnMediaPlayerService派生
                sp<IMediaPlayerClient> client =
                    interface_cast<IMediaPlayerClient>(data.readStrongBinder());
                int audioSessionId = data.readInt32();
                sp<IMediaPlayer> player = create(client, audioSessionId);
                reply->writeStrongBinder(IInterface::asBinder(player));
                return NO_ERROR;
            } break;
            ......
        }
}

其实,到这里,我们就明白了。BnXXX的onTransact函数收取命令,然后派发到派生类的函数,由他们完成实际的工作。

说明:

这里有点特殊,startThreadPool和joinThreadPool完后确实有两个线程,主线程和工作线程,而且都在做消息循环。为什么要这么做呢?他们参数isMain都是true。不知道google搞什么。难道是怕一个线程工作量太多,所以搞两个线程来工作?这种解释应该也是合理的。

网上有人测试过把最后一句屏蔽掉,也能正常工作。但是难道主线程退出了,程序还能不退出吗?这个…管它的,反正知道有两个线程在那处理就行了。

四、 MediaPlayerClient

这节讲讲MediaPlayerClient怎么和MediaPlayerService交互。
使用MediaPlayerService的时候,先要创建它的BpMediaPlayerService。我们看看一个例子
见frameworks/av/media/libmedia/IMediaDeathNotifier.cpp

// establish binder interface to MediaPlayerService
/*static*/const sp<IMediaPlayerService>
IMediaDeathNotifier::getMediaPlayerService()
{
    ALOGV("getMediaPlayerService");
    Mutex::Autolock _l(sServiceLock);
    if (sMediaPlayerService == 0) {
        sp<IServiceManager> sm = defaultServiceManager();
        sp<IBinder> binder;
        do {
            //向SM查询对应服务的信息,返回binder 
            binder = sm->getService(String16("media.player"));
            if (binder != 0) {
                break;
            }
            ALOGW("Media player service not published, waiting...");
            usleep(500000); // 0.5 s
        } while (true);

        if (sDeathNotifier == NULL) {
            sDeathNotifier = new DeathNotifier();
        }
        binder->linkToDeath(sDeathNotifier);
        //通过interface_cast,将这个binder转化成BpMediaPlayerService
        //注意,这个binder只是用来和binder设备通讯用的, 
        //实际上和IMediaPlayerService的功能一点关系都没有。        
        //还记得我说的Bridge模式吗?
        //BpMediaPlayerService用这个binder和BnMediaPlayerService通讯。
        sMediaPlayerService = interface_cast<IMediaPlayerService>(binder);
    }
    ALOGE_IF(sMediaPlayerService == 0, "no media player service!?");
    return sMediaPlayerService;
}

为什么反复强调这个Bridge?其实也不一定是Bridge模式,但是我真正想说明的是:

Binder其实就是一个和binder设备打交道的接口,而上层IMediaPlayerService只不过把它当做一个类似socket使用罢了。我以前经常把binder和上层类IMediaPlayerService的功能混到一起去。所以有一点请注意:

4.1 Native层
刚才那个getMediaPlayerService代码是C++层的,但是整个使用的例子确实JAVA->JNI层的调用。如果我要写一个纯C++的程序该怎么办?

int main()
{
  getMediaPlayerService();//直接调用这个函数能获得BpMediaPlayerService吗?
//不能,为什么?因为我还没打开binder驱动呐!但是你在JAVA应用程序里边却有google已经替你封装好了。
//所以,纯native层的代码,必须也得像下面这样处理:

sp<ProcessState> proc(ProcessState::self());//这个其实不是必须的,因为

//好多地方都需要这个,所以自动也会创建.

getMediaPlayerService();

//还得起消息循环呐,否则如果Bn那边有消息通知你,你怎么接受得到呢?

ProcessState::self()->startThreadPool();

//至于主线程是否也需要调用消息循环,就看个人而定了。不过一般是等着接收其他来源的消息,例如socket发来的命令,然后控制MediaPlayerService就可以了。

}

五、实现自己的Service
好了,我们学习了这么多Binder的东西,那么想要实现一个自己的Service该咋办呢?
如果是纯C++程序的话,肯定得类似main_MediaService那样干了。

int main()
{
    sp<ProcessState> proc(ProcessState::self());
    sp<IServiceManager> sm = defaultServiceManager();   
    sm->addService(“service.name”,new XXXService());    
    ProcessState::self()->startThreadPool();    
    IPCThreadState::self()->joinThreadPool();

}

//看看XXXService怎么定义呢?
//我们需要一个Bn,需要一个Bp,而且Bp不用暴露出来。那么就在BnXXX.cpp中一起实现好了。
//另外,XXXService提供自己的功能,例如getXXX调用

5.1 定义XXX接口
XXX接口是和XXX服务相关的,例如提供getXXX,setXXX函数,和应用逻辑相关。
需要从IInterface派生

class IXXX: public IInterface
{
public:

DECLARE_META_INTERFACE(XXX);//申明宏

virtual getXXX() = 0;

virtual setXXX() = 0;

}//这是一个接口。

5.2 定义BnXXX和BpXXX
为了把IXXX加入到Binder结构,需要定义BnXXX和对客户端透明的BpXXX。

其中BnXXX是需要有头文件的。BnXXX只不过是把IXXX接口加入到Binder架构中来,而不参与实际的getXXX和setXXX应用层逻辑。
这个BnXXX定义可以和上面的IXXX定义放在一块。分开也行。

class BnXXX: public BnInterface<IXXX>
{
public:

    virtual status_t    onTransact( uint32_t code,

                                    const Parcel& data,

                                    Parcel* reply,

                                    uint32_t flags = 0);

//由于IXXX是个纯虚类,而BnXXX只实现了onTransact函数,所以BnXXX依然是一个纯虚类

};

有了DECLARE,那我们在某个CPP中IMPLEMNT它吧。那就在IXXX.cpp中吧。

IMPLEMENT_META_INTERFACE(XXX, "android.xxx.IXXX");//IMPLEMENT宏



status_t BnXXX::onTransact(

    uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags)
{
    switch(code) {
        case GET_XXX: {

            CHECK_INTERFACE(IXXX, data, reply);

           //读请求参数

           //调用虚函数getXXX()

            return NO_ERROR;

        } break; //SET_XXX类似

BpXXX也在这里实现吧。

class BpXXX: public BpInterface<IXXX>

{

public:
    BpXXX (const sp<IBinder>& impl)

        : BpInterface< IXXX >(impl)
    {
    }

    vitural getXXX()
    {
      Parcel data, reply;

      data.writeInterfaceToken(IXXX::getInterfaceDescriptor());

       data.writeInt32(pid);

       remote()->transact(GET_XXX, data, &reply);

       return;
    }
    //setXXX类似
}

至此,Binder就算分析完了,大家看完后,应该能做到以下几点:

  • 如果需要写自己的Service的话,总得知道系统是怎么个调用你的函数。有2个线程在那不停得从binder设备中收取命令,然后调用你的函数。这是个多线程问题。

  • 如果需要跟踪bug的话,得知道从Client端调用的函数,是怎么最终传到到远端的Service。这样,对于一些函数调用,Client端跟踪完了,我就知道转到Service去看对应函数调用了。反正是同步方式。也就是Client一个函数调用会一直等待到Service返回为止。

作者:pengtgimust 发表于2016/8/24 17:55:06 原文链接
阅读:39 评论:0 查看评论

iOS运行时(runtime)探究二:主要函数

$
0
0

一、类相关操作函数

// 获取类的类名
const char * class_getName ( Class cls );

// 获取类的父类
Class class_getSuperclass ( Class cls );

// 判断给定的Class是否是一个元类
BOOL class_isMetaClass ( Class cls );

// 获取实例大小
size_t class_getInstanceSize ( Class cls );

// 获取类中指定名称实例成员变量的信息
Ivar class_getInstanceVariable ( Class cls, const char *name );

// 获取类成员变量的信息
Ivar class_getClassVariable ( Class cls, const char *name );

// 添加成员变量
BOOL class_addIvar ( Class cls, const char *name, size_t size, uint8_t alignment, const char *types );

// 获取整个成员变量列表
Ivar * class_copyIvarList ( Class cls, unsigned int *outCount );

// 获取指定的属性
objc_property_t class_getProperty ( Class cls, const char *name );

// 获取属性列表
objc_property_t * class_copyPropertyList ( Class cls, unsigned int *outCount );

// 为类添加属性
BOOL class_addProperty ( Class cls, const char *name, const objc_property_attribute_t *attributes, unsigned int attributeCount );

// 替换类的属性
void class_replaceProperty ( Class cls, const char *name, const objc_property_attribute_t *attributes, unsigned int attributeCount );

// 添加方法
BOOL class_addMethod ( Class cls, SEL name, IMP imp, const char *types );

// 获取实例方法
Method class_getInstanceMethod ( Class cls, SEL name );

// 获取类方法
Method class_getClassMethod ( Class cls, SEL name );

// 获取所有方法的数组
Method * class_copyMethodList ( Class cls, unsigned int *outCount );

// 替代方法的实现
IMP class_replaceMethod ( Class cls, SEL name, IMP imp, const char *types );

// 返回方法的具体实现
IMP class_getMethodImplementation ( Class cls, SEL name );
IMP class_getMethodImplementation_stret ( Class cls, SEL name );

// 类实例是否响应指定的selector
BOOL class_respondsToSelector ( Class cls, SEL sel );

// 添加协议
BOOL class_addProtocol ( Class cls, Protocol *protocol );

// 返回类是否实现指定的协议
BOOL class_conformsToProtocol ( Class cls, Protocol *protocol );

// 返回类实现的协议列表
Protocol * class_copyProtocolList ( Class cls, unsigned int *outCount );

// 获取版本号
int class_getVersion ( Class cls );

// 设置版本号
void class_setVersion ( Class cls, int version );

二、实例相关操作函数

// 返回指定对象的一份拷贝
id object_copy ( id obj, size_t size );

// 释放指定对象占用的内存
id object_dispose ( id obj );

// 修改类实例的实例变量的值
Ivar object_setInstanceVariable ( id obj, const char *name, void *value );

// 获取对象实例变量的值
Ivar object_getInstanceVariable ( id obj, const char *name, void **outValue );

// 返回指向给定对象分配的任何额外字节的指针
void * object_getIndexedIvars ( id obj );

// 返回对象中实例变量的值
id object_getIvar ( id obj, Ivar ivar );

// 设置对象中实例变量的值
void object_setIvar ( id obj, Ivar ivar, id value );

// 返回给定对象的类名
const char * object_getClassName ( id obj );
// 返回对象的类
Class object_getClass ( id obj );

// 设置对象的类
Class object_setClass ( id obj, Class cls );

// 获取已注册的类定义的列表
int objc_getClassList ( Class *buffer, int bufferCount );

// 创建并返回一个指向所有已注册类的指针列表
Class * objc_copyClassList ( unsigned int *outCount );

// 返回指定类的类定义
Class objc_lookUpClass ( const char *name );
Class objc_getClass ( const char *name );
Class objc_getRequiredClass ( const char *name );

// 返回指定类的元类
Class objc_getMetaClass ( const char *name );

// 设置关联对象
void objc_setAssociatedObject ( id object, const void *key, id value, objc_AssociationPolicy policy );

// 获取关联对象
id objc_getAssociatedObject ( id object, const void *key );

// 移除关联对象
void objc_removeAssociatedObjects ( id object );

// 获取所有加载的Objective-C框架和动态库的名称
const char ** objc_copyImageNames ( unsigned int *outCount );

// 获取指定类所在动态库
const char * class_getImageName ( Class cls );

// 获取指定库或框架中所有类的类名
const char ** objc_copyClassNamesForImage ( const char *image, unsigned int *outCount );

三、属性操作相关函数

// 获取属性名
const char * property_getName ( objc_property_t property );

// 获取属性特性描述字符串
const char * property_getAttributes ( objc_property_t property );

// 获取属性中指定的特性
char * property_copyAttributeValue ( objc_property_t property, const char *attributeName );

// 获取属性的特性列表
objc_property_attribute_t * property_copyAttributeList ( objc_property_t property, unsigned int *outCount );

四、 方法操作相关函数

// 调用指定方法的实现
id method_invoke ( id receiver, Method m, ... );

// 调用返回一个数据结构的方法的实现
void method_invoke_stret ( id receiver, Method m, ... );

// 获取方法名
SEL method_getName ( Method m );

// 返回方法的实现
IMP method_getImplementation ( Method m );

// 获取描述方法参数和返回值类型的字符串
const char * method_getTypeEncoding ( Method m );

// 获取方法的返回值类型的字符串
char * method_copyReturnType ( Method m );

// 获取方法的指定位置参数的类型字符串
char * method_copyArgumentType ( Method m, unsigned int index );

// 通过引用返回方法的返回值类型字符串
void method_getReturnType ( Method m, char *dst, size_t dst_len );

// 返回方法的参数的个数
unsigned int method_getNumberOfArguments ( Method m );

// 通过引用返回方法指定位置参数的类型字符串
void method_getArgumentType ( Method m, unsigned int index, char *dst, size_t dst_len );

// 返回指定方法的方法描述结构体
struct objc_method_description * method_getDescription ( Method m );

// 设置方法的实现
IMP method_setImplementation ( Method m, IMP imp );

// 交换两个方法的实现
void method_exchangeImplementations ( Method m1, Method m2 );

五、选择器相关的操作函数

// 返回给定选择器指定的方法的名称
const char * sel_getName ( SEL sel );

// 在Objective-C Runtime系统中注册一个方法,将方法名映射到一个选择器,并返回这个选择器
SEL sel_registerName ( const char *str );

// 在Objective-C Runtime系统中注册一个方法
SEL sel_getUid ( const char *str );

// 比较两个选择器
BOOL sel_isEqual ( SEL lhs, SEL rhs );

六、协议相关的操作函数

// 返回指定的协议
Protocol * objc_getProtocol ( const char *name );

// 获取运行时所知道的所有协议的数组
Protocol ** objc_copyProtocolList ( unsigned int *outCount );

// 创建新的协议实例
Protocol * objc_allocateProtocol ( const char *name );

// 在运行时中注册新创建的协议
void objc_registerProtocol ( Protocol *proto );

// 为协议添加方法
void protocol_addMethodDescription ( Protocol *proto, SEL name, const char *types, BOOL isRequiredMethod, BOOL isInstanceMethod );

// 添加一个已注册的协议到协议中
void protocol_addProtocol ( Protocol *proto, Protocol *addition );

// 为协议添加属性
void protocol_addProperty ( Protocol *proto, const char *name, const objc_property_attribute_t *attributes, unsigned int attributeCount, BOOL isRequiredProperty, BOOL isInstanceProperty );

// 返回协议名
const char * protocol_getName ( Protocol *p );

// 测试两个协议是否相等
BOOL protocol_isEqual ( Protocol *proto, Protocol *other );

// 获取协议中指定条件的方法的方法描述数组
struct objc_method_description * protocol_copyMethodDescriptionList ( Protocol *p, BOOL isRequiredMethod, BOOL isInstanceMethod, unsigned int *outCount );

// 获取协议中指定方法的方法描述
struct objc_method_description protocol_getMethodDescription ( Protocol *p, SEL aSel, BOOL isRequiredMethod, BOOL isInstanceMethod );

// 获取协议中的属性列表
objc_property_t * protocol_copyPropertyList ( Protocol *proto, unsigned int *outCount );

// 获取协议的指定属性
objc_property_t protocol_getProperty ( Protocol *proto, const char *name, BOOL isRequiredProperty, BOOL isInstanceProperty );

// 获取协议采用的协议
Protocol ** protocol_copyProtocolList ( Protocol *proto, unsigned int *outCount );

// 查看协议是否采用了另一个协议
BOOL protocol_conformsToProtocol ( Protocol *proto, Protocol *other );

七、block块相关的操作函数

// 创建一个指针函数的指针,该函数调用时会调用特定的block
IMP imp_implementationWithBlock ( id block );

// 返回与IMP(使用imp_implementationWithBlock创建的)相关的block
id imp_getBlock ( IMP anImp );

// 解除block与IMP(使用imp_implementationWithBlock创建的)的关联关系,并释放block的拷贝
BOOL imp_removeBlock ( IMP anImp );
作者:jiuchabaikaishui 发表于2016/8/24 18:09:51 原文链接
阅读:23 评论:0 查看评论

MPAndroidChart系列源码解读(五)

$
0
0

本篇主要是LineChart实战相关知识和简单的源码剖析,相关源码没有,自己动手实践学习才是最有效的方法。


LineChart Simple

运行效果图

个人感官觉得某些属性设置后太难看了并没有添加,so效果图上没有显示,如果你想测试这些属性自行参考下面的api介绍

一些调用方法说明

设置图标数据内容视图的背景颜色(默认RGB(240,240,240))

mChart.setDrawGridBackground(true);
mChart.setGridBackgroundColor(Color.RED);

关于图表的描述文字,默认放在图表右下角10个像素padding,也可以自己手动设置字体、文字大小、文字显示位置等

mChart.setDescription("LineChart");
mChart.setDescriptionColor(Color.BLACK);
mChart.setDescriptionPosition(300 , 300);
mChart.setDescriptionTextSize(16);
mChart.setDescriptionTypeface(Typeface.DEFAULT_BOLD);
mChart.setNoDataTextDescription("LineChart no Data");

这里特别提示Description绘制的setDescritionPosition方法的值,并不是设置的绘制文字起始位置,而是结束位置。参考下面

Touch触摸滑动相关,如果禁用了MaskView以及点击缩放滑动都不起效果了(默认没有禁用)

 mChart.setTouchEnabled(false);

关于图表更多关于缩放相关的参考下面API

  //是否允许拖拽图表
  mChart.setDragEnabled(true);
  //是否允许缩放图表
  mChart.setScaleEnabled(true);
  //是否允许缩放X轴比例
  mChart.setScaleXEnabled(true);
  //是否允许缩放Y轴比例
  mChart.setScaleYEnabled(true);

  //setScaleEnabled方法就是禁用x、y两个轴都不能缩放如果在这个方法调用后再单独调用setScaleXEnable、setScaleYEnable

  //就没有了之前x、y轴都不能缩放的效果,单击照样能缩放。

setPinchZoom(true) 时,x y轴同时缩放坐标尺比例根据手势缩放,false测更具手势方向和是否禁止缩放判断缩放各自对应轴的刻度值比例,下图是true时的效果概念图

设置整个图表的背景色

 mChart.setBackgroundColor(Color.GRAY);

触摸图标会有个高亮的十字虚线出现和MaskView弹出,这玩意在之前blog就有提到过,这里不解释了,调用参考官方Simple

 MyMarkerView mv = new MyMarkerView(this, R.layout.custom_marker_view);
 mChart.setMarkerView(mv);

图表上添加x、y轴的轴线LimitLine,参考下面流程(LimitLine的属性set方法自己参考API即可)

  LimitLine limitLineX = new LimitLine(20,"");
  LimitLine limitLineY = new LimitLine(50,"");

  // .......config limitLine x y...............    

  XAxis xAxis = lineChart.getXAxis();
  YAxis yAxis = lineChart.getAxisLeft();

  xAxis.addLimitLine(limitLineX);
  yAxis.addLimitLine(limitLineY);

补充说明(Lable对齐位置参考LimitLabelPosition枚举,关于轴线相关API请自行参考源码,之前blog也已有过理解,不再叙述):

最后一步就是装数据设置Data,这里得注意一点,不同版本会有所变化,这里提供的是最新版本的方式setValues,MaskView之前拷贝以前下载的Simple里面的对应的有些方法不支持也需要自行参考自己版本对应类提供的方法

 public void setData(ArrayList<Integer> valuesY){
        ArrayList<Entry> values = new ArrayList<Entry>();
        for (int i = 0; i < valuesY.size(); i++) {
            values.add(new Entry(i, valuesY.get(i)));
        }
        LineDataSet set1;
        if (lineChart.getData() != null &&
                lineChart.getData().getDataSetCount() > 0) {
            set1 = (LineDataSet) lineChart.getData().getDataSetByIndex(0);
            set1.setValues(values);
            lineChart.getData().notifyDataChanged();
            lineChart.notifyDataSetChanged();
        } else {
            set1 = new LineDataSet(values, "LimitLine 1");
            set1.enableDashedLine(10f, 5f, 0f);
            set1.enableDashedHighlightLine(10f, 5f, 0f);
           //TODO 省略config set1自行参考源代码里面的Simple,见名知其意,不过多解释
            ArrayList<ILineDataSet> dataSets = new ArrayList<ILineDataSet>();
            dataSets.add(set1);
            LineData data = new LineData(dataSets);
            lineChart.setData(data);
        }
    }

这里再提供一些不常用的API方法

 //设置Chart缩放的最大限度值
 mChart.getViewPortHandler().setMaximumScaleY(2f);
 mChart.getViewPortHandler().setMaximumScaleX(2f);

//移动图表位置中心
mChart.centerViewTo(200, 500, YAxis.AxisDependency.LEFT);

//图例样式配置
Legend l = mChart.getLegend();
l.setForm(LegendForm.SQUARE);//正方形、圆形、线条
l.setHorizontalAlignment(Legend.LegendHorizontalAlignment.RIGHT);//位置
l.setPosition(Legend.LegendPosition.RIGHT_OF_CHART);//位于Chart右侧

//具体使用参考枚举类相关源码

图例样式类似下图效果


LineChart源码分析

LineChart简化版UML

LIneChart核心知识点

ChartInterface 该接口提供chart视图已知的尺寸、界限、范围等,在之前blog已有提到这里不过多解释。
BarLineScatterCandleBubbleDataProvider、LineDataProvider从继承来看都是相关属性的扩展,之前同样blog有提到,具体实现在Chart相关类里面。

LineChart Draw方法相关的无非就是drawText、drawLine这些相关方法,这里就不提了,这里呢简单说一下核心工作流程:

onTouch方法通过代理的方式处理Touch事件

public abstract class BarLineChartBase<T extends BarLineScatterCandleBubbleData<? extends IBarLineScatterCandleBubbleDataSet<? extends Entry>>>
        extends Chart<T> implements BarLineScatterCandleBubbleDataProvider {

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        super.onTouchEvent(event);

        if (mChartTouchListener == null || mData == null)
            return false;

        // check if touch gestures are enabled
        if (!mTouchEnabled)
            return false;
        else
            return mChartTouchListener.onTouch(this, event);
    }


}

touch移动需要computeScroll,这个方法很久以前blog了解过了不累赘叙述具体用途

  @Override
    public void computeScroll() {

        if (mChartTouchListener instanceof BarLineChartTouchListener)
            ((BarLineChartTouchListener) mChartTouchListener).computeScroll();
    }

而ChartTouchListener仅仅是一个抽象类,很多方法都没有具体实现,具体实现在请移到这里BarLineChartTouchListener。

这里么的拖拽和缩放执行通过一些列的计算判断条件执行Matrix的postTranslate和postScale方法达到效果,当然这里面涉及到的还有模式的修改、移动的处理、高亮显示等相关事件。

至于MaskView是在什么地方被draw到图表中的呢?接着往下看..
在BarLineChartTouchListener处理touch事件引起的重绘回回到父类BarLineChartBase的onDraw方法里面,调用到父类Chart的 drawMarkers(canvas);方法,下面是具体方法块(MaskVew的绘制的和取消绘制不用多说了吧)

protected void drawMarkers(Canvas canvas) {

        // if there is no marker view or drawing marker is disabled
        if (mMarkerView == null || !mDrawMarkerViews || !valuesToHighlight())
            return;

        for (int i = 0; i < mIndicesToHighlight.length; i++) {

            Highlight highlight = mIndicesToHighlight[i];
            int xIndex = highlight.getXIndex();
            int dataSetIndex = highlight.getDataSetIndex();

            float deltaX = mXAxis != null 
                ? mXAxis.mAxisRange
                : ((mData == null ? 0.f : mData.getXValCount()) - 1.f);

            if (xIndex <= deltaX && xIndex <= deltaX * mAnimator.getPhaseX()) {

                Entry e = mData.getEntryForHighlight(mIndicesToHighlight[i]);

                // make sure entry not null
                if (e == null || e.getXIndex() != mIndicesToHighlight[i].getXIndex())
                    continue;

                float[] pos = getMarkerPosition(e, highlight);

                // check bounds
                if (!mViewPortHandler.isInBounds(pos[0], pos[1]))
                    continue;

                // callbacks to update the content
                mMarkerView.refreshContent(e, highlight);

                // mMarkerView.measure(MeasureSpec.makeMeasureSpec(0,
                // MeasureSpec.UNSPECIFIED),
                // MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));
                // mMarkerView.layout(0, 0, mMarkerView.getMeasuredWidth(),
                // mMarkerView.getMeasuredHeight());
                // mMarkerView.draw(mDrawCanvas, pos[0], pos[1]);

                mMarkerView.measure(MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED),
                        MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));
                mMarkerView.layout(0, 0, mMarkerView.getMeasuredWidth(),
                        mMarkerView.getMeasuredHeight());

                if (pos[1] - mMarkerView.getHeight() <= 0) {
                    float y = mMarkerView.getHeight() - pos[1];
                    mMarkerView.draw(canvas, pos[0], pos[1] + y);
                } else {
                    mMarkerView.draw(canvas, pos[0], pos[1]);
                }
            }
        }
    }

在官方simple的Activity有实现接口OnChartGestureListener,在onChartGestureEnd方法有执行如下操作

@Override
    public void onChartGestureEnd(MotionEvent me, ChartTouchListener.ChartGesture lastPerformedGesture) {
        if(lastPerformedGesture != ChartTouchListener.ChartGesture.SINGLE_TAP)
            lineChart.highlightValues(null);
    }

如果不是轻敲的触摸了屏幕,就取消MaskView的绘制。

由于Touch的具体实现个人不想再细致分析,感觉太累就点到为止,了解大致工作流程就好。

小结

学到两点知识:

1.接口的设计

2.MaskView draw到画布

以上内容你若觉得可以欢迎点赞,记得点赞哦!本篇若有理解错误之处还望指出,以免误人子弟,有问题在个人知识范围内欢迎交流。逗逼的学习之路途漫漫,还好最近工作不忙,明天继续..

作者:AnalyzeSystem 发表于2016/8/24 18:21:44 原文链接
阅读:38 评论:0 查看评论

安卓学习笔记之Drawable

$
0
0

Drawable基础

什么是Drawable

首先Drawable是一个抽象类,表示的是可以在Canvas中绘制的图像,常被用作一个view的背景,有多种实现类完成不同的功能。其次Drawable大致可以分为这几类:图片、由颜色构成的图像。一般用xml中进行定义。

Drawable的继承体系

这里写图片描述

Drawable的实现类及标签

这里写图片描述

Drawable内部宽高的获取

    getIntrinsicHeight、getIntrinsicWidth
    - 当Drawable由图片构成时方法返回的是图片的宽高
    -  当Drawable由颜色构成时则没有宽高的概念,返回-1

各类Drawable及其用法

BitmapDrawable

用于显示一张图片,如下示例

<?xml version="1.0" encoding="utf-8"?>
<bitmap xmlns:android="http://schemas.android.com/apk/res/android"
    android:antialias="true"
    android:dither="true"
    android:filter="true"
    android:gravity="top"
    android:src="@mipmap/girl"
    android:tileMode="repeat" />

常用属性

android:antialias 是否开启抗锯齿
android:dither 是否开启防抖动
android:filter 是否开启过滤效果
android:gravity 用于对图片进行定位
android:src 图片资源id
android:tileMode 平铺模式,repeat、mirror、clamp三种 

ColorDrawable

代表了单色可绘制区域,包装了一种固定的颜色,在画布上绘制一块单色的区域。

示例:

<?xml version="1.0" encoding="utf-8"?>
<color xmlns:android="http://schemas.android.com/apk/res/android" android:color="@color/colorAccent">
</color>

还可以用代码创建

ColorDrawable drawable = new ColorDrawable(int color); //传入一个color的integer值

NinePatchDrawable

即9-patch图,可以根据内容进行自由缩放宽高而不失真

示例:

<?xml version="1.0" encoding="utf-8"?>
<nine-patch xmlns:android="http://schemas.android.com/apk/res/android"
    android:dither="true"
    android:filter="true"
    android:src="@color/colorAccent">
</nine-patch>

用draw9patch设定缩放区域

图中1、2方向表示在draw9patch中绘制黑线,黑线长度交集为可拉伸的范围
图中3、4方向黑线长度交集表示内容可以填充的区域
这里写图片描述

ShapeDrawable

通过颜色来构造图形,既可以为纯色图形,也可以为具有渐变效果的图形。能构成的图形有rectangle、oval、ring、line

具有渐变效果的圆示例:

<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
    android:shape="oval">
    <gradient
        android:angle="45"
        android:centerColor="@color/colorAccent"
        android:centerX="50%"
        android:centerY="50%"
        android:endColor="@color/colorPrimary"
        android:gradientRadius="150dp"
        android:startColor="@color/colorPrimaryDark"
        android:type="sweep" />
    <size
        android:width="260dp"
        android:height="260dp" />
</shape>

注意:1、android:angle值必须为45的倍数 2、oval用于绘制椭圆,当size的宽高相等时绘制成圆

圆环示例:

<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
    android:innerRadius="100dp"
    android:shape="ring"
    android:thickness="10dp"
    android:useLevel="false" >

    <stroke
        android:width="10dp"
        android:color="@color/colorAccent" />
</shape>
注:
1、android:useLevel设置为false,否则无法显示理想效果
2、innerRadius为圆环内半径,innerRadiusRation为内半径占圆环宽度的比率,两者以innerRadius为主
3、thickness为圆环的宽度,thicknessRatio为此宽度占圆环宽度的比率,以thickness为主

常用属性

- android:shape 要绘制的形状,rectangle、oval、ring、line
- <stroke> 形状的描边,有如下属性
        - android:width 描边的宽度
        - android:color 描边的颜色
        - android:dashGap 绘制虚线的线宽
        - android:dashWidth 绘制虚线的线段间隔 (要绘制虚线,后两者均不能为0)
-<solid> 纯色填充,android:color指定shape颜色 
- <gradient> 渐变效果,与solid不可一起用,有如下属性
        - android:angle 渐变的角度,必须为45的倍数
        - android:startColor 渐变的起始颜色
        - android:centerColor 渐变的中间颜色
        - android:endColor 渐变的结束颜色
        - android:centerX 渐变的中心点横坐标
        - android:centerY 渐变的中心点纵坐标
        - android:gradientRadius 渐变半径
        - android:type 渐变类型,linear(线性)、sweep(扫视)、radial(径向)
- <corners> 表示矩形(rectangle)的四个角的角度,不适用于其他shape ,有如下属性
        - android:topLeftRadius、android:topRightRadius、android:bottomLeftRadius、android:bottomRightRadius 分别为设置左上角、右上角、左下角、右下角的角度
        - android:radius 为四角设置相同角度,优先级低,会被其他四个属性覆盖
- <size> shape的宽高,对应着android:width、android:height
        -  shape默认无宽高,getIntrinsicHeight、getIntrinsicWidth返回-1
        -  通过size可以设置其宽高,但作为view背景时任然会被拉伸或缩小为 view大小
- <padding> 设置容纳shape的view的空白间距

StateListDrawable

可以看作是一个状态选择器,通过view不同的状态选择对应的item中的drawable显示

示例:

<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:drawable="@color/colorPrimaryDark" android:state_pressed="false"></item>
    <item android:drawable="@color/colorAccent" android:state_pressed="true"></item>
</selector>

常见状态

android:state_pressed 当按住一个view时,按下的状态
android:state_checked  当一个view被选中时,适用于CheckBox
android:state_selected  当一个view被选择时
android:state_enabled  当一个view处于可用状态
android:state_focused  当view获取焦点

LayerDeawable

表示的是一种分层的的Drawable集合,类似于ps中的图层的概念,将多个drawable放在不同的层上面形成一种叠加的效果

示例:

<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:drawable="@mipmap/night" />
    <item
        android:drawable="@mipmap/photo6"
        android:gravity="center" />
</layer-list>

注意事项:

1、layer-list可以包含多个item,每个item表示一个drawable,并且后添加的item会覆盖到之前添加的item上面
2、默认情况下,layer-list所有的drawable都会缩放至view大大小,通过设施android:gravity可以调节缩放的效果
3、可以设置上下左右偏移量,android:top、android:bottom、android:left、android:right

LevelListDrawable

表示一个drawable集合,集合中的每一个Drawable都有一个等级(level),通过设置不同的等级,可以使LevelListDrawable切换至不同的Drawable。等级范围在0~10000之间, android:maxLevel设置最大level, android:minLevel设置最小level
示例:

<?xml version="1.0" encoding="utf-8"?>
<level-list xmlns:android="http://schemas.android.com/apk/res/android">
    <item
        android:drawable="@mipmap/photo0"
        android:maxLevel="20"
        android:minLevel="10" />
    <item
        android:drawable="@mipmap/photo1"
        android:maxLevel="40"
        android:minLevel="30" />
</level-list>

通过设置level可以切换不同的Drawable,在代码中

    //将ImageView的背景切换为photo1, 35 在30~40之间
    iv.setImageLevel(35); 
    //将ImageView的背景切换为photo0, 15在10~20之间
    iv.setImageLevel(15); 

TransitionDrawable

LayerDeawable的子类,用于实现连个Drawable的淡入淡出效果
示例:
xml文件定义

<?xml version="1.0" encoding="utf-8"?>
<transition xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:drawable="@mipmap/night" />
    <item android:drawable="@mipmap/photo6" />
</transition>

给ImageView设置src,在java代码中

    iv= (ImageView) findViewById(R.id.iv_transition);
    drawable = (TransitionDrawable) iv.getDrawable();
    drawable.startTransition(1000); // 实现淡入淡出效果
    drawable.reverseTransition(1000);

InsetDrawable

嵌入其他Drawable,并可以在四周保留一定的间距
示例:

<?xml version="1.0" encoding="utf-8"?>
<inset xmlns:android="http://schemas.android.com/apk/res/android"
    android:drawable="@mipmap/photo6"
    android:inset="20dp">
</inset>

ScaleDrawable

根据等级将一个Drawable缩放到一定的比例,当level为0时不可见,当level为10000时无缩放效果
示例:

<?xml version="1.0" encoding="utf-8"?>
<scale xmlns:android="http://schemas.android.com/apk/res/android"
    android:drawable="@mipmap/night"
    android:scaleGravity="center"
    android:scaleHeight="50%"
    android:scaleWidth="50%" />

要显示出效果,必须设置level大于0

   iv = (ImageView) findViewById(R.id.iv_scale);
   ScaleDrawable drawable= (ScaleDrawable) iv.getDrawable();
   drawable.setLevel(1);
- android:scaleHeight="percentage",android:scaleWidth="percentage",设置宽高缩放为原来的比例为(100%-percentage)
- 设置level越大,图像显示越大

ClipDrawable

根据自己的等级(level)来对另一个Drawable进行裁剪,裁剪的方向由android:clipOrientation、android:gravity共同决定。设置level进行裁剪,level的大小从0到10000,level为0时完全不显示,为10000时完全显示
xml定义

<?xml version="1.0" encoding="utf-8"?>
<clip xmlns:android="http://schemas.android.com/apk/res/android"
    android:clipOrientation="horizontal"
    android:drawable="@mipmap/night"
    android:gravity="right"></clip>

设置给ImageView


    <ImageView
        android:id="@+id/iv_clip"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:src="@drawable/drawable_clip" />

通过设置level来裁剪

    ImageView iv = (ImageView) findViewById(R.id.iv_clip);
    ClipDrawable drawable= (ClipDrawable) iv.getDrawable();
    drawable.setLevel(5000); //  设置的level越大裁剪的范围越小

属性

android:clipOrientation ,horizontal 水平方向裁剪,vertical 垂直方向裁剪
android:gravity ,配合裁剪方向

这里写图片描述


自定义Drawable

自定义圆形Drawable

package com.yu.drawablelearing;

import android.graphics.Bitmap;
import android.graphics.BitmapShader;
import android.graphics.Canvas;
import android.graphics.ColorFilter;
import android.graphics.Paint;
import android.graphics.PixelFormat;
import android.graphics.Shader;
import android.graphics.drawable.Drawable;

public class CircleDrawable extends Drawable{

    private int radius;
    private int mWidth;
    private int mHeight;
    private Paint mPaint;
    @Override
    public void draw(Canvas canvas) {
        canvas.drawCircle(mWidth/2,mHeight/2,radius,mPaint);
    }

    public CircleDrawable(Bitmap bitmap) {
        radius = Math.min(bitmap.getWidth(), bitmap.getHeight())/2;
        mWidth = bitmap.getWidth();
        mHeight = bitmap.getHeight();
        BitmapShader bitmapShader = new BitmapShader(bitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);
        mPaint = new Paint();
        mPaint.setShader(bitmapShader);
        mPaint.setAntiAlias(true);

    }
    @Override
    public void setAlpha(int alpha) {
        mPaint.setAlpha(alpha);
        invalidateSelf();
    }

    @Override
    public void setColorFilter(ColorFilter colorFilter) {
        mPaint.setColorFilter(colorFilter);
        invalidateSelf();
    }

    @Override
    public int getOpacity() {
        return PixelFormat.TRANSLUCENT;
    }

    @Override
    public int getIntrinsicHeight() {
        return mHeight;
    }

    @Override
    public int getIntrinsicWidth() {
        return mWidth;
    }
}

自定义带圆角的矩形Drawable

package com.yu.drawablelearing;

import android.graphics.Bitmap;
import android.graphics.BitmapShader;
import android.graphics.Canvas;
import android.graphics.ColorFilter;
import android.graphics.Paint;
import android.graphics.PixelFormat;
import android.graphics.RectF;
import android.graphics.Shader;
import android.graphics.drawable.Drawable;

/**
 * Created by pecu on 2016/08/24.
 */
public class RoundRectangleDrawable extends Drawable {
    private RectF rectF;
    private Paint mPaint;
    Bitmap mBitmap;
    @Override
    public void draw(Canvas canvas) {
        canvas.drawRoundRect(rectF, mBitmap.getWidth()/6,mBitmap.getHeight()/6, mPaint);
    }

    public RoundRectangleDrawable(Bitmap bitmap) {
        mBitmap = bitmap;
        mPaint = new Paint();
        BitmapShader bitmapShader = new BitmapShader(bitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);
        mPaint.setAntiAlias(true);
        mPaint.setShader(bitmapShader);
    }
    @Override
    public void setAlpha(int alpha) {
        mPaint.setAlpha(alpha);
        invalidateSelf();
    }

    @Override
    public void setColorFilter(ColorFilter colorFilter) {
        mPaint.setColorFilter(colorFilter);
        invalidateSelf();

    }

    @Override
    public void setBounds(int left, int top, int right, int bottom) {
        super.setBounds(left, top, right, bottom);
        rectF = new RectF(left, top, right, bottom);
    }

    @Override
    public int getOpacity() {
        return PixelFormat.TRANSLUCENT;
    }

    @Override
    public int getIntrinsicWidth() {
        return mBitmap.getWidth();
    }

    @Override
    public int getIntrinsicHeight() {
        return mBitmap.getHeight();
    }

}

自定义Drawable的一般步骤

 1.  自定义Drawable类继承自Drawable
 2.  实现getOpacity,setColorFilter,setAlpha等方法
 3.  在onDraw方法中进行绘制
 4.  若自定义的Drawable有固定的大小,则需实现getIntrinsicWidth、getIntrinsicHeight方法
作者:qq_28261343 发表于2016/8/24 18:29:25 原文链接
阅读:23 评论:0 查看评论

GLSurfaceView渲染过程详解

$
0
0


GLSurfaceView提供了下列特性:
1> 管理一个surface,这个surface就是一块特殊的内存,能直接排版到android的视图view上。
2> 管理一个EGL display,它能让opengl把内容渲染到上述的surface上。
3> 用户自定义渲染器(render)。
4> 让渲染器在独立的线程里运作,和UI线程分离。
5> 支持按需渲染(on-demand)和连续渲染(continuous)。
6> 一些可选工具,如调试。


概念

Display(EGLDisplay) 是对实际显示设备的抽象。
Surface(EGLSurface)是对用来存储图像的内存区域FrameBuffer的抽象,包括Color Buffer, Stencil Buffer ,Depth Buffer.
Context (EGLContext) 存储OpenGL ES绘图的一些状态信息。


步骤:

获取EGLDisplay对象
初始化与EGLDisplay 之间的连接。
获取EGLConfig对象
创建EGLContext 实例
创建EGLSurface实例
连接EGLContext和EGLSurface.
使用GL指令绘制图形
断开并释放与EGLSurface关联的EGLContext对象
删除EGLSurface对象
删除EGLContext对象
终止与EGLDisplay之间的连接。


GLSurfaceView的绘制流程


由上图可知,GLSurfaceView的主要绘制过程都是在一个子线程中完成,即整个绘制最终都是guardenRun()中完成。在这个过程中完成了整个EGL绘制的所有步骤。

我把guardenRun()的大多数细节代码都删掉了,剩下一些精华:

 private void guardedRun() throws InterruptedException {

                while (true) {
                    synchronized (sGLThreadManager) {
                        while (true) {

                            // Ready to draw?
                            if (readyToDraw()) {
                                // If we don't have an EGL context, try to acquire one.
                                if (! mHaveEglContext) {
                                    if (sGLThreadManager.tryAcquireEglContextLocked(this)) {
                                         mEglHelper.start();
                                    }
                                }

                            sGLThreadManager.wait();
                        }
                    } // end of synchronized(sGLThreadManager)

                    if (createEglSurface) {
                        if (mEglHelper.createSurface()) {
                           ...
                        }
                    }

                    if (createGlInterface) {
                        gl = (GL10) mEglHelper.createGL();
                    }

                    if (createEglContext) {
                        if (view != null) {
                            view.mRenderer.onSurfaceCreated(gl, mEglHelper.mEglConfig);
                        }
                    }

                    if (sizeChanged) {
                        if (view != null) {
                            view.mRenderer.onSurfaceChanged(gl, w, h);
                        }
                        sizeChanged = false;
                    }

                
                    if (view != null) {
                        view.mRenderer.onDrawFrame(gl);
                    }
                    
                    int swapError = mEglHelper.swap();
        }

其中mEglHelper.start():

        public void start() {
            /*
             * Get an EGL instance
             */
            mEgl = (EGL10) EGLContext.getEGL();

            /*
             * Get to the default display.
             */
            mEglDisplay = mEgl.eglGetDisplay(EGL10.EGL_DEFAULT_DISPLAY);

          
            mEglConfig = view.mEGLConfigChooser.chooseConfig(mEgl, mEglDisplay);

            /*
            * Create an EGL context. We want to do this as rarely as we can, because an
            * EGL context is a somewhat heavy object.
            */
            mEglContext = view.mEGLContextFactory.createContext(mEgl, mEglDisplay, mEglConfig);
        
        }

mEglHelper.start()就完成了4步:

1,获取EGLDisplay对象

2,初始化与EGLDisplay 之间的连接。
3,获取EGLConfig对象
4,创建EGLContext 实例

请注意注解中提到createContext()创建的mEglContext是一个重量级对象,在创建的时候很耗资源,我们尽可能少的创建它。所以,在guardenRun()中我们做了对mEglContext的是否存在的判断:

 if (! mHaveEglContext) {
                       if (sGLThreadManager.tryAcquireEglContextLocked(this)) {
                                         mEglHelper.start();
                                    }
                                }

接下来createSurface()

 /**
         * Create an egl surface for the current SurfaceHolder surface. If a surface
         * already exists, destroy it before creating the new surface.
         *
         * @return true if the surface was created successfully.
         */
        public boolean createSurface() {
            if (view != null) {
                mEglSurface = view.mEGLWindowSurfaceFactory.createWindowSurface(mEgl,
                        mEglDisplay, mEglConfig, view.getHolder());
            } 

            /*
             * Before we can issue GL commands, we need to make sure
             * the context is current and bound to a surface.
             */
            if (!mEgl.eglMakeCurrent(mEglDisplay, mEglSurface, mEglSurface, mEglContext)) {
                /*
                 * Could not make the context current, probably because the underlying
                 * SurfaceView surface has been destroyed.
                 */
                return false;
            }
            return true;
        }
这里主要完成了两件事:

5,创建mEglSurface,这个代表了将要被渲染的那段内存。请注意到createWindowSurface()的四个参数,尤其是最后一个参数view.getHolder()。

createSurface()上面有一句注解:Create an egl surface for the current SurfaceHolder surface.这个只能意会,很难言传。我理解是被渲染后的mEglSurface也是为了给mSurface来呈现的。总之mEglSurface和mSurface之间一定有着很重要的关系的,在一定程度上你也可以理解他们代表着同一块用来渲染的内存。

6,连接EGLContext和EGLSurface:eglMakeCurrent()。


7,使用GL指令绘制图形

<span style="white-space:pre">		</span>    if (createGlInterface) {
                        gl = (GL10) mEglHelper.createGL();
                    }

                    if (createEglContext) {
                        if (view != null) {
                            view.mRenderer.onSurfaceCreated(gl, mEglHelper.mEglConfig);
                        }
                    }

                    if (sizeChanged) {
                        if (view != null) {
                            view.mRenderer.onSurfaceChanged(gl, w, h);
                        }
                        sizeChanged = false;
                    }

                
                    if (view != null) {
                        view.mRenderer.onDrawFrame(gl);
                    }
所以在实现Render看到的GL10 gl,就是从这里传过来的。

在整个guardenRun()过程中,你应该要发现一个很重要的点,这是一个无限循环的程序,而onDrawFrame(gl)几乎是没有设置任何障碍就可以每次循环都被触发。而onDrawFrame(gl)的实现正是整个渲染的主体部分,由Render的子类来实现。

后面几个步骤就不一一讲诉了

8,断开并释放与EGLSurface关联的EGLContext对象
9,删除EGLSurface对象
10,删除EGLContext对象
11,终止与EGLDisplay之间的连接。


在使用GlSurfaceView的时候,通常会继承GLSurfaceView,并重载一些和用户输入事件有关的方法。如果你不需要重载事件方法,GLSurfaceView也可以直接使用, 你可以使用set方法来为该类提供自定义的行为。

说到这里,我就上一个最简化的demo:

public class MainActivity extends Activity {
    private MyGLSurfaceView mGLView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        mGLView = new MyGLSurfaceView(this);
        mGLView.setRenderer(new ClearRenderer());
        setContentView(mGLView);
    }

    @Override
    protected void onPause() {
        super.onPause();
        mGLView.onPause();
    }

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

    class ClearRenderer implements MyGLSurfaceView.Renderer {

        @Override
        public void onSurfaceCreated(GL10 gl, javax.microedition.khronos.egl.EGLConfig config) {

        }

        public void onSurfaceChanged (GL10 gl, int w, int h)
        {
            gl.glViewport(0, 0, w, h);
        }

        public void onDrawFrame(GL10 gl) {
            gl.glClearColor(mRed, mGreen, mBlue, 1.0f);
            gl.glClear(GL10.GL_COLOR_BUFFER_BIT | GL10.GL_DEPTH_BUFFER_BIT);
        }

    }
}




GLSurfaceView的绘制过程要点

1,GLSurfaceview的渲染模式RenderMode

在onAttachedToWindow后就启动了一个无线循环的子线程,该子线程完成了整个绘制流程,并系统默认是负责不断刷新重绘,刷新的帧率是16FPS。从这里也可以看出来,GLSurfaceView系统默认是60ms就重绘一次,这样的耗性能的重绘操作一定是要用在那种有持续动画的效果才有意义。

当然,你也可以通过设置setRenderMode去设置主动刷新:

    /**
     * Set the rendering mode. When renderMode is
     * RENDERMODE_CONTINUOUSLY, the renderer is called
     * repeatedly to re-render the scene. When renderMode
     * is RENDERMODE_WHEN_DIRTY, the renderer only rendered when the surface
     * is created, or when {@link #requestRender} is called. Defaults to RENDERMODE_CONTINUOUSLY.
     * <p>
     * Using RENDERMODE_WHEN_DIRTY can improve battery life and overall system performance
     * by allowing the GPU and CPU to idle when the view does not need to be updated.
     * <p>
     * This method can only be called after {@link #setRenderer(Renderer)}
     *
     * @param renderMode one of the RENDERMODE_X constants
     * @see #RENDERMODE_CONTINUOUSLY
     * @see #RENDERMODE_WHEN_DIRTY
     */
    public void setRenderMode(int renderMode) {
        mGLThread.setRenderMode(renderMode);
    }
注解中提到:系统默认mode==RENDERMODE_CONTINUOUSLY,这样系统会自动重绘;mode==RENDERMODE_WHEN_DIRTY时,只有surfaceCreate的时候会绘制一次,然后就需要通过requestRender()方法主动请求重绘。同时也提到,如果你的界面不需要频繁的刷新最好是设置成RENDERMODE_WHEN_DIRTY,这样可以降低CPU和GPU的活动,可以省电。


2,事件处理

为了处理事件,一般都是继承GLSurfaceView类并重载它的事件方法。但是由于GLSurfaceView是多线程操作,所以需要一些特殊的处理。由于渲染器在独立的渲染线程里,你应该使用Java的跨线程机制跟渲染器通讯。queueEvent(Runnable)方法就是一种相对简单的操作。

class MyGLSurfaceView extends GLSurfaceView {  
    private MyRenderer mMyRenderer;  
  
        public void start() {  
            mMyRenderer = ...;  
            setRenderer(mMyRenderer);  
        }  
  
  
        public boolean onKeyDown(int keyCode, KeyEvent event) {  
  
            if (keyCode == KeyEvent.KEYCODE_DPAD_CENTER) {  
                queueEvent(new Runnable() {  
                    // 这个方法会在渲染线程里被调用  
                         public void run() {  
                             mMyRenderer.handleDpadCenter();  
                         }});  
                     return true;  
                 }  
  
                 return super.onKeyDown(keyCode, event);  
            }  
      }  
}  


调用queueEvent就是给队列中添加runnable

public void queueEvent(Runnable r) {

    synchronized(sGLThreadManager) {
        mEventQueue.add(r);
        sGLThreadManager.notifyAll();
    }

}

在guardenRun()中有如下代码:

<span>		</span>if (! mEventQueue.isEmpty()) {
                    event = mEventQueue.remove(0);
                    break;
                }
                
                ...
                
                if (event != null) {
                    event.run();
                    event = null;
                    continue;
                }
因为每次都会remove掉添加的runnable,所以上面那个demo就是非常好的解释,每次按键就是添加runnable。当然,这也是要求绘制是一直在循环重绘的状态才能看到效果。
(注:如果在UI线程里调用渲染器的方法,很容易收到“call to OpenGL ES API with no current context”的警告,典型的误区就是在键盘或鼠标事件方法里直接调用opengl es的API,因为UI事件和渲染绘制在不同的线程里。更甚者,这种情况下调用glDeleteBuffers这种释放资源的方法,可能引起程序的崩溃,因为UI线程想释放它,渲染线程却要使用它。)

关于GLSurfaceView的渲染过程的重要知识点已经介绍完毕,了解这些对开发当然是很有用的,很多时候你需要实现自定义的类GLSurfaceView的类。

那么现在,最后剩下的就是onDrawFrame(GL10 gl)的主体绘制的实现,这也是最重要的一个部分,因为涉及的内容较多,就不在这里陈述了。这里使用的就是opengl的绘制引擎进行渲染操作,跟之前View的渲染是使用的Skia渲染引擎。

还记得View的绘制onDraw(Canvas canvas)吗,对比onDrawFrame(GL10 gl),我想你该知道区别了。一个使用Skia引擎渲染,一个使用opengl引擎渲染。


问题:

1,GLSurfaceView继承了SurfaceView,它自己的mEglSurface和从父类继承的mSurface之间的关系?

但是呢,

  mEglSurface = view.mEGLWindowSurfaceFactory.createWindowSurface(mEgl,
                        mEglDisplay, mEglConfig, view.getHolder());
mEglSurface在创建的时候,是有view.getHolder作为输入的,我们知道SurfaceHolder是持有Surface的。我一直跟踪到android_opengl_EGL14.cpp和com_google_android_gles_jni_EGLImpl.cpp 发现:surface总是作为一种输入后再加上其他参数,才能返回mEglSurface。我就开始怀疑他们是不是同一个surface,他们是不是指向了同一快内存地址?

为了验证我的这个想法,于是我打印了mSurface和mEglSurface的地址,发现他们却不是同一块地址。这就让人深思了,现在的情况只能说明,他们两个一定有关系,但是又不是指向同一块地址。对这方面有经验的朋友欢迎指导。


作者:aa841538513 发表于2016/8/24 18:56:14 原文链接
阅读:25 评论:0 查看评论

Android初级教程:ViewPage使用详解

$
0
0

转载本博客,请注明出处:http://blog.csdn.net/qq_32059827点击打开链接

ViewPage使用之一就是轮播广告,就以此为出发点,来详细解析一下ViewPage的使用和加载机制。

首先直接上一个damo,在代码中我也直接给出了详细的解释。然后,再在案例后边,对加载机制做一个解释。

Demo:

首先,配置文件:(注意引入全类名)

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

    <android.support.v4.view.ViewPager
        android:id="@+id/viewPage"
        android:layout_width="match_parent"
        android:layout_height="200dip" >
    </android.support.v4.view.ViewPager>

</RelativeLayout>

mainactivity活动代码:

package com.itydl.viewpage;

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

import android.os.Bundle;
import android.app.Activity;
import android.support.v4.view.PagerAdapter;
import android.support.v4.view.ViewPager;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.ImageView.ScaleType;

public class MainActivity extends Activity {
	
	//数据源
	List<ImageView> mImageViewLists = new ArrayList<ImageView>();
	//准备数据,图片资源(一般来自服务器端)
	int[] imagsIds = {
			R.drawable.a,
			R.drawable.b,
			R.drawable.c,
			R.drawable.d,
			R.drawable.e
	};

    private ViewPager mViewPager;

	@Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        initView();//初始化界面
        initDatas();//初始化数据
        
        
    }

	private void initDatas() {
		// 往集合里面添加Imageview对象
		ImageView iv =null;
		for(int i =0;i<imagsIds.length;i++){
			iv = new ImageView(this);
			iv.setImageResource(imagsIds[i]);
			//强制要求图片matchparent
			iv.setScaleType(ScaleType.FIT_XY);
			//这样集合中就存在了5个ImageView对象
			mImageViewLists.add(iv);
		}
	}

	private void initView() {
		setContentView(R.layout.activity_main);
		//获取viewpage实例
		mViewPager = (ViewPager) findViewById(R.id.viewPage);
		
		//PagerAdapter关联ViewPager,数据源间接绑定到ViewPager
		mViewPager.setAdapter(new MyAdapter());
	}

	private class MyAdapter extends PagerAdapter{

		@Override
		public int getCount() {
			// TODO Auto-generated method stub
			return mImageViewLists.size();
		}

		/**
		 * (看得见的view)
		 * 为true的时候,复用View对象显示选中的;false时,可能显示左右加载的(看往那边拖拽)
		 * view 当手指开始滑动时,被拖拽的View对象
		 * obj 当前被选中的item。
		 * 这个方法会决定哪个View加载进来,只有当前选中的view与当前的item相同时才会显示当前的view
		 */
		@Override
		public boolean isViewFromObject(View view, Object object) {
			// TODO Auto-generated method stub
			return view == object;
		}

		/**(看不见的view)
		 * 销毁对应position位置的item(默认加载三张,三张外的之前先被加载的view就会被销毁,传递过来的position等于那个销毁的position)
		 * container 就是当前创建的ViewPager
		 * object: 标记。预销毁的view会传递到这里
		 */
		@Override
		public void destroyItem(ViewGroup container, int position, Object object) {
			//下边这行代码是个坑——不可以保留,一定要注销掉才可以使用,如果不注销掉,抛出异常。
			//super.destroyItem(container, position, object);
			// 移除掉ViewPager中position的item对象
			container.removeView((ImageView) object);
		}

		/**(看不见的view)
		 * 预加载时调用,默认加载三张;当前显示一view,会预加载后边马上要显示的位置的那张view
		 * 加载对应position位置的item
		 * object: 标记。预加载的view会传递给这里
		 */
		@Override
		public Object instantiateItem(ViewGroup container, int position) {
			// 把position位置的对象加载出来,并且添加到ViewPager中
			ImageView iv = mImageViewLists.get(position);
			//Adds a child view. 向ViewPager中添加ImageView对象
			container.addView(iv);
			return iv;//返回显示
		}
		
	}
}

详细的代码解释如上,现在运行看看结果:

看来效果还不错,那么就紧跟脚步,把结论写在了一张图片上,那么就用一张图片来解释一下ViewPage是怎么加载的:


现在对ViewPage用法应该是比较清楚了,您可以自己加上log日志,验证上边图片总结的内容哦。

个人水平总有限,有更好的解释方式还望您的指正。


欢迎关注本博客点击打开链接  http://blog.csdn.net/qq_32059827,每天花上5分钟,阅读一篇有趣的安卓小文哦。


作者:qq_32059827 发表于2016/8/24 19:19:32 原文链接
阅读:36 评论:0 查看评论

android 自带gps定位Location相关知识

$
0
0

android自带gps定位功能相信大家都不会太陌生了,都有所涉及。简单的写了一个示例程序,获取经纬度还有其它相关数据的代码,还有其他相关的知识,比如直接跳转到打开系统gps设置的界面。还有一个bug的处理,异常信息:Incomplete location object, missing timestamp or accuracy

1、获取location 示例程序

package com.example.locationexample;

import android.location.Location;
import android.location.LocationListener;
import android.location.LocationManager;
import android.os.Build;
import android.os.Bundle;
import android.os.SystemClock;
import android.util.Log;
import android.widget.TextView;
import android.annotation.TargetApi;
import android.app.Activity;
import android.content.Context;

public class MainActivity extends Activity {
	LocationManager mLocationManager;
	Location mlocation;
	TextView mTextView;
	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);
		
		mTextView = (TextView)findViewById(R.id.textView1);
		
		getLocation();
		
		
//		mlocation.setElapsedRealtimeNanos(SystemClock.elapsedRealtimeNanos());
//		mLocationManager.setTestProviderLocation(LocationManager.GPS_PROVIDER, mlocation);
	}
	
	public Location getLocation(){
		mLocationManager = (LocationManager)getSystemService(Context.LOCATION_SERVICE);
		mlocation = mLocationManager.getLastKnownLocation(LocationManager.GPS_PROVIDER);
		if (mlocation == null) {
			mlocation = mLocationManager.getLastKnownLocation(LocationManager.NETWORK_PROVIDER);
		}
		mLocationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 100, 0, mLocationListener);
		return mlocation;
	}
	
	LocationListener mLocationListener = new LocationListener() {
		@TargetApi(17)
		@Override
		public void onLocationChanged(Location mlocal) {
			if(mlocal == null) return;
			String strResult = "getAccuracy:" + mlocal.getAccuracy() + "\r\n"
					+ "getAltitude:" + mlocal.getAltitude() + "\r\n"
					+ "getBearing:" + mlocal.getBearing() + "\r\n"
					+ "getElapsedRealtimeNanos:" + String.valueOf(mlocal.getElapsedRealtimeNanos()) + "\r\n"
					+ "getLatitude:" + mlocal.getLatitude() + "\r\n"
					+ "getLongitude:" + mlocal.getLongitude() + "\r\n"
					+ "getProvider:" + mlocal.getProvider()+ "\r\n"
					+ "getSpeed:" + mlocal.getSpeed() + "\r\n"
					+ "getTime:" + mlocal.getTime() + "\r\n";
			Log.i("Show", strResult);
			if (mTextView != null) {
				mTextView.setText(strResult);
				}
		}

		@Override
		public void onProviderDisabled(String arg0) {
		}

		@Override
		public void onProviderEnabled(String arg0) {
		}

		@Override
		public void onStatusChanged(String provider, int event, Bundle extras) {
		}
	};


}

在这里提醒一下,最好activity销毁时候onDestroy(),移除mLocationManager这个变量。

权限如下:

    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
    <uses-permission android:name="android.permission.ACCESS_MOCK_LOCATION"/>
    <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />

还有重点看这行代码参数mLocationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 100, 0, mLocationListener);

LocationManager.GPS_PROVIDER是gps提供,100是时间,多久刷新一次,0是距离,如果你不动的话,测试最好写0

mLocationListener是接口啦,数据就是那里来的。

2、异常

如果你使用mLocationManager.setTestProviderLocation(LocationManager.GPS_PROVIDER, mlocation);这行代码报出下面的堆栈错误信息

 java.lang.IllegalArgumentException: Incomplete location object, missing timestamp or accuracy? Location[gps 23.126704,113.365648 acc=1 et=?!? alt=-7.422 vel=0.008333334 bear=237.76]

解决办法:

方法一:
主要是这句,location.setElapsedRealtimeNanos(SystemClock.elapsedRealtimeNanos());,其实加上这句就可以了。

例如:

    @SuppressLint("NewApi")
    private Location getLoc(String provider)
    {
        Location location = new Location(provider);
        location.setLatitude(lat);
        location.setLongitude(lng);
        location.setAltitude(altitude);
        location.setBearing(bearing);
        location.setSpeed(speed);
        location.setAccuracy(accuracy);
        location.setTime(System.currentTimeMillis());
        location.setElapsedRealtimeNanos(SystemClock.elapsedRealtimeNanos());
        return location;
    }
主要是这句,location.setElapsedRealtimeNanos(SystemClock.elapsedRealtimeNanos());,其实加上这句就可以了。如下

		mlocation.setElapsedRealtimeNanos(SystemClock.elapsedRealtimeNanos());
		mLocationManager.setTestProviderLocation(LocationManager.GPS_PROVIDER, mlocation);

方法二,加上下面的反射调用:
    try
    {
        Method method = Location.class.getMethod("makeComplete");
        if (method != null)
        {
            method.invoke(localLocation);
        }
    }
    catch (NoSuchMethodException e)
    {
        e.printStackTrace();
    }
    catch (Exception e)
    {
        e.printStackTrace();
    }

方法二使用,是借鉴android系统源码的反射作用

源码如下:

   /**
     * Helper to fill incomplete fields.
     *
     * 
Used to assist in backwards compatibility with
     * Location objects received from applications.
     *
     * @see #isComplete
     * @hide
     */
    public void makeComplete() {
        if (mProvider == null) mProvider = "?";
        if (!mHasAccuracy) {
            mHasAccuracy = true;
            mAccuracy = 100.0f;
        }
        if (mTime == 0) mTime = System.currentTimeMillis();
        if (mElapsedRealtimeNanos == 0) mElapsedRealtimeNanos = SystemClock.elapsedRealtimeNanos();
    }

建议使用第一个方法。

3、是否已打开自身GPS

	//获取是否已打开自身GPS
	public boolean isGpsEnable() {
		String providers = Settings.Secure.getString(mContext.getContentResolver(), Settings.Secure.LOCATION_PROVIDERS_ALLOWED);

		if (providers.contains(LocationManager.GPS_PROVIDER)) {
			return true;
		} else {
			return false;
		}
	}

4、直接跳转到系统设置gps界面

	Intent callGPSSettingIntent = new Intent(
		android.provider.Settings.ACTION_LOCATION_SOURCE_SETTINGS);
		this.startActivity(callGPSSettingIntent);


作者:qq_16064871 发表于2016/8/24 19:52:40 原文链接
阅读:19 评论:0 查看评论

Android——线程+最简单计数器

$
0
0

Android——线程+最简单计数器


1.第一种方法

<span style="font-size:18px;">package com.example.jer824;

import android.os.Handler;
import android.os.Message;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.TextView;

public class MainActivity extends AppCompatActivity {
    private TextView tv;
    private TextView tv1;
    int a=100;
    private Handler handler=new Handler(){
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            if (msg.what==1){
                Log.d("??????",a+"");
                tv.setText(a+"");
                a--;
                Log.d("??????","成功");
             new  GetCache(a).run();

            }else{tv.setText("倒计时结束");}
        }
    };
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
     tv=(TextView)findViewById(R.id.tv);
        tv1=(TextView)findViewById(R.id.tv1);
        tv1.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                new  GetCache(a).run();
            }
        });

    }

    private class GetCache implements Runnable {  //两种方法继承或接口
        private int id;

        public GetCache(int id) {

            this.id = id;
        }

        @Override
        public void run() {



            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            Message message = new Message();
            if (id==0) {
                message.what = 2;
                handler.sendMessage(message);
            }else {
                message.what = 1;
                handler.sendMessage(message);
            }
        }
    }
}</span>

<span style="font-size:18px;"><?xml version="1.0" encoding="utf-8"?>
<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"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    tools:context="com.example.jer824.MainActivity">

    <TextView
        android:layout_width="match_parent"
        android:layout_height="300dp"
        android:text="100"
        android:textSize="100sp"
        android:textStyle="bold"
        android:gravity="center"
        android:id="@+id/tv"
        />
    <TextView
        android:layout_width="match_parent"
        android:layout_height="200dp"
        android:text="计时器"
        android:textSize="100sp"
        android:clickable="true"
        android:id="@+id/tv1"
        android:textStyle="bold"
        android:gravity="center"
        android:layout_below="@+id/tv"
        />

</RelativeLayout></span><span style="font-size:24px;">
</span>
2.第二种方法

<span style="font-size:18px;">package com.example.jer824;

import android.os.Bundle;
import android.os.Message;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;

public class Main2Activity extends AppCompatActivity {
    private TextView tv;
    private Button bt;
    //线程间通信
    private android.os.Handler handler=new android.os.Handler(){
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            tv.setText(msg.what+"");

            Bundle bundle=msg.getData();
            String str=bundle.getString("a");
            tv.setText(str+msg.what);

        }
    };
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main2);
        tv= (TextView) findViewById(R.id.tv);
        bt= (Button) findViewById(R.id.bt);
        bt.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                new  Thread(new GetCache()).start();


//                new Thread(new Runnable() {
//                    @Override
//                    public void run() {
//                        int a=100;
//                        while (a>=0){
//                            try {
//                                Thread.sleep(100);
//                            } catch (InterruptedException e) {
//                                e.printStackTrace();
//                            }
//                            a--;
//                            Message message=new Message();
//                            message.what = a;
//                            handler.sendMessage(message);
//                        }
//                    }
//                }).start();
            }
        });
    }
    //多线程 子线程
    //public class GetCache extends Thread
    public class GetCache implements Runnable{
        @Override
        public void run() {
            //子线程运行完了,去主线程
                         int a=100;
                        while (a>=0){
                            try {
                                Thread.sleep(100);
                            } catch (InterruptedException e) {
                                e.printStackTrace();
                            }
                            a--;
                            Message message=new Message();
                            message.what = a;
                            Bundle bundle=new Bundle();
                            bundle.putString("a","AAA");
                            message.setData(bundle);

                         //   handler.sendMessageDelayed(message,200);

                          handler.sendMessage(message);
                         //与上面三行一个意思
                          //  handler.sendEmptyMessage(a);


                        }
        }
    }

}
</span>
<span style="font-size:18px;"><?xml version="1.0" encoding="utf-8"?>
<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="com.example.jer824.Main2Activity">
    <TextView
        android:id="@+id/tv"
        android:layout_width="match_parent"
        android:layout_height="100dp"
        android:text="100"
        android:textSize="40sp"
        android:textStyle="bold"
        android:gravity="center"
        />
    <Button
        android:layout_width="match_parent"
        android:layout_height="50dp"
        android:layout_below="@+id/tv"
        android:text="开始计时"
        android:id="@+id/bt"
        />

</RelativeLayout></span><span style="font-size:24px;">
</span>

3.第三种方法
<span style="font-size:18px;">package com.example.jer824;

import android.os.Bundle;
import android.os.Message;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;

public class Main3Activity extends AppCompatActivity {
    private TextView tv;
    private Button bt;
    private int count=100;
    //线程间通信
    private android.os.Handler handler=new android.os.Handler(){
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            //tv.setText(msg.what);
            if(count==0){
                return;
            }
            tv.setText(msg.what + "");
            handler.sendEmptyMessageDelayed(count--,500);

            //tv.setText(msg.what + "");
            //bundle传值
//            Bundle bundle=msg.getData();
//            String str = bundle.getString("a");
//            tv.setText(str+msg.what);


        }
    };
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main2);
        tv= (TextView) findViewById(R.id.tv);
        bt= (Button) findViewById(R.id.bt);
        bt.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                handler.sendEmptyMessage(count);
//
//            new Thread(new Runnable() {
//                    @Override
//                    public void run() {
//                        int a=100;
//                        while (a>0){
//                            try {
//                                Thread.sleep(100);
//                            } catch (InterruptedException e) {
//                                e.printStackTrace();
//                            }
//                            a--;
//                           // tv.setText(a+"");
//                            Message message=new Message();
//                            message.what = a;
//                            handler.sendMessage(message);
//                        }
//                    }
//                }).start();
                //new Thread(new GetCache()).start();
            }
        });
    }
    //多线程 子线程
    //public class GetCache extends Thread
    public class GetCache implements Runnable{
        @Override
        public void run() {
            //子线程运行完了,去主线程
            int a=100;
            while (a>0){
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                a--;
                // tv.setText(a+"");
                Message message=new Message();
                message.what = a;
                //加入bundle值
//                Bundle bundle=new Bundle();
//                bundle.putString("a","1");
//                message.setData(bundle);
                handler.sendMessage(message);
                //多久后启动
                //handler.sendMessageAtTime(message,500);
                //handler.sendMessageDelayed(message,500);

                //handler.sendEmptyMessage(a);传空值
            }

        }
    }

}
</span>
<span style="font-size:18px;"><?xml version="1.0" encoding="utf-8"?>
<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="com.example.jer824.Main2Activity">
    <TextView
        android:id="@+id/tv"
        android:layout_width="match_parent"
        android:layout_height="100dp"
        android:text="100"
        android:textSize="40sp"
        android:textStyle="bold"
        android:gravity="center"
        />
    <Button
        android:layout_width="match_parent"
        android:layout_height="50dp"
        android:layout_below="@+id/tv"
        android:text="开始计时"
        android:id="@+id/bt"
        />

</RelativeLayout>
</span>









作者:zhangyufeng0126 发表于2016/8/24 19:57:03 原文链接
阅读:16 评论:0 查看评论

[React Native混合开发]React Native for iOS之代码结构

$
0
0

一、了解index.ios.js

大家都清楚,React-Native就是在开发效率和用户体验间做的一种权衡。React-native是使用JS开发,开发效率高、发布能力强,不仅拥有hybrid的开发效率,同时拥有native app相媲美的用户体验。目前天猫也在这块开始试水。
用编辑器打开index.ios.js文件,分析代码结构:
1、第一句:var React = require('react-native');有Node.js开发经验的同学都清楚,require可以引入其他模块。如果没有node.js开发经验的同学,可以脑补下java的import和c++的#include。
2、第二句代码,批量定义组件:
var {
    AppRegistry,
    StyleSheet,
    Text,
    View,
} = React;
其实,这只是一个语法糖而已,比如AppRegistry我们可以这样定义:var AppRegistry = React.AppRegistry;
3、构建Heollo World入口类。React提供了React.createClass的方法创建一个类。里面的render方法就是渲染视图用的。return返回的是视图的模板代码。其实这是JSX的模板语法,可以提前学习下。
4、相对于web开发,我们需要提供视图的样式,那么StyleSheet.create就是干这件事的,只是用JS的自面量表达了css样式。
5、如何引入css样式?其实在render方法返回的视图模板里已经体现出来了,即style={styles.container}.其中style是视图的一个属性,styles是我们定义的样式表,container是样式表中的一个样式。
6、注册应用入口,这个一定不能少,否则模拟器会提示报错:
    AppRegistry.registerComponent('HelloWorld', () => HelloWorld);

二、其实你还需要看点这方面的知识

对于React-Native开发,仅仅有基础前端开发的知识是不够的,你还需要了解和掌握的有:

* Node.js基础
* JSX语法基础
* Flexbox布局

三、目前需要关注的文件

1、目前阶段有几个文件时需要注意下的:
(1)在xcode项目代码中AppDelegate.m会标识入口文件,例如:
jsCodeLocation = [NSURL URLWithString:@"http://localhost:8081/index.ios.bundle"];
如果是网上下载别人的源码,注意此处的ip和端口是否有被修改。
(2)闪屏界面在哪修改?在xcode项目中找到LaunchScreen.xib文件,点击,你会看到界面,这个就是启动界面,你手动添加组件或者修改文本即可,最好了解下xcode的使用。
(3)文本编辑器打开index.ios.js文件,是js代码的入口文件,所有的代码编写从这开始,可以定义自己的模块和引入第三方模块。

四、修改文件index.ios.js

1、修改启动界面,如下图

这里写图片描述

2、添加图片和修改样式.我们在第一篇的demo基础上修改。去掉第二个和第三个<Text>,增加我们需要的图片,因为图片更具表达力,就像最近的图片社交应用很火一样。
(1)添加Image组件,将代码修改成如下即可:
var {
    StyleSheet,
    Text,
    View,
    Image,
} = React;
(2)将render返回中的模版增加Image组件视图,具体如下:
render: function() {
    return (
        <View style={styles.container}>
            <Text style={styles.welcome}>
                React-Native入门学习
            </Text>
            <Image style={styles.pic} source={{uri: 'https://avatars3.githubusercontent.com/u/6133685?v=3&s=460'}}>
            </Image>
        </View>
    );
}
其中,Image标签的source的第一个大括号是模板,第二个大括号是js对象,js对象里面有个key是uri,表示图片的地址。
(3)修改图片视图的样式,删除多余的样式,增加pic样式:
var styles = StyleSheet.create({
    container: {
        flex: 1,
        justifyContent: 'center',
        alignItems: 'center',
        backgroundColor: '#F5FCFF',
    },
    welcome: {
        fontSize: 20,
        textAlign: 'center',
        margin: 10,
        color: 'red',
    },
    pic: {
        width:100,
        height:100,
    }
});
(4)可以cmd + Q 停止模拟器,然后再cmd + R开启模拟器,你会发现启动界面和首页都你想要的样子:

这里写图片描述

如果终端被关闭了怎么办

 不用担心,其实只要你切到项目的根目录,命令行输入npm start即可,这样即可开发终端监听。实际上也是node.js的监听服务开启而已。如下图表示成功。

这里写图片描述

作者:BaiHuaXiu123 发表于2016/8/25 0:01:31 原文链接
阅读:285 评论:0 查看评论
Viewing all 5930 articles
Browse latest View live


<script src="https://jsc.adskeeper.com/r/s/rssing.com.1596347.js" async> </script>