Android面试笔记(有福利)

96
LogosTravel
0.2 2016.11.22 21:18* 字数 21656
面试

第一次修改和更新

内容:

1.修正文章的排版问题

2.更新一下在电子市场中这个项目经验

第二次更新说明

内容

1.最近手上有项目需要开发,本人也有前端的内容需要学习和专研,可能就忽略更新,此处给Android开发的人员带来一点点福利,链接可能会失效,有需要的童鞋留言就可以了,就是30多个GB的Android实例代码,刚刚入门参加工作的童鞋可以拿去用下:

http://cloud.189.cn/t/3YRZbq6Z3Ur2(访问码:6163)

2.关于我这次要更新的内容可能会加入一点面试上的经验之谈,当然也会出现我自己面试别人的题目,IT人才的市场现在是越来越混乱了,面试一个IT人员的成本也随之加大,希望对有幸面试别人的童鞋也会有一定的帮助。

缓存机制

向服务器请求数据,服务器返回数据(通常的做法)

向服务器请求一下数据的最新更新时间,这个时间和本地缓存更新的时间进行比较决定是否真正访问网络获取数据

只要本地缓存的时间是最新的数据我们就不用去请求网络更新我们的数据了

否则当然要更新

此处检查更新时间只用极少的流量

三级缓存

内存缓存(第一级缓存,优先去内存中获取)

速度非常快,而且不占用我们的网络资源

内存缓存其实就是在我们的代码创建一个集合,这个集合自然会被加载到我们的内存之中,我们可创建一个Map集合,我们创建一个HashMap集合,使用我们的键值对去创建对象,当然还是两个方法:写缓存以及读缓存,写缓存就是使用HASHMAP的put方法,读取这个缓存就是一个通过KEY值参数获取我们的缓存对象.

写入内存缓存的时机是在本地缓存加载好之后,当然当然网络缓存获取之后我们也可以写入内存缓存,这样我们读取内存缓存速度会更加的块,当然弊端是会占用我们的内存资源

读内存缓存的时机当然还是在我们要加载图片资源时调用,优先于我们的本地缓存以网络缓存

内存溢出(OOM)

假设我们不断在map增加对象,我们的内存资源就会不断地被占用,内存空间或者说资源时有限的,特定情况下内存会不够用,就会造成我们所说的内存溢出

java中我们分为栈堆(堆里面对象太多容易造成内存溢出),Davik默认给每一个application分配了16MB内存空间,现在可能会分配32MB,64MB,有的root定制机甚至有128MB,获取我们最大的内存方法(RunTime.getRunTime.maxMemory去获取),这个和我们手机本身的配置无关,主要和虚拟机的机制有关系,垃圾回收器(garbage collector)只会回收不存在引用的对象,不要高估gc的能力.

避免内存溢出的方法(软引用),在android有强引用,弱引用,软引用以及虚引用,软件引用的特点是当内存不足时,我们的gc回去回收软引用对象,默认状态我们的引用全部是强引用的状态,gc不能直接去回收强引用的对象,弱引用被回收的级别更高(其实就是一个被回收级别的等级),虚引用最容易被回收(太容易被回收,所以在实际场景中应用不多),我们将集合中的bitmap对象改为一种软引用对象(SoftReference)或者称之为包装我们的bitmap对象,软引用对象soft使用get方法就能获取到我们的bitmap对象,前提是判断一下soft对象是否为null(原因是软引用对象容易被回收)

软引用在高版本中不会有任何的作用(基本上马上就被回收了,从2.3版本,也就是API9之后垃圾回收器更倾向于回收软引用和弱引用),在android中谷歌给我们提供了API让我们处理图片的内存缓存,这个类就是LruCache,new出这个类对象有一个参数两个泛型,两个泛型就是键值对,参数是最大的内存上限,通常我们将其设置为最大内存的八分之一,还需要重写一个方法(sizeOf),这个方法会返回每一个对象的大小,这个对象的大小我们给定,默认值为一个1个字节,我们需要调用图片自身计算大小的方法,之后将这个值返回.用法跟我们的hashmap时一样的,就是初始化时需要设置一下.假设LruCache回收的原则而是回收最近最对象,* 本地缓存(通常是从SD卡中去获取)

我们可以将图片保存在我们的本地磁盘之中,我们可以使用url去命名我们的这个本地磁盘文件,让其成为我们的图片的唯一标识,通常在android中我们通常将其保存在我们的SD卡中,我们需要判断一下这个文件夹是否存在(也就是判断一下这个路径下的file对象是否存在),确保安全的情况下我们还需要判断一下这个是不是一个文件夹(isDirectory),我们可以对url进行MD5对其进行转换,转换出来一个MD5的值,bitmap对象本身有一个压缩保存的方法(bitmap.compress),参数:第一个参数是图片格式,CompressFormat.图片类型,第二个参数为图片的质量(1-100,数值越大质量越高),最后一个参数是一个输出流,这样图片文件就能够被保存在我们的SD卡之中了,之后我们需要创建一个获取这张图片的方法,传入一个url去获取就可以了,细节就是这个url也要被MD5编译成为一个MD5的值,这个值就是我们的文件名,判断一下文件是否存在,就能够判断是否存在着本地缓存读缓存,是在我们要从网络下载图片时判断一下是否已经存在本地缓存了,我们要写入缓存是在我们成功从网络上下载了图片之后我们就可以执行我们的写入缓存的操作

网络缓存(最后我们会从网络上去加载缓存资源)

速度较慢,而且还会浪费我们的网络资源,优先使用内存缓存和本地缓存

主要还是通过一个异步线程池去下载我们的网络资源,当然在此处我们需要注意到一个事情,这个事情就是我们的两个参数必须具有一致性,可以使用view的settag方法来标记我们的view对应的url

从开发的角度我们是要先开发我们的网络缓存

在实际工作中我们还是会使用工具类(当然目前我们使用的是xutils),整体的结构和思路一样

学习这个三级缓存只要是为了能够说出android三级缓存的图片的一个框架和思路

我们学习这个框架主要是为了面试的使用

面试官的问题:平常在工作中是否有碰到过内存溢出的问题,跟我讲一下

实际工作中大多数情况是图片加载的内存溢出问题,之后我们可以跟面试官谈一下三级缓存

技巧性的回答:开发中不会经常碰到OOM的状况,在大图加载时会碰到这个状况,也上网查了资源,发现图片加载分为三级缓存,前面的网络缓存和本地缓存我完全理解了,我使用内存缓存之后,发现内存还是溢出,我发现了一个问题,这个内存缓存机制会不断在内存中添加对象(主要是我们在hashmap不断添加图片对象),可是我觉着特别的不可思议,不是说java本身又一个垃圾回收的机制(gc机制,garbage collector),后我有去查了一下资料以及问了一下同事,原来java中存在着不同级别的引用,垃圾回收器不会回收我们的强引用对象,这个我是了解的,gc通常会回收空引用的对象,我发现还存软引用,弱引用以及虚引用,虚引用大家通常不用,虚引用太容易被gc回收,网上大部分的人使用软引用,通常是认为软引用是在内存不足时才会被回收,我也试了一下,发现内存缓存根本毫无作用,从根本上来讲还是这个软引用被回收了,我又去查了一下资料,也查了一下android的官方文档,发现在android2.3版本之后android会优先回收或者尽量回收软引用以弱引用对象,这样的话这种内存缓存机制就有失效了,当然在文档我也发现谷歌推荐我们一个类:LruCache,这个类能够把最近使用最少的对象从内存中移除掉,就马上根据官方文档去使用了一下这个类,这个类的使用需要主要的是需要重写sizeOf方法,返回value值的实际大小,如果不设置的话默认返回的是1一个字节,最后我也去查了一下这个类的源码,发现里面就是封装了一个LinkedHashMap对象,我觉着我也能写出来,后来去网上有查了一下发现gitHub上有一个xutils也是用这种原理加载图片的,就直接拿过来用了.

屏幕适配(面试经常会问)

图片适配

ldpi(240320),mdpi(320480),hdpi(480*800),xhdpi(1280720),xxhdpi(19201080):dpi是像素密度,和我们屏幕尺寸无关,在不同的drawable文件中(使用了分辨率的限制符)使用相同的名字就会自动匹配不同像素密度的手机,假设找不到就找相近dpi的图片,倾向于先去找分辨率高的图片

实际开发中,美工做一套图就可以了,基于主流屏幕去做图,主流就是xhdpi(1280*720)

布局适配

定义:我们可以在不同的屏幕分辨率中加载不同的布局,可以用分辨率(比方说800x480,高度写在前面,宽度写在后面)对layout文件进行限定,里面也可以放置相同名字的布局文件,也可以使用dpi进行设定,在实际开发中我们是不使用的,简单的布局我们可能会使用

尺寸适配(dimen,极为常用,需要记住常用的dpi和分辨率)

获取屏幕dpi,getResource.getDisplayMetrics().density

240*320 0.75(l)

320*480 1.0(m)

480*800 1.5(h)

1280*720 2(xh)

公式:dp = px/dpi,px = dpi*dp

values里面有一个dimens.xml文件,在xml文件中引用直接@dimen/dimen资源,我们可以重新一个values-分辨率,重新在dimens.xml中创建一个名称相同名称的dimen值就可以了,这样就可以做到尺寸适配了

权重适配

使用xml文件中的weight属性进行适配,还有weightSum设置总权重

权重属性必须要用线性布局,能用尽量去使用

代码适配

通过代码获取屏幕的宽高,之后按比例给我们的控件设置宽高参数

屏幕适配的总结:

面试中:你们平时如何进行屏幕适配?

一开始开发时我们是基于主流屏幕进行开发,现在主流屏幕为1280*720以及480*800,

开发到了后期我们才开始考虑做屏幕适配,保证我们在我们的主流屏幕上完美展示,

其它屏幕上能够正常显示就可以了,之后我们的项目就可以上线,养成好的开发习惯

就可以避免屏幕适配问题,局部当中多使用线性和相对布局,多用dp,少用px代码中使用px尽量转换一下.

图片加载的OOM(内存溢出)补充

图片显示

我们需要根据需求去加载我们的图片

比方说在列表中我们可以显示图片的缩略图

只有当用户浏览图片时采取加载整个图片

图片像素

ALPHA_8:一个字节/px

ARGB_4444:两个字节/px

ARGB_8888:4个字节/px(默认)

RGB_565:2个字节/px

android默认为:ARGB_8888

图片的缩放

我们可以使用BitmapFactory.Options设置SamleSize,设置一个缩放值x,表示为原图宽高的1/x

实例代码主要是使用我们BitmapFactory.Options对象,里面有一个SamleSize成员,设置这个成员的值(这个值主要是用图片的高度/imageview的高度去获取,当然也可以使用宽高比),在BitmapFactory解码图片时将Options对象作为选项参数传进去就可以了获取一张缩放之后的缩略图了.

补充一下:也可以设置image的scale属性达到图片缩放的目的

图片的回收

在我们的图片被使用之后我们要及时将内存之中的图片进行回收

//判断是否为空,而且已经回收

if(bitmap != null && !bitmap.isRecycled()){

//回收并且置为空

bitmap.recycle();

bitmap = null;

}

//调用一下垃圾回收器

System.gc();

捕获异常(经过上面的步骤还可能造成图片加载上的OOM异常)

捕获OOM异常,OutOfMemoryError,使用try catch,这样我们的application不会马上挂掉(这样用户体验太差),假设实例化我们的图片不成功,我们可以返回一张默认的图片给我们的用户.

//声明一个bitmap的引用

Bitmap bitmap = null;

try {

//实例化我们的bitmap对象.进行异常捕捉

bitmap = BitmapFactory.decodeFile(path);

}catch(OutOfMemoryError e){

//避免直接崩溃

}

if{bitmap == null

//如果实例创建失败了,就返回一张默认的图片

return defaultBitmap;

}

使用LruCache进行内存管理

3.0之后默认会存储到默认内存之中,这样就无法避免掉我们的OOM异常

通常我们给其设置的最大值,maxSize为可用内存的八分之一,可用内存的获取:Runtime.getRuntime.maxMemory

会把最近的不经常使用的对象优先删除

代替了我们之前所使用软引用和弱引用

BitmapRegionDecoder

支持的格式优先,可以按照指定宽高获取到我们的图片

流程:创建这个对象,传入我们图片资源,使用decodeRegion(rect,options),就是获取图片特定区域的值

可以用来浏览大图,比方说世界地图,github上有一个资源:https://github.com/johnnylambada/WorldMap

使用常用的第三方框架来加载我们的图片(加载图片常用的第三方框架)

Universal-Image-Loader

XUtils-bitmapUtils

Picasso

Fresco(facebook改造的)

Volley

关于内存泄露的问题(答题)

从概念上来讲内存泄露就是向内存申请了一部分内存的使用,使用完了未完全释放这部分内存,比方说一个主程序中子线程在主程序结束之后还在运行,这就是内存泄露,现在手机的内存已经非常大了,可是系统不可能将所有的内存分配给我们的应用程序去使用,每个程序可使用的内存在android中存在一个上限,我们通常称之为堆内存大小(heap size),不同的手机,堆内存大小不相同(有的可能被定制过),现在的手机通常可以分配32MB的内存给我们的一个应用程序使用,如果我们想要获取一个应用程序被分配的堆内存大小(单位是MB):

ActivityManager manager = (ActivityManager)

getSystemService(Context.ACTIVITY_SERVICE);

int heapSize = manager.getMemoryClass();

我们在开发应用程序的内存不能超出这个限制,否则就是造成OOM错误.

我们可以利用一段经典代码去测试一下我们的内存泄漏,代码如下:

public class MainActivity extends AppCompatActivity {

@Override

protected void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

setContentView(R.layout.activity_main);

//不停的横竖屏切换可能就会造成内存泄漏

new InnerThread().start();

}

/**

* 这是一个测试用的线程类

*

*/

class InnerThread extends Thread{

@Override

public void run() {

while(true){

System.out.println("我只是在证明我还存在");

}

}

}

}

上面的代码中不断进行横竖屏切换,我们的activity就会不断的重建(我未配置activity的screentype),由于子线程是一个死循环,所以还会占有原来的acticity引用,所以原来的activity的引用还会被占用,而且这个对象不会被gc回收(非回收对象,是一个强引用对象),我们不断切换,就会不断创建对象,而且不会被回收,最后会导致OOM异常,从而造成内存泄漏.

使用我们的MAT工具进行测试

首先我们必须确定一下系统是否系统是否无法回收一部分被引用完毕之后该释放的对象,而且这种对象会随着我们使用或者说申请不断地被创建,我们可以测验我们的功能,我们可以使用不停调用我们的gc去回收垃圾,假设始终无法回收,就说明这个功能可能会造成内存泄露,我们可以多次去点击,观察这个应用程序所占用的内存变化,假设一直在变大我们就可以使用Dump java heap功能导出一个文件详情,经过格式转化之后就能用MAT打开,打开之后我们就去分析每一个对象所占用的内存情况了,假设GC ROOT可以访问的对象左下角会有一个小点,假设不是不是系统类,就与可能是无法被回收的自定义对象,就有可能是这个对象的创建造成了内存泄漏.

也要补充一下:也可以导入一个第三方的类库:LeakCanary,使用方特别的简单(下面我CV一下)

如何使用我们的leakcanary

在build.gradle中加入引用,不同的编译使用不同的引用:

dependencies {

debugCompile 'com.squareup.leakcanary:leakcanary-android:1.3'

releaseCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.3'

}

在Application中:

publicclassExampleApplicationextendsApplication{@OverridepublicvoidonCreate(){super.onCreate();LeakCanary.install(this);}}

关于Adapter的抽取(这个可以根据自己的情况发挥一下,万能的Adapter)

此处我直接上我所抽取出来的代码,其实还是将初始化view和bindData的方法抽取为抽象方法让用户去自定义,其它由我们的基类去完成这样一个思路,在之后的工作中我们可能还会经常进行抽取,下面就直接上代码了

import android.content.Context;

import android.view.View;

import android.view.ViewGroup;

import android.widget.BaseAdapter;

import java.util.List;

/**

* 一个万能的adapter,抽取之后形成

*/

public abstract class MyBaseAdapter extends BaseAdapter {

/**

* 需要初始化一个上下文和我们的数据

*/

protected Context context;

protected List infos;

protected T info;

/**

* 在构造方法中初始化我们的数据和上下文

*

* @param infos

* @param context

*/

public MyBaseAdapter(List infos, Context context) {

this.infos = infos;

this.context = context;

}

/**

* 返回条目的个数

*

* @return

*/

@Override

public int getCount() {

return infos.size();

}

@Override

public T getItem(int position) {

return infos.get(position);

}

@Override

public long getItemId(int position) {

return position;

}

@Override

public View getView(int position, View convertView, ViewGroup parent) {

ViewHolder holder = null;

if (convertView == null) {

holder = getViewHolder();

} else {

holder = (ViewHolder) convertView.getTag();

}//初始化数据

holder.bindData(infos.get(position));

return holder.getView();

}

public abstract ViewHolder getViewHolder();

/**

* 一个内部类,初始化view对象以及绑定data

*/

protected abstract class ViewHolder {

//这个是初始化view对象

protected View view;

public ViewHolder() {

view = initView();

view.setTag(this);

}

/**

* 初始化我们的view对象*

* @return

*/

public abstract View initView();

/**

* 绑定我们的对象

*/

public abstract void bindData(T info);

public View getView() {

return view;

}

}

}

新闻客户端的框架-面试必备

整个项目使用了四个activity去做(当然我们只是完成了去新闻中心的构建)

SplashActivity(做过应用的人一定了解这个界面的功能:主要是推广,对app的推广,投放广告之类的)

GuideActivity(第一次使用app时的一个引导界面,可以展示app的新功能,基本操作流程,也可以引导用户进行基础性的设置,比方说选择app的主题,设置app的解锁密码,当然不推荐让用户进行太过繁琐的操作)

WebActivity(用来展示网页的内容,此处当然要使用到我们的WebView控件)

MainActivity(主界面,此处我们需要展开去讲,主界面有两大块组成,一个是左侧的菜单栏,一个右侧的内容栏),我们在MainActivity中将这两个fragment填充到布局中,这个布局最好是帧布局,由于左侧的Sliding菜单我们使用第三方框架,所以我们要将两个fragment填充到两个不同的布局中

创建一个基类BaseFrament继承Fragment,将初始化view的方法放在onCreatView中进行,初始化数据的方法放在onActivityCreated中进行,此时能够获取更多准确的数据,之后创建两个子类

LeftMenuFragment(我们先讲这个简单的fragment)

里面是一个listview布局,每一个item显示一个标题

点击每一个item可以跳转到不同的界面,需要在item的点击事件中

ContentFragment

第一层分为:ViewPager和RadioGroup

这个ViewPager的滑动事件我们将其禁用,使用一个自定义的viewpager,默认不拦截任何触摸事件,设置一下这个viewpager的监听pagechanged的监听事件,当其为首页或者设置item时禁用左侧SlidingMenu,默认首页为禁用状态,这个viewpager的布局可以分为标题栏和内容,其中标题栏的样式大致相同,内容部分我们使用帧布局作为一个容器之后使用,创建一个基类BasePager,构造函数中完成初始化view的工作,初始化数据由不同的pager子类自定义,在此个fragment中完成这个viewpager的数据初始化工作,此处我们就讲一个pager界面,NewsCenterPager

其实我们在NewsCenterPager中处理的逻辑也是极为简单的,一个标题栏,我们可以先忽略不去处理,主要是让帧布局作为一个容器去装不同的view,view其实就是可见的一切控件,此处我们填充的view有四个,当然这时通过访问网络中的数据获取到的个数,这个四个view的切换由我们的左侧菜单栏所决定,所以我们需要在左侧菜单栏的listview的item点击事件中让其获取到NewsCenterPager对象调用帧布局填充view的方法(根据listview的position填充不同的view和view数据),此处我们当然也可以创建四个不同菜单选项的基类,继承这个基类创建四个不同的选项类,此处我们只是去处理一下新闻选项

处理新闻选项时,总体而言我们可以将其作为一个indicator和viewpager进行处理,两者还需要绑定,要在view设置适配器之后,当然我们还是要根据网络数据确定indicator的条目,确定我们要填充的数据,也是将viewpager当做一个容器去使用,由于这次的界面样式相同,只是填充的数据不同,可以传入不同的网络数据进行设置,所以我们只需要创建一个基类,初始化view就可以了,当然此处为了左侧的菜单栏只有在viewpager的第一个item时可以滑动弹出,我们可以设置viewpager的pagerchanged监听事件,让其只有在一个条目时是可以全屏滑动的,indicator本身的滑动事件也不能被抢走,所以设置一下其的触摸事件不能被父控件抢走,此处我们需要初始化网络数据填充,主要是一个listview控件

这也是最后view对象了,主要是填充上述viewpager,主要是一个listview布局,头布局是一个也是一个viewpager(主要是负责图片的轮播),当然还有一个上拉刷新和下拉加载的功能,所以中一定是一个自定义的listview,我们可以自己去定义,当然也可以使用第三方框架,第三方框架相对而言要稳定,而且我们已经清楚了这种效果实现的原理(主要还是依靠改变toppadding的值去实现),这个viewpager也是一个自定义的控件,关键是我们外面有一个listview控件,还有两个viewpager的父控件,我们要实现这个viewpager的滑动需要判断一下,首先我们可以区分一下上下滑动的事件和左右滑动的事件,计算dx和dy的值,假设dx大于dy(当然是绝对值)我们就认为是左右滑动的事件,上下滑我们交给listview处理,左右滑动事件我们利用dx是否的正负属性可以判断,左滑为负,右滑为正,,左滑时判断一下是否为最后一张图片,假设是最后一张图片就交给我们上一级的父控件处理,上一级的listview不处理左右滑事件,就会交给我们的上一级viewpager处理,右滑也是同理,不处理第一张的事件,图片轮播通过handler发送一个空的延迟消失来实现,唯一的逻辑就是滑动到最后一张时,从重新定位到第一张,此处假设想要点住图片不执行图片轮播,可以给设置一个ontouch监听,在down时让handler删除消息,在up时重新发送延迟消息,可能这个事件会被取消,所在cancel状态下也要让其发送延迟消息,否则轮播效果就失效了.

补充:listview的条目点击事件允许期间的move事件,view的onclick事件不允许,ontouch中必须返回false,否则点击事件失效,点击之后进入到我们的WebActivity中,携带网页的url数据过去(使用intent携带),关于标题栏的变化我在此处就不展开去讲了,不同对象之间的相互调用也可以自行领悟一下.

RadioGroup也是在我们的这个fagment中被初始化,设置一个rg的checked的监听事件,不同的item被选中时我们进行viewpager的切换就可以了,此处rg的逻辑已经完成了

需要补充一下的是标题栏上的菜单按钮,我们可以BasePager中设置一个点击事件,调用侧边栏的toggle()方法改变其当前的弹出和隐藏状态

注意:这个项目中我们修改了第三方框架,也是用集成度特别高的Xutils框架,我们不推荐这样做,一方面假设第三方框架更新了,我们自定义的内容可能会失效(又要重新去考虑实现逻辑),另一方面对第三方的框架依赖性太强.

关于性能的优化

内存优化

我们可以使用LeakCanary和MAT分析我们的内存泄露状况,减少内存泄露的发生,从释放更多的内存,减少应用对内存的占用

布局优化

需要延迟加载的布局可以使用我们的viewStub标签,这个标签可以通过inflate方法去加载内部的布局文件,而不是当其被对象化时就开始加载内部的布局,这样可以减少不必要的资源消耗,假设显示或者隐藏属性,从本质上还是会加载view对象,需要我们注意的事项是这个viewstub对象在第一次调用我们的隐藏或者显示属性时,默认会调用inflate方法去填充我们的布局资源.

从根本来讲还有就是减少我们的视图层级,刷新和渲染每一次需要遍整个viewtree

include标签,复用我们的布局资源,不必要重复创建

merge,整个节点通常会被忽略,假设使用include调用这个根标签的布局文件,就会忽略掉这个节点,从而不会进行多余的加载,从而消耗手机的性能

当然还有过渡绘制的问题,我们可以开启手机的中开发者选项,让其显示出过渡绘制的区域,根据,之后根据这个区域去检查我们的代码中是否设置了多余的background,我们删除这部分的背景,从而提高性能.

线程优化

使用我们的线程池(ThreadPool),不要每一个使用new Thread

当然安卓给我们的提高了一个异步线程池的类:AsynTask,通过这个封装类的使用可以有效地让系统去管理我们的线程,其本质是通过Handler和线程池实现的.

当然还可以使用我们的RXjava(这个需要自己去了解下,是一种新的technology)

代码优化

对于listview的优化

复用我们的convertview

使用holder存储子控件对象,避免重新view的树形结构

还有缓存机制,不能每一次去重新去加载一张网络上的图片,当然第三方框架可能已经实现了这种功能,主要是通过内存缓存和本地缓存两种方式去缓存我们从网络上加载到的资源

Cursor的回收

这个对象主要是在我们对数据库进行查询工作时被返回,在使用完之后我们必须关闭由其所打开的数据流通道,只需要调用我们的close方法就可以实现了

还有一点比较重要,就是这个游标对象不能使用GC来处理,否则可能会报错,Android明显倾向于让用户自己手动将Cursor close掉

对广播接受者的回收,我们可以动态注册一个广播接收者,在使用完之后我们必须注销这个广播接受者,通常会是在activity的onDestroy方法中对其进行注销,对于只使用一次的监听器,我们也可以手动调用remove方法将其移除.

当然还要减少不必要的全局变量,以及尽可能不要在application类中写代码,尽量使用Application这种Context类型,前者容易造成内存泄露.

各种数据流的关闭,IO流,数据库,游标(Cursor),文件,bitmap资源(回收以及将其引用重置为null)

避免创建不必要的对象,频繁操作一个字符时,建立使用StringBuffer以及StringBuilder进行代替,创建int数组不能使用Integer创建(后者占用了更多的内存).

还有就是在Android慎重枚举,枚举的开销是静态常量的两倍多

还有在android中不需要使用getter和setter方法,这样性能开销太多,android之中直接使用public修饰,直接使用就可以了.

这也是极为重要的一个教训:永远不要在for循环的第二个条件中调用任何的方法,这样这个方法被调用多次,影响我们的性能,切记

//错误方式

for(int i = 0; i < this.getCount ; i++){

}

_____________________________

//正确方式

Int N = this.getCount();

for(int i =0; i

SparseArray:

这个类的使用(据说安卓的这个类可以代替HashMap来使用,而且有着更加好的性能),使用的情况是当其的key为一个int值时可以使用(而不必创建一个Integer对象),这样效率可能会更加的高,当然原理的部分只需要了解一下就可以了,不必去掌握.

使用工具去优化我们的性能

使用HierarchyViewer分析UI性能,每一个view下面会有三个小点,分别代表了测量,布局以及绘制的渲染事件,慢不一定就代表性能差,也可能是viewgroup本身的子控件太多造成的.

在设置->开发者选项->调试GPU过度绘制(不同设备可能位置或者叫法不同)中打开调试后可以看见如下图(对settings当前界面过度绘制进行分析,这个工具我之前提到过了

无色  WebView等的渲染区域

蓝色  1x过度绘制

绿色  2x过度绘制

淡红色 3x过度绘制

红色  4x(+)过度绘制

补充:

由于过度绘制指在屏幕的一个像素上绘制多次(譬如一个设置了背景色的TextView就会

被绘制两次,一次背景一次文本;这里需要强调的是Activity设置的Theme主题的背景不被算在过度绘制层级中),所以最理想的就是绘制一次,也就是蓝色(当然这在很多绚

丽的界面是不现实的,所以大家有个度即可,我们的开发性能优化标准要求最极端界面下红色区域不能长期持续超过屏幕三分之一,可见还是比较宽松的规定),

因此我们需要依据此颜色分布进行代码优化,譬如优化布局层级、减少没必要的背景、暂时不显示的View设置为GONE而不是INVISIBLE、自定义View的onDraw方法设置canvas.clipRect()指定绘制区域或通过canvas.quickreject()减少绘制区域

当然此处还是要补充一个知识点,我们知道电影的fps通常为24fps就极为流畅(主要是存在影像过渡,人们的肉眼不容易觉察不出来这种变化),而我们在手机上使用的动画是不存在这种过渡影像,直接从一张清晰的图片却换到另一张清晰的图片,假设也使用24fps就会被人眼所识别出来,人眼识别出来就会觉着动画好卡,或者说认为这个动画存在着卡顿现象,在实际的调试中我们认为60fps不会让人眼觉着有卡顿感,转换一下单位就是说一帧的显示不能大于16ms,这样才能给用户最好的体验.

准则:尽可能保证每一次必须在16ms之内处理完所有的CPU以及GPU计算,绘制,渲染操作,

否则就会造成卡顿或者是丢帧的问题

使用"adb shell dumpsys gfxinfo [应用包名]"去检查我们的UI性能,手机里面也可以查询到GPU的性能

也可以使用android自带的分析工具,选中我们要分析的代码,右键进行分析

也有可能是对象频繁地创建和释放造成的内存抖动,频繁出发GC操作

当然还有其它的调试工具,我们此处就不补充了,最后当然是要总结,其实我们之后所做的这一切是一种补救措施,俗话说我们需要防患于未然,所以我们在补救之前就需要尽可能避免这种性能丢失的现象产生.

关于安卓系统的定时任务(大致可以读一下,感觉Handler机制已经不必去讲了)

第一个当然还是使用我们的handler类去实现,发送延迟消息,递归调用我们的handlermessage方法就可以实现循环操作,当然也就能实现延迟操作,当然如果还是单纯地进行延迟操作,还可以使用handler的postDelay方法进行操作,当然这个方法是从工作线程却换到UI主线程的方法,注意一下使用情景.

还有可以执行定时任务的类Timer和TimerTask,一个设置延迟方式,另一个设定要执行的任务

上述的定时功能可能不太准确,在API19之后(也就是安卓4.4版本之后)alarmmanager用来执行我们的延迟任务可能会更加准确,或者说执行我们的延迟意图paddingIntent(还未被决定的意图)

补充知识(xmpp和http的区别)

http

可以使用tomcat服务器

我所使用浏览器就是我们的客户端

第三方库:httpclient,UrlHttpConnection,volley,okHttp

传输的文件可以是json格式的

客户端主动

xmpp

使用服务器有openfire

客户端我们可以使用一个spark

第三方跨:smack,android中通常使用:asmack

传输文件的格式是xml格式

服务端和客户端之间维系了一个长连接(本质是一个输入输出流的消息通道),服务端和客户端之间可以相互访问

面试题的整理以及解答

activity的生命周期

此处就不再赘述了,就说一下如果activity异常终止会运行到onSaveInstance()这个方法去保存我们的数据,之后重新开启我们的activity,将保存的数据传递回来

service的生命周期

开启我们的service有两种方式,第一种方式就是我们常用的startService,每一个start我们的service全部会执行到service中的onStartCommand()的方法

当然还可以绑定我们的服务,执行bindService的方法,这个方法被执行之后,返回的IBinder对象是异步返回的,所以要使用serviceConnection对象进行接收,里面有两个方法,服务连接上之后就会返回我们的IBinder对象,意外中断或者连接不上会执行到未连接方法,结束连接不会触发该方法.

如何列Activity,View以及Window三者之间的关系

activity被创建时会初始化我们的window,准确来讲是一个PhoneWindow

window内部有一个viewroot,是一切VIew或者说ViewGroup的根视图

这是一个根视图(连接windowmanager和decorview),通过addViw将其它的视图加载进来.

监听事件通常是由windowmanager接收,之后回调acivity中的方法.

activity中不同的LaunchMode以及使用场景

standard模式

这是一种标准启动模式,每次启动Activity会创建activity实例并且被放到栈顶,大多数的activity

singleTop模式

已经有一个activity的实例处于栈顶了,当我们又创建这个Activity的实例放到栈顶,这个实例不会被创建,而是复用原来的实例,使用场景就是新闻类app的内容界面

singleTask模式

假设栈中已经存在一个实例,重新开一个实例时就会复用原来的实例,将其上面的实例全部移出栈,,比方说浏览器,不管被多少应用调用,这会启动主界面一次.

singleinstance

一个实例具有单独的栈,切换时会复用实例,singleInstance不要用于中间页面,如果用于中间页面,跳转会有问题,比如:A -> B (singleInstance) -> C,完全退出后,在此启动,首先打开的是B

自定义view

三个基本步骤:测量(onMesure),布局(onLayout),绘制(onDraw)

三个最为基本的定义方法是继承view,继承viewGroup,继承已经定义的view控件

绘制流程(其中有源码分析,有源码恐惧症的同学可以跳过)

首先绘制流程从一个ViewRootLmpl类(就是viewroot的实现类)的performTraversals()方法开始,这个方面主要是判断一下rootview本身是否需要重新测量,布局以及绘制,假设需要就就执行相应的动作,假设重新测量会有一个MeasureSpec,通常这个测量控件有的specMode为EXACTLY(已经精确测量),specSize为windowSize,所以我们的根视图通常是全屏的.

测量流程

测量从本质上就是遍历整个view树结构,开始点是我们的rootview,之后遍历每一个view以及viewgroup进行测量,假设是viewGroup还会遍历viewGroup下的每一个子view,对每一个view(当然还有我们的viewGroup)进行测量

measure方法:这个是由系统调用的,根据父视图进行测量,不允许我们重载调用,所以通常用来获取系统的测量值

onMeasure()是由我们开发者自定义的一个测量,我们假设需要自己设定测量的方法和值,我们可以在此个方法中写代码,通过setMeasureDimension来设置任意大小的布局,当然这样做是不太好的,我们在实际开发使用时需要慎重,此处的尺寸可能必须由我们的系统所决定,我们随意更改可能也不会生效,系统会进行判断,当然也有可能其的模式是可以让用户自定义的,这时我们就可以对空间的宽高设定一个固定值了.

父控件会调用子控件的measure,这是传递性质的,树形结构本身是嵌套

总结 View的measure方法是final的,不允许重载,View子类只能重载onMeasure来完成自己的测量逻辑。

最顶层DecorView测量时的MeasureSpec是由ViewRootImpl中getRootMeasureSpec方法确定的(LayoutParams宽高参数均为MATCH_PARENT,specMode是EXACTLY,specSize为物理屏幕大小)。

ViewGroup类提供了measureChild,measureChild和measureChildWithMargins方法,简化了父子View的尺寸计算。

只要是ViewGroup的子类就必须要求LayoutParams继承子MarginLayoutParams,否则无法使用layout_margin参数。

View的布局大小由父View和子View共同决定。

使用View的getMeasuredWidth()和getMeasuredHeight()方法来获取View测量的宽高,必须保证这两个方法在onMeasure流程之后被调用才能返回有效值

布局流程

要布局必须有容器,也就是viewGroup,不存在容器的view设置layout属性是不存在任何意义的

viewgroup的layout方法不可被重载,也就是说是被final所修饰,onLayout方法是一个抽象方法,可以重写,当我们继承一个ViewGroup就可以重新摆放子控件的位置,继承之后我们必须重写这个方法,一个view设置其的layout属性的前提是必须有父容器,假设不存在父容器我们设置这个layout本身就毫无意义.

和上面的获取测量宽高的方法一样(必须在onMeasure方法之后),我们想要直接获取宽高(getWidth(),getHeight())也必须在onLayout方法之后进行

绘制的流程分析(相对而比较复杂)

本质是在画布对象canvas上绘制,如果view是一个viewGroup,需要递归绘制所有的子view,也就是说viewgroup本身不进行绘制工作

View默认不会绘制任何的内容(就是空白的画布),需要我们自己在子类中去定制绘画的内容,借助传入的canvas实现

view和viewGroup的动画是有区别的

view动画是针对自身的动画

viewGroup是针对子视图的动画(比方说子视图出现的方式,逐个出现之类的)

从draw中传入的canvas(传入到onDraw方法中)默认会自动处理掉padding参数,我们只需要关注绘制的内容就可以了

默认情况下子View的ViewGroup.drawChild绘制顺序和子View被添加的顺序一致,但是你也可以重载ViewGroup.getChildDrawingOrder()方法提供不同顺序。

dispatchDraw()方法已经调用子控件的draw方法,所以通常我们不需要重写这个方法

onDrawScrollBars():绘制滚动条,我们通常情况下不会调用这个方法,有兴趣可以去浏览一下官方文档

其实还有一个线程上的事情需要补充,就是draw方法是在主线程中进行的

invalidate以及postInvalidate(这个就是将工作线程的操作换到UI线程中操作)

在我们的activity被创建时调用的setContentView方法从本质上来讲就是绘制id为content的区域,这个区域最外层为framlatout的布局,在addView方法中会调用invalidate(true)方法,从而进行递归绘制

直接调用invalidate方法以及setSelection方法只会重绘调用者本身

从可视状态发生变化时会调用invalidate方法进行重新绘制,变到gone状态还会调用requestLayout,我们gone本身不占据任何的空间位置,也只会绘制需要重新绘制的区域(通过特定的判定实现)

setEnable(),请求重新绘制,可是不会重新绘制任何的控件

触发requestFocus方法。请求View树的draw过程,只绘制“需要重绘”的View。

来一个总结,总结就是理论结合实际,在实际的运用中我们才能发现这种理论模型的价值,在面试过程中我们只需要结合实际的案列说出特定的点就可以了,比方说post开头的方法大部分是将子线程中的代码换到主线程中去执行,假设我们继承了view,是一定要绘制这个view控件的,位置可能是由我们的父容器本身所决定的,假设是使用ViewGroup,相当于是一个容器,这个容器需要重写一下子view如何摆放,这是必须的,绘制也是对子view的绘制,不如交给我们的子view自己实现,当然还有分发draw的顺序,其本身是根据按照子view的加载顺序执行的,当然有需要我们也可以改变一下,重点是作为一个容器我们需要关心的是子view如何在我们的容器中摆放(这才是正事),还有就继承已有的控件,关键是对该控件进行扩展以及逻辑判断上的重写,比方说对触摸事件的拦截模式(我们可以根据自己的需求进行更改),比方说自定义listview中直接加上一个下拉刷新的头view,当然我们还需要注意到其它的方法,比方说大小发生改变的方法,此处救不详细去讲了.

touch事件的传递机制(重点又来了)

上面我们分析自定义控件的流程,其实我想说在android中的难点还有一个对生命周期掌握,对象引用的理解,最后当然还有对touch事件的理解,要修炼成为一个android工程师也不是一件简单的事情,当然最后还有对android框架的理解,以及搭建android项目的框架以及性能调试方面的知识是我所不能涉及的,扯完了,说正事.

Android控件大致可以被分为两类,一个是容器(容器是一个概念,可以被扩展,比方说封装一部分代码的类就是一个容器,之后我们还可能去详细地论述):ViewGroup,另一个是真正被显示出来的视图(真正展现在我们眼前的视图动画之类的):View,而且从rootView开始以树状结构存在,我们经常所使用的findViewByID就是从树顶开始去查找ID匹配的view对象,其实这种方法效率太低,在listview我们通常采用一个Holder类去保存已经找到了的view对象,在复用时不用重新去查找,本质就是为了优化我们的性能,减少执行findViewByID()的次数(此处我进行了一定的扩展,回顾一下).

让我们从view控件的触摸事件开始理解(不理解view的人可以被打打死了)

我们说任何view的触摸事件是从onTouch事件开始的,之后才会执行到我们的onClick之类的触发事件

在我们的disPatchTouchEvent方法中首先执行onTouch方法(切记如果在这个方法中返回true,也就是说这个方法消费了触摸事件之后,事件就不会往后面进行传递了),可能有人会问了我们经常使用的onTouchEvent()何时被执行?下面我们就可以去解决这个问题了

假设我们的return为false或者onTouchListenner不存在实例化对象(为null)或者控件本身不是enable情况下就会调用onTouchEvent()方法

换句话讲就是ontouch方法如果想要单独消费上述的触摸事件必须满足上述的三个条件,需要注意的是当控件不是enable时,我们说是不会走onTouch方法的,如果控件是enable,返回true,dispatchTouchEvent会直接返回true,不会让onTouchEvent()被执行到了,也就是说这个触摸事件被onTouch单独消费了.

dispatchTouchEvent

我们需要注意到一点只有前面的action返回true才能继续触发下一个触摸事件或者说action,也就是说返回false一个点击的事件不会被消费,而是会被继续传递下去,这是在我们的view中特有的机制,也就是说在onTouch中假设down这个action未能被返回true消费了,在之后的onTouchEvent中会继续被接收到,从本质上来讲还是原来的Event事件对象.

如何被设置了enable为fasle,我们是无法触发点击事件以onTouch事件的,需要我们重写onTouchEvent中的触发事件来处理触摸事件,需要注意的是假设仅仅是设置了不可点击的状态,假设我们又设置了点击的监听器,这个监听器会主动将状态改变为可点击的状态,也就是说我们还是可以触发点击事件,这是我们需要注意到的一个知识点.

总结一下

如何让我们单纯地去分析一个view的事件,其实只要关注到onTouch()方法和onTouchEvent()方法在dispatchTouchEvent()中的如何被分发就可以了,其中我们也粗略地谈到了控件状态对于这种事件的影响,主要是enbale状态以及clickable状态,还有就是在view中action假设无法被返回true处理掉,还是会继续讲这个action传递下去,而不是继续讲下一个action传递下去,这一点我认为需要引起我们的注意,也是我们的需要记住的常识,案例就是假设我们想要按住viewpager上的图片不让其进行轮播操作(我们的轮播操作是通过handler发送延迟消息,递归执行处理的消息的方法实现的),我们可以设置一个onTouch的监听,在down时,取消消息的发送,up时继续发送延迟消息,当然还有可能这个触摸事件被取消,我们可以cancel状态下继续发送我们的延迟消息.,主要注意的是必须在重写的onTouch()方法只能够return false,否则viewpager本身的滑动事件就无法被响应了.

接下当然就是ViewGroup的触摸事件了

ViewGroup的dispatch方法(触摸事件分发机制)

首先我们必须注意到这个方法是一个调用的方法,而且这个方法里面可能会直接调用拦截的方法或者是onTouchEvent()方法

第一次要处理的当然是DownAction,主要还是做初始化的工作,清空状态标记以及重置这个touch事件(记住就好了)

第二步是要处理是否拦截该事件了,intercept方法所返回的布尔值决定了该事件是否要继续向下传递,注意一下的是该方法在分发的方法中被执行,如果返回true就是说拦截该事件,该触摸事件就不会向下传递,传递到此处就被终止了.

这才是真正的第二步,requestDisallowInterceptTouchEvent(boolean b),假设调用这个方法,true表示不拦截,false表示拦截,这个状态最大的应用在于子控件在分发机制中请求父控件不要拦截(true)自己的触摸事件,当然还有一点需要注意的是假设我们的继VIewGroup写一个自定义的容器,默认情况下是不拦截touch事件,也就是说返回值为false.

有人认为现在该是把事件分发给我们的子view了?假设这个事件被别人抢走了,我们需要考虑下这种情况,也就是说执行到了ActionCance状态,如果这个事件未被拦截而且未被取消掉,我们就说这个事件通过我们的ViewGroup要被分发给我们的子控件进行处理.

这一段的源码比较复杂,我们直接点来讲(不使用状态值的判断),就是说假设子View不消费掉所有的触摸事件,就会返回给我们的父控件进行处理.

上述的情况我们实质上是论述了事件的传递机制,当然还有责任机制,假设下载的View无法消费事件,事件就会返回上一层的去处理,假设还是无法处理还会想上层进行回传,这就是事件的责任链机制.

记忆机制:默认情况下记忆一个view处理down事件,接下来的触摸事件全部会交给我们的view处理,当然父控件进行拦截还是会交给我们的父控件处理,其实最为关键的问题在于在于down事件传递到子控件时,可以处理,却可不消费这个down事件,如下无法继续向下传递,我们说这个down事件就会被回转到父控件(这是最为关键的一句话),如果一个事件被消费了,就不存在了,此处一定要分清楚对触摸事件的处理和消费的区别,要想要消费一个触摸事件必须在onTouchEvent中返回true,这样才能真正把这个事件销毁掉.

总结

这个总结相对而言还是简单的,只要明白事件的传递机制,责任机制以及记忆机制就可以了,当然需要引起我们注意的还是事件传递中消费和处理的概念,事件只要被消费才真正被销毁,事件被处理只是事件传递中的一种代码逻辑.

最后幕后人物:Activity

一切触摸从此处开始分发,第一个会分发给root view,此处所指的当然是phoneWindow内部的DecorView类的对象,这个view当然也是一个ViewGroup,循环分发我们的触摸事件,当然最后假设还是回转到我们的ACtivity,这个触摸事件可能就会被丢弃掉了,所以一切触摸事件从此处开始,可是却不一定从此处结束,此处我们只需要了解一下就可以了,后面我们可能还是要说下Android的图层(可以期待一下).

谈谈Context,setContentView以及LayoutInflate

我先谈一下Context这个抽象类,这个类简单来讲就是一个让我们访问android系统中所有公共资源的功能类,在我们的开发中有三个类直接继承或者说间接机场了这个类:

Application

Activity(我们经常使用这个对象获取资源)

Service(服务就是一个不存在任何的界面的Activity)

其实对这个对象的使用,我个人只有一个建议:不要使用太多的上下文对象,在一个Application中我们可以使用单个Application对象作为我们的上下文来使用,这样可以有效地避免我们的内存泄漏,当然有的地方不能使用这个上下文(不用就行了),防止内存泄露的原理就是这个上下文对象只有当整个应用退出时才会被gc回收,对于超级强大的工具类,我们无需多说,既然系统提供了只管用就可以了.

前面我们说会谈到android的图层,在setContentView()中

前面我们提到了幕后的操控者Activity,这个操控者是处于整个UI界面的控制中心,其本质就是控制单元

这个对象要操控的是一个叫Window的对象,由于Window本身是一个抽象类,其有一个实现类对象PhoneWindow就能了被操控的对象

我们不经想问这个Window对象是谁?这个Window对象具有的功能就是对这块显示屏进行绘制,或者我们也可以说其封装了一系列绘制窗口的API,说白了就是一个操作底层绘制屏幕的框架,PhoneWindow本质就是对手机屏幕进行绘制.

现在我们的问题我们需要绘制的内容,这种内容我们将其称之为view,这种内容的布局我们称之为ViewGroup,在我们PhoneWindow中有一个内部类:DecorView,这个类继承了Framlayout,显然已经封装了一个帧布局,当然还给我们预留一个状态栏以及导航栏的位置,decorview里面哟一个actionbar(5.0版本之后称之为toolbar,其实这个官方的标题栏在开发中我们通常不实用或者说我们会自己自定义一个),DecorView代表了整个视图内容,假设要对整个视图的属性进行获取或者修改可以获取这个对象进行修改,setContentView()设置的是除了标题栏,状态栏,导航栏的内容区域,整个区域也是由我们的开发者所自定义的区域,这个方法的实质将我们的布局文件作为一个view填充到这个内容区域之中.

LayoutInflate就是将一个布局文件解析为view对象,注意在这个过程中会忽略原先布局中的宽高参数,等于是放弃原先的框架,让其成为一个独立的view对象,在setContentView中我们实际上将这个view放到id为content的FramLayout容器之中.

关于merge跟标签的作用和ViewStub标签的使用前面我们已经提到过了,主要是优化方面的双亲,前者避免我们inflate时对根标签进行解析提升效率,可是我们必须为其制定一个父控件(一定要是ViewGroup),后者是避免将要inflate的内容直接加载到内存,只有在执行inflate时才进行加载,开始这种inflate只能执行一次,ViewStub对象随之会被销毁,不能嵌套merge,当然还有使用include方法直接引入一个布局文件,前面我们已经提到这部分的优化内容,此处就不再赘述了.

总结

此处当然还是会有补充,我们经常会使用会给view对象设置一个LayoutParams,这个参数是指上就是一个布局参数,等于是给一个view加上一个布局参数,加上这个布局参数主要是为了让控件的显示收到这个参数的约束.

Android中的动画

View Animation:这种动画被称之为补间动画(Tween动画)

AlphaAnimation,渐变透明度动画效果

RotateAnimation,画面转移旋转动画效果

ScaleAnimation,画面渐变伸缩动画效果

TranslateAnimation,画面转换位置移动动画效果

AnimationSet,一个持有其它动画元素alpha,scale,tanslate,rotate

我们可以给我们的动画设置各种不同的插补器,以求达到不同的动画效果

缺点是view本身只是被隐藏,并不是真的在执行动画

还有一种我们称之为帧动画(Frame动画),就是一帧一帧的来设置我们的动画效果,如同我们平时播放幻灯片一样的效果.

:里面可以放置不同的图

属性动画(为了解决补间动画的缺点)

内部实现:3.0之后view增加新的用来记录动画行为的属性

translationX,translationY,scaleX,scaleY,rotationgX

具体实现的类:ObjectAnimation

问题是只能在API11之后才能够使用(就是安卓3.0版本),使用nineoldeAndroid实现我们的属性动画,jakewharton创建的第三方类库

使用这个类库使用的类是ViewPropertyAnimation进行设置设置就可以了

使用了链式编程,可以直接使用一系列的动画效果

我们在公司的实际开发张经常要使用到这个动画包

by方法是相对值,其它的是一个绝对值,只会执行一次

直接调用view的属性来实现view的形态,我们可以直接设置我们的view形态,之后通过上述动画进行改变

如果想要在低版本直接操作,使用ViewHelper设置属性就可以了

还可以使用我们的interpolater,可以实现特殊的动画效果

overshoot是超过一点之后再恢复原来状态

bounce:弹簧效果

Cycle:动画被反复执行,参数是要执行x次

自定义动画,ValueAnimation

帮助我们的定义了动画的执行流程,可是并未实行具体的动画逻辑

我们需要在监听之中监听动画的进度,然后回调这个方法中的动画逻辑执行我们的动画效果

总结

在实际开发中我们已经不会再去使用原来的view动画(或者我们也可以称之为补间动画),我们通常会使用属性动画,由于android原生的属性动画只能在要求最低版本为3.0,由于我们使用第三方类库:nineoldAndroid,我们在使用属性动画时主要分为两步:使用ViewHelper定义view在动画开始时的属性状态,之后使用ViewPropertyAnimation去定义一个具体的属性动画,当我们碰到一个动画效果不是通过四种动画效果以及插值器实现时,我们需要自定义一个动画效果,这时我们就必须考虑去使用我们的值动画,利用这个value值的进度变化,在监听中实现我们的动画效果.

补充一下(不同API对应的android版本,不需要记忆,只是熟悉一下重要的版本)

android2.3.3:API10(这个是最为重要的版本)

android3.0:API11(这个版本主要被应用在平板上,也引入我们的fragment)

android4.4:API19(这也是一个经典的版本)

android5.0:API21

android6.0:API23

android7.0:API24(牛轧糖)

自定义布局实例:流式布局(FlowLayout)

我们首先需要考虑的是直接继承一个布局view还是继承viewgroup

如何直接继承一个布局view说明我们只是想要在这个布局的基础上进行修改,前提是我们对这个布局的逻辑要特别的熟悉,不然的话会造成不必要的bug.

假设我们想要完全独立创建一个自己的自定义布局,我们可以直接ViewGroup,直接继承这个控件的必须要求我们的重写onLayout方法

在我们考虑这个布局中的子view该如何被摆放之前,我们需要考虑一个问题,这个控件的摆放和子控件本身的尺寸是否相关?获取说是否受到子view本身的尺寸约束(必然和尺寸相关,毕竟我们的控件本身的尺寸或者更准确地来讲显示面积是有限的),我们在这个自定义布局中也对内容区域的面具进行了提取,也就是说这内容显示区域的宽高通常是为自定义布局本身的宽高减去对应的padding值,这才是内容区域真正的宽高值(这需要我们注意一下,在摆放我们的控件时必须以这个宽高值作为边界),当然在布局中我们还需要考虑另一个问题,就是对子控件的封装(最好采用javabean的方式对其的子控件,以及相关的属性进行封装,通常会是宽高信息),此处我们所采用的方式主要是对控件的一行进行封装(在内部创建了一个Line类封装一行的信息,以及创建了一个方法添加同一行的子view对象),在添加子view的过程中主要是根据一行的宽度是否足以摆下下一个控件进行判断的,当然第一个控件无论是否能够摆下,我们必须摆在着一行,这个需要对尺寸进行测量和判断,主要是在我们的onMeasure方法中进行,在这个方法中进行逻辑判断以子view的添加是需要引起我们注意的是子view的宽高不能直接获取,还有就是不能在这个方法执行完毕之前调用这个控件的测量宽高值的方法,这个方法无法获取到这个控件的宽高,我们可以通过传进来的MessageSpec来获取宽高值(这是一个技巧,需要我们自己注意一下).

在我们对子view分组好之后,我们要将每一组的控件按照特定的规则摆放到我们的这个布局之中,此处我们不详细地展开去讲,只能说在此个方法中我们需要实现每一个控件的摆放(根据的特定摆放逻辑和要求).

补充(注意):前面我们已经提到子view的宽高不能直接被获取,通常我们会使用子view的measure方法传进去一个默认参数获取子view被测量之后的宽高值,当然我们也可以传进去一个准确的值去对子view的宽高进行赋值,由于这种布局本身和每一个相关控件的宽高密切相关,所以在自定义布局我们必须注意一下我们在自定义过程中所获取的宽高值是否是有效的,需要注意的是添加子view时用的是for循环,主线程只能将for循环执行完毕之后,才能去执行onMeasure方法,所以这个方法只执行了一次.

关于设计框架(在Android中的应用)

设计框架就是整个项目的代码模板,所谓的设计模式不过是一种代码上的设计技巧(其实可以根据我们自身的理解扩展一下,这也是一个开放性的问题,不必为了理解这个两个概念的定义去背书,背书有背书的好处,我不否定这种方式)

当此处我们还是要提一下android中的mvp设计,在此个解释这个框架之前,我们先提一下mvc框架,这个框架主张的是将视图,业务逻辑的控制以及数据模型分开,我们需要注意的是尽管三个比模块可是彼此之间还是通过特定的方式衔接着,当然这种衔接可能是一种方法的调用,也可能是一种回调,这属于代码的设计范畴,对于android来讲我们在mvc中的vc通常是指我们的activity,也就是说这个对象控制视图和用户的交互以及UI的更新,还有就是接收用户的交互操作(触摸事件),对这个操作进行判断,之后可能需要通过一个dao类对我们的数据模型进行操作(此处我们可以可以将其说成是我们的数据库),在数据库我们通常可以注册一个内容观察者,通过这个内容观察者的回调我们对我们的UI进行更新,当然的设计对于来讲是不好的,我们需要从activity抽取代码,让activity本身只承担一个view的更新以及交互工作,而且将所有的逻辑操作以及对数据模型的操作工作全部放在另一个模型中(当然也可以是一个封装的类中),这就是必须提到我们在android编码中所使用到的mvp模式(p:Presenter).

在MVP模式中我切断了M和V之间的联系,将这种功能转交给了我们的P全权处理,也就是说view的更新,以及UI的渲染工作大部分是通过调用P中的方法完成的,或者是提供接口给我们的P去完成具体的操作,通常这个P需要有个模块:初始化view的操作,初始化数据的工作,提供对数据操作的方法,更新或者刷新我们的UI界面,当然假设存在重复性的结构我们还需要对我们的代码本身进行抽取,有一个弊端是我们无法避免的,就是P中的代码太多了,对于处理复杂逻辑的界面是有好处的,可是对于处理简单的逻辑需求这种模式就太重了.

多线程的下载框架设计(主要还是要写一个DownLoadManager和DownLoadInfo)

ThreadPoolExecutor:线程池(了解一下构造函数中的六个参数)

corePoolSize:核心线程数,也就是说可以同时执行的线程数

maximumPoolSize:最大的线程数,包含了核心线程数,所以这个线程数不能小于核心线程数

keepAliveTime:超出workQueue的等待任务的存活时间

unit:时间的单位,分秒时之类的

workQueue:阻塞等待线程的队列,通常使用new LinkedBlockingQueue()这个,如果不指定容量,会一直往里边添加,最大值为int类型的最大值

threadFactory:创建线程的工厂,使用系统默认的类

handler:当任务数超过maximumPoolSize时,对任务的处理策略,默认策略是拒绝添加

核心线程数的算法 = cpu的核数 * 2 + 1

使用Runtime.getRuntime.获取可用核心数

cpu按照时间片随机切换线程,不能将时间浪费在切换上,也不能浪费其的性能,所以我们的研究人员通过实验发现了开启的线程数为cpu的核数加上一能够最大限度地使用我们的cpu性能.

首先我们需要对我们的这个线程池进行封装以及管理,创建一个ThreadPoolManager,做一个单例设计,此处我们采用的是一个饿汉式的单例设计,也就是私有化我们的构造方法,在构造方法中初始化我们的线程池对象,这样我们就可以使用这个对象中的这个线程池对象了.

当在此个管理对象中我们还提供了添加任何(也就是new出来一个runnable对象),当然还有一个就是删除这个任务,此处其实就是封装了线程池本身的添加和删除方法

DownloadManager:下载逻辑的管理类

1.设计为单例模式

2.对下载逻辑进行状态细分:未下载,下载中,暂停,下载完成,下载出错,等待中

3.设计相关的方法和变量

4.下载方法的实现:

a.获取下载任务对应的DownloadInfo,因为DownloadInfo封装了我进行下一步操作的数据

b.获取下载任务对应的state来判断是否能够进行下载:未下载,暂停,下载出错,

c.判断下载是从头下载还是断点下载

d.获取流,保存到文件

反射

获取我们的Class对象(字节码文件)

对象.getClass

类.class

Class.forName(全类名)

基本数据类型,String,void,数组,引用数组全部存在类类型Class

方法

newInstance():创建实例对象

getName():带有包名的名称

getSimpleName:不带包名的类名

getMethod():第一个参数为方法的名称,第一个参数为参数类型

getDeclareMethod():获取执行的方法

getMethods():获取所有的公有方法,方法数组

getDeclareMethods():获取所有的方法

类被加载到内存的方法区

动态加载:直接获取我们的字节码,而不会通过new对象加载,字节码.newInstance()

静态加载:当我们new出来一个对象时,首先会把类加载到我们的方法去区,这就是我们静态加载

Method

getReturnType():获取返回值类型

getName():获取方法名

getParameterTypes():获取所有的参数类型

反射就是可以直接获取到一个类,通过这个类获取这个类中的成员以及方法

系统注解(方法,类,成员变量)

一个是我们的方法重写(@Override,不能放在method上,不能放在变量上)

另一个是方法已经过时了的系统注解(@Deprecated)

@SuppressWarnings:不需要警告的类,成员变量...

元注解(为了构建注解本身的注解,自定义注解)

xuitls是运行时注解,butterknife是一种编译时注解

@Target:指定注解存放的位置

构造函数

成员变量(Field)

布局变量

方法(method)

包(package)

描述参数(Parameter)

用户描述类,接口(包括注解类型上面)或者enum声明(TYPE)

@Retention,定义了Annotation被保留的时间,描述了注解的生命周期

1.Source:在源文件存在的位置(在代码中有效)

2.CLASS:在字节码中有效

3.RUNTIME:在运行时有效

@Ducumented:在生成文档之后,注解的信息会显示在文档中

@Inherited:注解可以被子类所继承,也就是说当一个子类继承父类时,会继承对应的注解,需要注意的是类上面

例子

@Target({ElementType.METHOD})

@Retention(RetentionPolicy.CLASS)//表示在编译中存在,在运行时就失效了

@Ducumented//会显示在doc文档之中

public @interface People{

//成员变量,可以定义默认值,名字最定义为value

String name() default "默认值";

int age();

}

//现在就能在方法上面使用@People了

自定义注解

public @interace [注解名]{定义体}

类型:基本数据类型,以及注解类型本身,String,枚举,Class类型,以及数组,不允许返回一个对象

注解的处理器

方法1:T getAnnotation(ClassannotationClass): 返回改程序元素上存在的、指定类型的注解,如果该类型注解不存在,则返回null。

方法2:Annotation[] getAnnotations():返回该程序元素上存在的所有注解。

方法3:boolean is AnnotationPresent(Class annotationClass):判断该程序元素上是否包含指定类型的注解,存在则返回true,否则返回false.

方法4:Annotation[] getDeclaredAnnotations():返回直接存在于此元素上的所有注释。与此接口中的其他方法不同,该方法将忽略继承的注释。(如果没有注释直接存在于此元素上,则返回长度为零的一个数组。)该方法的调用者可以随意修改返回的数组;这不会对其他调用者返回的数组产生任何影响。

使用注解去定义网络框架(使用注解传递一个值)

例子

//注解的范围

@Target(ElementType.Method)

//注解存在的范畴

@Retention(RetentionPolicy.RUNTIME)

//在文档中显示

@Ducumented

public @interface Request{

enum Method(POST,GET) default Method.GET    ;

{

动态代理

根据编译时期和运行期,分为动态代理以及静态代理

静态代理是在编译时期就存在的代理类

动态代理是在运行时期才存在的代理类

动态代理实现步骤(增强功能,过滤方法)

定义接口

接口的实现类

定义委托类和代理类的桥梁

生成代理类

调用代理类中的方法

原理

A:例子中的学生是委托类(实现了People接口的类)

B:proxyInstance是代理类(实现了People接口),调用所有的方法,用Proxy去创建这个类

C:studentHandler(实现InvocationHandler,定义了代理规则,传进去了委托类)

依赖注入

解决耦合性问题以及方便进行我们的单元测试

主要是采用了注解加上反射

dragger2,依赖注入的框架

首先我们的依赖注入是注解以反射去实现的,主要的功能是为了解耦,也就是接触一种依赖关系

其实这个是一个概念性的定义,具体的手段是不相同的,只能够接触内部的强依赖关系,让外部注入就可以了,可以通过构造方法从外部传入一个对象,解除内部创建这个对象的强依赖关系就可以了,当然也可以使用对注解的解析获取到数据对我们的成员变量进行赋值,也可以称之为依赖注入,请读者务必理解这个概念本身所锁定的含义,而不要去记住各种各样的不同的例子,要找出这个方法存在的共同目的或者说共性,这样我们就能理解不同的依赖注入过程本身的需求了.

电子市场

1.讲的是思路和框架,而且不会去具体的代码实现以及布局文件的创建

2.做完整个项目可能要花点时间,特别服务端的搭建可能不是我们这群做客户端的人能够左右的

3.接口文档和美工的切图当然是必须的

4.我相信能够读到这里的人是真不容易,完成这种文章对于我来讲难度不大,还是想要带着大家去领略一下这种代码的框架,太多的细节可能只要肯下功夫调试和CV全部是可以完成的,对于模板代码也不需要太去计较内部的原理.

5.说实话初级的Android真的不好找工作,如果不是真的喜欢编程或者计算机中的思维模式,假设还有更好的选择,其实真的不要走编程这条路,你会扑空的.


面试题

最近公司想做太多的项目,而且想要敏捷开发,尽可能地缩短开发周期,这样就必须进行人才储备了,一开始我并未参与简历的筛选工作,可是人事这边找来让我面试的人太令我失望了,先不要说回答不上来我所提出的面试题,连最为基本的Android知识和技巧也说不出来,还有的时培训出来的,其实我不反感培训出来的人,可是底子也太差了,后来我自己要筛选简历,主要也是为了避免这种浪费时间的局面出现,另外就是我这边的开发任务比较急,我不想在招聘环节上浪费太多的时间,然后招到了我和一个学校出来的一个学弟(可能有的人会说我在偏袒自己的学弟,还真有),废话不多说,直接上我的面试流程。

1.上一家公司的android开发所承担的工作内容以及工作职务,以及离职的原因,主要是考察一下面试者在上一家公司中是否负责核心代码的编写,还有就是是否符合我们的工作定位。

2.知识技能的面试

Hashmap的两种遍历方式,何种遍历方式效率高,原因?

Volatile关键字的作用?

Dvm进程和Linux进程,应用程序的进程是否为同一个概念?

OnRebind()何时被执行,是在unbind()返回值为true时才会被执行到

关于热修复的了解,主要是就andfix说明一下

关于内存泄漏和内存溢出的概念,以及如何检测和预防

关于fagment的了解,以及设计一种链式的结构代替这种管理类

讨论一下你最熟悉的框架(最好能够说源码中的原理和流程细节)

3.讨论一下我们目前的项目的技术点,跟对方进行对接

4.日常性的聊天,告知对方是否适合通过第一轮的面试,是否合适在我们公司的发展。

上述面试题的答案我就不展开去分析,答案大家可以去查询一下,其实以上的知识点可能有点偏冷,对方如何答不上来我回继续询问简单的开发技巧,上次有一个人回答不上来Activity的声明周期,还跟我说这种声明周期根本就不重要,我的内心是崩溃的,我认为做技术还是要投缘的,最为基本的知识要懂,关键还是一个上手速度的问题,还有就是解决问题的思路,以及问题的关键点和原理所在的,也就是说要学会从策略层面到技术层面去分析我们的需求,而不是从技术层面本身去考虑问题(这样的程序员沟通是一个问题,其实程序员和其它相关人员的沟通成本是我们需要考虑到的一个因素),我认为技术层面无非就是接口问题,之后就是整个架构的逻辑架构,代码的策略,无非就是解耦合性,以及代码本身的模块化设计,碰到的问题能够速度地去查找到解决就可以了,下面还是提供一下经典的Android面试题目,说实话以前我特别熟悉Handler机制,基本倒背如流,现在也差不多忘了,不过浏览一下还是能想起,关键还是应用场景。

集合安全性问题

请问ArrayList、HashSet、HashMap是线程安全的?如果不是我们如何去做到集合的线程安全?

我们说以上的集合类型当然不是线程安全的,Vector和HashTable是线程安全的,内部的核心也是加上同步锁,也就是使用了关键字:synchronized。

当然我们也可以使用Collectionos中的方法将线程不安全的集合变成线程安全的集合:


方法调用

关键字volatile的作用

volatile关键词修饰的成员变量或者是类的静态成员变量被volatile修饰之后说明这个变量的值是不确定的,要从主存中获取或者说读取,也就是说会即时去访问被修改之后的值,防止出现多线程访问时出现的并发修改异常,也就是说会即时地去寄存器取值,一旦被修改之后,另一个线程在处理这个变量时会去取即时的值,而不是取原来的变量值。

线程并发修改异常产生的原因

每一个线程从堆内存中读取对象的成员变量时,我们说在该线程内部会创建一个该变量的副本,之后该线程修改的就是这个副本,当修改完毕之后或者是在该线程退出之前,我们说这个线程就通过当前这个副本变量的值去修改堆内存中的对象的成员变量,这也造成了一系列线程并发对成员变量进行修改时产生的异常。

IT专辑
Web note ad 1