单例模式的双重检测

单例模式是设计模式中比较常见简单的一种,典型双重检测写法如下:


 

接下来对该写法进行分析,为何这样写?

一、为何要同步:

多线程情况下,若是A线程调用getInstance,发现instance为null,那么它会开始创建实例,如果此时CPU发生时间片切换,线程B开始执行,调用getInstance,发现instance也null(因为A并没有创建对象),然后B创建对象,然后切换到A,A因为已经检测过了,不会再检测了,A也会去创建对象,两个对象,单例失败。因此要同步。

 

二、同步为何不用 public synchronized static SingletonClass getInstance(),也就是说为何不同步这个方法,而要同步下面的语句:

因为synchronized修饰的同步块可是要比一般的代码段慢上几倍,如果经常调用getInstance,那么性能问题就得考虑了。

 

三、最外层为何要有if (instance == null)判断:

因为我们在分析二中,发现依旧存在着性能问题,也就是说,只要getInstance方法被调用,那么就会执行同步这个操作,于是我们加个判断,当instance没有被实例化的时候,也就是需要去实例化的时候才去同步。

 

四、instance为何要有volatile 修饰:

这个问题就涉及到了编译原理,所谓编译,就是把源代码“翻译”成目标代码——大多数是指机器代码——的过程。针对Java,它的目标代码不是本地机器代码,而是虚拟机代码。编译原理里面有一个很重要的内容是编译器优化。所谓编译器优化是指,在不改变原来语义的情况下,通过调整语句顺序,来让程序运行的更快。这个过程成为reorder。

JVM实现可以自由的进行编译器优化。而我们创建变量的步骤:

1、申请一块内存,调用构造方法进行初始化。

2、分配一个指针指向这块内存。

而这两个操作,JVM并没有规定谁在前谁在后,那么就存在这种情况:线程A开始创建SingletonClass的实例,此时线程B调用了getInstance()方法,首先判断instance是否为null。按照我们上面所说的内存模型,A已经把instance指向了那块内存,只是还没有调用构造方法,因此B检测到instance不为null,于是直接把instance返回了——问题出现了,尽管instance不为null,但它并没有构造完成,就像一套房子已经给了你钥匙,但你并不能住进去,因为里面还没有收拾。此时,如果B在A将instance构造完成之前就是用了这个实例,程序就会出现错误了。

在JDK 5之后,Java使用了新的内存模型。volatile关键字有了明确的语义——在JDK1.5之前,volatile是个关键字,但是并没有明确的规定其用途——被volatile修饰的写变量不能和之前的读写代码调整,读变量不能和之后的读写代码调整!因此,只要我们简单的把instance加上volatile关键字就可以了。

 

转载请标明出处http://77blogs.com/?p=488

参考链接http://blog.51cto.com/devbean/203501

LayoutInflater.inflate()方法两个参数和三个参数

转载请标明出处:http://77blogs.com/?p=489

很多人都用过LayoutInflater(布局填充器)

对于我来说通常使用下面两种:
LayoutInflater.from(context).inflate(R.layout.recycle_foot_item,null);
LayoutInflater.from(context).inflate(R.layout.recycle_foot_item,parent,false);

那么两个参数与三个参数究竟有什么区别呢?

我们进去源码看一下两个参数时的代码:

可以看出来使用两个参数时,它的内部也是调用了3个参数的方法。

如果我们使用LayoutInflater.from(context).inflate(R.layout.recycle_foot_item,null);

则实际上是调用了LayoutInflater.from(context).inflate(R.layout.recycle_foot_item,null,null!=null);

等同于:LayoutInflater.from(context).inflate(R.layout.recycle_foot_item,null,false);

 

如果我们使用LayoutInflater.from(context).inflate(R.layout.recycle_foot_item,parent);

则实际上是调用了LayoutInflater.from(context).inflate(R.layout.recycle_foot_item,parent,parent!=null);

等同于:LayoutInflater.from(context).inflate(R.layout.recycle_foot_item,parent,true);

 

我们再来看看三个参数的方法的源码:


 

在里面调用了
inflate(parser, root, attachToRoot);方法,在源码中可以看到,inflate(parser, root, attachToRoot);方法中有下面代码:


 

从上面可以看出:
如果第二个和第三个参数均不为空的话,即root不为null,而attachToRoot为true的话那么我们执行完LayoutInflater.from(context).inflate(R.layout.recycle_foot_item,parent,true);之后,R.layout.recycle_foot_item就已经被添加进去parent中了,我们不能再次调用parent.add(View view)这个方法,否则会抛出异常。

 

那么root不为null,attachToRoot为false是代表什么呢?我们可以肯定的说attachToRoot为false,那么我们不将第一个参数的view添加到root中,那么root有什么作用?其实root决定了我们的设置给第一个参数view的布局的根节点的layout_width和layout_height属性是否有效。我们在开发的过程中给控件所指定的layout_width和layout_height到底是什么意思?该属性的表示一个控件在容器中的大小,就是说这个控件必须在容器中,这个属性才有意义,否则无意义。所以如果我们不给第一个参数的view指定一个父布局,那么该view的根节点的宽高属性就失效了。

如果我想让第一个参数view的根节点有效,又不想让其处于某一个容器中,那我就可以设置root不为null,而attachToRoot为false。这样,指定root的目的也就很明确了,即root会协助第一个参数view的根节点生成布局参数,只有这一个作用。

但是这个时候我们要手动地把view添加进来。

 

TextView图文混排

转载请标明出处:http://77blogs.com/?p=491

大家都知道,textView有一个setCompoundDrawables的方法来设置上下左右位置的图标,当然,也可以在xml布局文件中设置,然而问题来了,假如我们把图标放在左边,当我们让TextView分多行显示的时候,会出现一种情况,左边的图标并不会与第一行对齐,而是与整个textView居中对齐。

即我们要的是下图:

结果是这个图:

怎么办呢?我们可以用图文混排:

我们可以利用SpannableString和ImageSpan。

1、构建SpannableString对象。

2、获取Drawable对象,即将我们的图案转换为Drawable对象,并设置大小。

3、构建ImageSpan 对象

 4、设置给上面的SpannableString对象

5、最终设置给TextView

 

加下来讲讲上面的方法:

1、ImageSpan对象,第二个参数为图像与文字的对齐方式,ImageSpan只带有两个对齐方式,分别是:ALIGN_BASELINE、ALIGN_BOTTOM。

ALIGN_BOTTOM 表示与文字内容的底部对齐,如果在构造ImageSpan时没有传入对齐方式,那么默认就是这种底部对齐。

ALIGN_BASELINE, 表示与文字内容的基线对齐


 

2、setSpan()方法


 

what传入各种Span类型的实例; 
startend标记要替代的文字内容的范围; 
flags是用来标识在Span范围内的文本前后输入新的字符时是否把它们也应用这个效果,它有如下几个:

Spanned.SPAN_EXCLUSIVE_EXCLUSIVE、

Spanned.SPAN_INCLUSIVE_EXCLUSIVE、

Spanned.SPAN_EXCLUSIVE_INCLUSIVE、

Spanned.SPAN_INCLUSIVE_INCLUSIVE

INCLUSIVE表示应用该效果,EXCLUSIVE表示不应用该效果,如Spanned.SPAN_INCLUSIVE_EXCLUSIVE表示对前面的文字应用该效果,而对后面的文字不应用该效果。

 

坑:

1、既然ImageSpan只带有两个对齐方式,那我们需要自己实现居中对齐:

 

 为何上面的自定义能够实现居中对齐呢?首先要了解Paint.FontMetrics。

请看另一篇博客:https://www.cnblogs.com/tangZH/p/8692910.html

 

android找不到aar包

转载请标明出处,维权必究:http://77blogs.com/?p=494

 

在做项目的时候引入aar包,编译的时候却提示错误(这个错误大概说的是…….模块B>模块C……有点忘了),其实大概可以看出是由于多个模块重复依赖造成的,下面具体讲讲:
主项目A引入模块B和C,模块B中也引入了模块C,而模块C中引入了aar包

(aar包在libs文件中,具体怎么引入请看我另一篇博客:https://www.cnblogs.com/tangZH/p/9939494.html

 

那么这时候就要注意了:

1、我们需要在模块B中的build.gradle文件中的android标签下写上下面代码:

 

假如模块C与模块B处于同一个目录下,那么模块C的libs文件的路径应该这样写../C/libs(关于相对路径这一块,可以看我另一篇博客https://www.cnblogs.com/tangZH/p/9939655.html 

如果有其它模块的libs路径需要添加,那么就在后边用逗号隔开

 

2、由于A也引用了模块C,所以需要做相同的处理,因为编译的时候会去找aar包。

 

在实际过程中发现编译已经没有问题了,但是A中无法调用aar包中的代码,最终发现:

将C的build.gradle中的implementation (name: ‘aar包名称’, ext: ‘aar’)  改为:api (name: ‘aar包名称’, ext: ‘aar’)就可以了,因为implementation指的是本Module,但是api指的是整个项目,依赖的范围不同

 

在这个过程中需要格外注意的是相对路径不要写错,否则会报找不到arr包的错误,如果还不行就clean一下工程

android中的相对路径

转载请标明出处:http://77blogs.com/?p=495

 

1、同个文件夹访问

D:\Java\main\A.java

D:\Java\main\B.java

A访问B的相对路径为B.java

 

2、目标文件在其子目录

D:\Java\A.java

D:\Java\main\B.java

A访问B的相对路径为main\B.java

注意的是在Android代码中必须这样写main/B.java,上面之所以那样写只是为了说明它在window下的路径,Java程序中路径分隔符是’/’或者’\\’,因为Java程序中’\’表示转义的意思

 

3、目标文件在其上一级目录

D:\Java\main\A.java

D:\Java\B.java

A访问B的相对路径为..\B.java,同理类推,上两级目录下那就是../../B.java

 

android引用arr包

转载请标明出处,维权必究:http://77blogs.com/?p=496

android中引用的包一般分为两种:

1、jar包

2、aar包

arr包其实带有res的jar包,而普通的jar包是不带资源文件的。那么如何在项目中引用呢?

 

1、将aar包复制到libs目录下

2、在build.gradle文件中添加一个本地仓库,并把libs目录作为仓库的地址。

3、在build.gradle文件中dependencies标签中添加下面的依赖。

 

若是整个项目都要用到aar包

则用api (name:’你的aar名字’, ext:’aar’)

WebView断网提示

转载请标明出处,维权必究http://77blogs.com/?p=497

 

重写WebViewClient中的方法,然后WebView.setWebViewClient(mWebViewClient);

 

在onReceivedError中便可以进行网络出错时候的提示与处理,若是走到这里说明网络出错,或者服务器出错。可以在该方法中进行判断是不是没有网络。6.0以上与6.0以下的都可以用该方法进行监听回调。

实践证明

1、在该方法被调用后,onPageFinished()方法也会被回调,所以最好不要在该方法中进行页面重置操作,比如断网时候弹出断网页面,但是若在onPageFinished()里面去显示正常页面,那么断网提示就消失了。

2、onPageFinished()会调用多次)

 

不过在6.0以上的时候又增加了一个新的方法:

 

实践证明:成功加载出网页的时候也会回调该方法,或者出现其他非网络错误的时候也会,所以我们需要通过error来判断是什么错误,进行过滤处理。