android部分:
这里的android根据面试官的水平,面试官水平高的话,会问很多源码级的东西,并且问的非常细,这个就没办法了,不过相对来说还是占少数的,当然高大上的公司就必须做好心理准备了。现在来说一般比较常问的android技术点
首先从四大组件说起:
Activity:
生命周期:
activity三种状态:运行(运行在最前端)、停止(不可见,完全被覆盖)、暂停(可见,但前端还有其他activity)
生命周期相关的方法:onCreate-onStart-onResume-onPause-onStop-onDestory-onRestart
切换时如果要保存数据, 可以重写: onSaveInstanceState();
恢复数据时, 重写: onRestoreInstanceState();
启动模式:
1.standard:默认的,每次调用startActivity()启动时都会创建一个新的activity放在栈顶
2.singleTop:启动Activity时,指定Activity不在任务栈栈顶就创建,如在栈顶,则不会创建,会调用onNewInstance(),复用已经存在的实例。
3.singleTask:在任务栈里面只允许一个实例,如果启动activity不存在就创建,如果存在直接跳转到指定Activity所在位置。
4.singleInstance:开启一个新的任务栈来存放这个Activity的实例,此模式开启的Activity是运行在自己单独的任务栈中的。
2.BroadcastReceiver广播接收者
有序广播用过调用abortBroadcast()方法来中断,接收者之间可以传递数据
动态注册广播register,取消unregister
广播接收者打开Activity,需要设置一下Intent.FLAG_ACTIVITY_NEW_TASK
应为广播接收者是没有Activity任务栈的
sendOrderBroadcast()发送有序广播
1)静态注册:在AndroidManifest.xml注册,android不能自动销毁广播接收器,也就是说当应用程序关闭后,还是会接收广播。
2)动态注册:在代码中通过registerReceiver()手工注册.当程序关闭时,该接收器也会随之销毁。当然,也可手工调用unregisterReceiver()进行销毁。
3.Service服务
Service中的生命周期方法(Context调用执行):
1)startService() 如果没创建就先onCreate()再startCommand(), 如果已创建就只执行startCommand();
2)stopService() 执行onDestroy()
3)bindService() 如果没有创建就先onCreate()再onBind()
4)unbindService() 如果服务是在绑定时启动的, 先执行onUnbind()再执行onDestroy(). 如果服务在绑定前已启动, 那么只执行onUnbind();
3、开启服务的2种方式
2种不同开启方式的区别:
1)startService:
开启服务,服务一旦开启,就长期就后台运行,即使调用者退出来,服务还会长期运行;
资源不足时,被杀死,资源足够时,又会复活;
2)bindService:
绑定服务,绑定服务的生命周期会跟调用者关联起来,调用者退出,服务也会跟着销毁;
通过绑定服务,可以间接的调用服务里面的方法(onBind返回IBinder);
4、服务混合调用生命周期
一般的调用顺序:
①、start -> stop 开启 –> 结束
②、bind -> unbind 绑定(服务开启) -> 解绑(服务结束)
混合调用:
①、start –> bind -> stop->unbind->ondestroy 通常不会使用这种模式
开启 –> 绑定 –> 结束(服务停不了)->解除绑定(服务才可停掉)
②、start –> bind -> unbind -> stop 经常使用
开启 –> 绑定 –> 解绑(服务继续运行)-> stop(不用时,再停止服务)
这样保证了服务长期后台运行,又可以调用服务中的方法
4、内容提供者
这个问的非常少,但是一旦问道还是要知道怎么回事的。
ContentProvider:
在数据库中有对应的增删改查的方法,如果要让别的应用程序访问,需要有一个路径uri
通过content:路径对外暴露,uri写法:content://主机名/表名
1)ContentProvider:内容提供者
把一个应用程序的私有数据(如数据库)信息暴露给别的应用程序,让别的应用程序可以访问;
在数据库中有对应的增删改查的方法,如果要让别的应用程序访问,需要有一个路径uri:
通过content:// 路径对外暴露,uri写法:content://主机名/表名
2)ContentResolver:内容解析者
根据内容提供者的路径,对数据进行操作(crud);
3)ContentObserver:内容观察者
可以理解成android系统包装好的回调,数据发送变化时,会执行回调中的方法;
ContentResolver发送通知,ContentObserver监听通知;
当A的数据发生变化的时候,A就会显示的通知一个内容观察者,不指定观察者,就会发消息给一个路径
ContentProvider和sql的实现上的区别:
1,Contentprovider屏蔽了数据存储的细节,内部实现对用户完全透明,用户只需要i、关心操作数据的uri就可以了,ContentProvider可以实现不同app之间的共享
2,Sql也有增删改查的方法,但是sql只能查询本应用下的数据库。而Comtentprovider还可以去增删改查本地文件.xml文件的读取等
异步加载网络数据(AsyncTask)
AsyncTask类,这个类中的任务是运行在后台线程中的,并可以将结果放在UI线程中去处理
它定义了三种泛型,分别是Params、Progress和Result,分别表示请求的参数、任务的进度和获取的结果数据
执行过程:1.onPreExecte():执行在主线程。这步操作是用于准备好任务的,作为任务加载的准备工作。建议在这个方法中弹出一个提示框。
2.doInBackground():执行在子线程中。需要将请求的参数传递进来,发送给服务器,并将获取到的数据返回,数据回传给下一步的onProgressUpdate()中进行进度更新。
3.onProgressUpdate():进度更新
4.onPostExectue():界面更新
实现原理:
1.线程池的创建:默认创建一个线程池ThreadPoolExecutor,并默认创建出5个线程放入到线程池中,最多可放128个线程,且这个线程池是公共的唯一一份。
2.任务的执行:执行run方法,执行完run方法后,会调用scheduleNext()不断的从双端队列中轮询,获取下一个任务并继续放到一个子线程中执行,直到异步任务执行完毕。
3.消息的处理:在AsyncTask中维护了一个InternalHandler的类,这个类是继承Handler的,获取的数据是通过handler进行处理和发送的。其中handleMessage方法中,将消息传递给onProgressUpdate()进行进度的更新,也可以将结果发送到主线程中,进行界面的更新了。
LisView优化:
ListView如何提高其效率
1.复用ContentView
2.自定义静态类viewholder
3.使用分页加载
4.使用weakReference引用ImageView对象
listView可以显示多种类型的条目:
Listview显示的每个条目都是通过baseAdapter的getView来展示的,理论上我们完全可以让每个条目都是不同类型的view,除此之外adapter还提供了getViewTypeCount()和getItemViewType(int position)两个方法。在getview方法中我们可以根据不同的viewtype加载不同的布局文件。
ListView中的数据分批及分页加载:
设置ListView的滚动监听器:setOnScrollListener
1.在监听器中有两个方法:滚动状态发生变化的方法onScrollStateChanged和listView被滚动时调用的方法
2.在滚动状态发生改变的方法有三个状态:触摸滑动、惯性滚动、静止状态
3.对不同的状态进行处理:分批加载数据,只关心静止状态:如果最后一个可见条目就是数据适
配器里的最后一个,此时可加载更多的数据。
ListView图片优化
1.不要直接拿路径去循环decodeFile();使用Option保存图片大小,不要加载图片到内存
2.拿到图片一定要经过边界压缩
3.在listView去图片是也不要直接拿个路径去取图片,而是以WeakReference代替强引用
4.在getView中做图片转换时,产生的中间变量一定及时释放
异步加载图片基本思想:
1.先从内存缓存中获取图片显示(内存缓冲)
2.获取不到的话从SD卡里获取(SD卡缓存)
3.都获取不到的话从网络下载图片并保存到SD卡同时加入内存并显示
图片错位问题:
本质是因为listView使用了缓存convertView。可见则显示,不可见则不显示。在imageLoader里有个imageViews的map对象,用于保存当前显示区域图像对应的url集,在显示前判断处理一下即可。
内存缓冲机制:
首先限制内存图片缓冲的堆内存大小,每次有图片往缓存里加时判断是否超过限制大小,超过的话取最少使用的图片并将其移除
二级缓存:从LinkedHashMap里移除的缓存放在SoftReference里
LinkedHashMap缓存在没有移除出去之前是不会被GC回收的,而SoftRefernce里的图片缓存在没有其他引用保存时随时都会被GC回收。
ListView的其他优化:
1.尽量避免在BaseAdapter中使用static来定义全局静态变量
2.尽量使用getApplicationContext
3.尽量避免在ListView适配器中使用线程
ScrollView和ListView的冲突问题:
在ScrollView添加ListView会导致listView控件显示不全,两个控件的滚动事件冲突导致。通过listView中的item数量去计算listView的显示高度。
熟悉XML/Json解析数据,以及数据存储方式
数据存储方式包括:File、SharedPreference、XML/Json、数据库、网络
XML/Json解析数据
Handler机制和事件分发机制
Message:消息,由MessageQueue统一列队,终由Handler处理
Handler:处理者,负责Message发送消息及处理。Handler通过与Looper进行沟通,从而使用Handler时,需要实现handlerMessage方法来对特定的Message进行处理
MessageQuene:消息队列,用来存放Handler发送过来的消息,按照先入先出规则执行。
Looper:消息泵,不断从MessageQueue中取出Message执行。因此,一个线程中的MessaeQueue需要一个Looper进行管理。
耗时操作,比如网络请求、文件处理、多媒体处理需要在子线程中操作,手机显示的刷新频率60Hz,一秒钟刷新60次,没16.7毫秒刷新一次,为了不丢帧,主线程处理代码最好不要超过16毫秒。
Handler消息机制
在主线程中 Android 默认已经调用了 Looper.preper()方法,调用该方法的目的是在 Looper 中创建MessageQueue 成员变量并把 Looper 对象绑定到当前线程中。当调用 Handler 的 sendMessage(对象)方法的时
候就将 Message 对象添加到了 Looper 创建的 MessageQueue 队列中,同时给 Message 指定了 target 对象,其实这个 target 对象就是 Handler 对象。主线程默认执行了 Looper.looper()方法,该方法从 Looper 的成员变量MessageQueue 中取出 Message,然后调用 Message 的 target 对象的 handleMessage()方法。这样就完成了整个消息机制。
事件分发机制
onTouch和onTouchEvent
这两个方法都是在View的dispathTouchEvent中调用的,onTouch优先于onTouchEvent执行,在onTouch中返回true将事件消费掉,onTouchEvent将不会再执行。
依次下发,下发的过程是调用View的dispatchTouchEvent方法实现的。简单来说,就是viewgroup遍历包含者的子view,调用每个View的dispatchTouchEvent方法,而当子view为viewgroup时,又会调用viewgroup的dispatchTouchEvent方法继续调用内部的view的dispatchTouchEvent方法。
touch事件分发有两个主角:viewGroup和view。
viewgroup包含onInterceptTouchEvent、dispatchTouchEvent、onTouchEvent
view包含dispatchTouchEvent、onTouchEvent两个
onInterceptTouchEvent有两个作用:1.拦截down事件的分发 2.中止Up和move事件向目标view传递,使得目标view所在的viewgroup捕获up和move事件
触摸事件是有action_Down、Action_Move、Action_Up组成,其中一次完整的触
摸事件中,Down和Up都只有一个,Move有若干个,可以为0个。
自定义控件
1.自定义组合控件
声明一个view对象,继承相对布局,或者线性布局或者其他的viewGroup
在自定义的view对象里面重写它的构造方法,在构造方法里面把布局初始化
根据业务需要添加一些api方法,扩展自定义组合控件
在布局文件里面可以自定义一些属性
声明自定义属性的命名空间
在res目录下的values目录下创建attrs.xml的文件声明我们写的属性
在布局文件中写自定义的属性
使用自定义的属性
2.view的绘制过程
mesarue()过程:为整个view树计算实际的大小
view : mMeasuredHeight高和mMeasureWidth()宽
viewGroup:重写onMeasure()方法,遍历measure()过程,通过调用父类ViewGroup类里面的measureChildWithMargins()方法去实现
layout()根据子视图的大小以及布局参数将view树放到合适的位置上
view: 设置该view视图位于父视图的坐标抽即mLeft,mTop,mLeft,mBottom,回调onLayout方法
viewGroup:遍历每个子视图childView,调用该子视图的layout()方法去设置它的坐标值
3.draw()绘制过程
viewRoot对象的performTraversals()方法调用draw()方法发起绘制view树不需要全部重绘只需要绘制需要绘制的部分
这个控件中,父视图使用unspecified dimensions来将它的每个子视图都测量一次来算出它们到底需要多大尺寸,而这些子视图没被限制的尺寸的和太大或太小,所以会用精确数值再次调用measure()(也就是说,如果子视图不满意它们获得的区域大小,那么父视图将会干涉并设置第二次测量规则)。其中measure()方法会调用onMeasure()方法。
代码中,由于把每行剩余空间重新分配,会调用了requestLayout()方法,这个方法又会导致measure()和onLayout()方法的再次调用。
最后你会发现 onMeasure()方法调用了 1次*2*2=4次 onLayout()方法调用了 1次*2 =2次
谈一谈android的安全机制
1,在安卓是有文件权限的控制,在清单文件声明
2,每个android中每个应用都有自己的/data/data/包名 文件夹,该文件夹只能该应用访问,而其他应用则无权访问
3,Android的代码混淆保护了开发者的劳动成果
多线程断点续传下载
多线程下载的实现过程:
1,首先得到下载文件的长度,然后设置本地文件的长度。
HttpURLConnection.getContentLength();
RandomAccessFile file = new RandomAccessFile(“FeiQ.exe”,”rwd”);
file.setLength(filesize); //设置本地文件的长度
2,根据文件长度和线程数计算每条线程下载的数据长度和下载位置。
如:文件的长度为6M,线程数为3,那么,每条线程下载的数据长度为2M,每条线程开始下载的位置如图所示。
3,使用Http的Range头字段指定每条线程从文件的什么位置开始下载,下载到什么位置为止.
如:指定从文件的2M位置开始下载,下载到位置4M为止,代码如下:
HttpURLConnection.setRequestProperty(“Range”, “bytes=2097152-4194303”);
4,保存文件
使用RandomAccessFile类指定每条线程从本地文件的什么位置开始写入数据。
RandomAccessFile threadfile = new RandomAccessFile(“QQWubiSetup.exe “,”rwd”);
threadfile.seek(2097152); //从文件的什么位置开始写入数据
数据库操作
在Android系统,提供了一个SQLiteOpenHelper抽象类,该类用于对数据库版本进行管理.该类中常用的方法:
onCreate 数据库创建时执行(第一次连接获取数据库对象时执行)
onUpgrade 数据库更新时执行(版本号改变时执行)
onOpen 数据库每次打开时执行(每次打开数据库时调用,在 onCreate,onUpgrade方法之后)
Android提供了一个名为SQLiteDatabase的类,该类封装了一些操作数据库的API,使用该类可以完成对数据进行添加(Create)、查询(Retrieve)、更新(Update)和删除(Delete)操作(这些操作简称为CRUD)。对SQLiteDatabase的学习,我们应该重点掌握execSQL()和rawQuery()方法。 execSQL()方法可以执行insert、delete、update和CREATE TABLE之类有更改行为的SQL语句; rawQuery()方法用于执行select语句。
aidl:接口声明语言,跨进程通信
bindService有一个ServiceConnec接口覆写onServiceConnect方法,把第二个参数IBinder对象强制转换为aidl中的接口类
- 写服务类
- 定义一个接口文件, 声明一个方法forwardPayMoney方法, 把后缀名修改为.aidl, 并且把修饰词去掉.
- 在服务中定义一个内部类MyBinder, 继承Stub类, 并且把抽象方法forwardPayMoney实现了.
- 在onBind方法中把第二部定义的内部类对象MyBinder返回.
- 另一个程序的Activity
- 使用隐式的方式绑定服务, 传递过去一个连接桥对象.
- 把服务程序中的aidl文件拷贝当前工程中, 包名要保留一致.
- 在连接桥对象中的onServiceConnected方法中, 把IBinder对象转换成aidl接口对象
mAlipayRemoteService = Stub.asInterface(service); - 使用aidl接口对象, 调用接口中的抽象方法, 实际上会调用到远程服务中内部类中的forwardPayMoney方法.
android中的动画
1.View Animation: 视图动画/ Frame动画/属性动画:这种动画是可扩展的,
图片处理框架
imageLoader:imageLoaderEngine,cache及
imageDownloader,imageDecoder,bitmaodisplayer,bitmapProcessor五大模块
简单来说就是imageLoader收到加载及显示图片的任务,并将它交给
imageLoaderEngine,ImageLoaderEngine分发任务到具体线程池去执行,任务通过cache及imageDownloder获取图片。
优点:1,支持下载监听2,可以在view滚动中暂停图片加载3.默认实现多种内存缓存算法4.支持本地缓存文件名规则定义
picasso
这个库分为dispatcher/requestHandler及Downloader,picassoDrawable等模块
picasso收到加载及显示图片的任务,创建Request并将他交给
Dispatcher,dispatcher分发任务到具体的requesthandler,任务memoryCache及Handler获取图片,图片获取成功后通过picassoDrawable显示到Target中
优点:1.自带统计监控功能2.支持优先级处理3.支持延迟加载4.支持飞行模式、并发线程数根据网络类型而变
图片缓存处理:
LruCache类:主要算法原理是把最近使用的对象用强引用存储在在LinkedHashMap中,并且把最近最少使用的对象在缓存值达到预定值之前从内存中移除。
内存溢出原因:
1.资源释放问题
2.对象内存过大
3.static
4.线程导致内存溢出
图片占用进程的内存算法简介
android中处理图片的基础类是Bitmap,顾名思义,就是位图。占用内存的算法如下:
图片的width*height*Config。
如果Config设置为ARGB_8888,那么上面的Config就是4。一张480*320的图片占用的内存就是480*320*4 byte。
在默认情况下android进程的内存占用量为16M,因为Bitmap除了java中持有数据外,底层C++的 skia图形库还会持有一个SKBitmap对象,因此一般图片占用内存推荐大小应该不超过8M。这个可以调整,编译源代码时可以设置参数。
Activity的启动与生命周期的监控
应用程序被开启后,是需要开启并创建Activity,加载相应的view,从而展示出应用程序
1、Activity是通过startActivity开启起来的,startActivity是由有Context调用的,其具体的实现类是ContextImpl
在ContextImpl中的startActivity方法中,会调用ActivityThread的相关方法【mMainThread.getInstrumentation().execStartActivity()】;可以追溯到Instrumentation这个类,其中的execStartActivity()的方法中实现了startActivity的调用:ActivityManagerNative.getDefault().startActivity,由此可以看出是底层进行处理。
2、ActivityMonitor监控Activity
当Activity实例创建的时候,就会给Activity配置一个监视器ActivityMonitor,监控Activity的声明周期:
在Instrumentation的execStartActivity()的方法中,上来先判断ActivityMonitor是否为null:在第一次开启Activity的时候,ActivityMonitor还是null的,就会调用ActivityManagerNative.getDefault().startActivity(…..),是在操作native底层的信息,从而执行startActivity,再去开启一个Activity。
简单来说,就是通过调用JNI,调用startActivity方法,开启Activity;创建好了之后,随即也创建好了Activity的监视器ActivityMonitor
3、在ActivityMonitor中就有Activity各种生命周期的监控
①、在newActivity方法中:
可以通过拿到Activity的字节码,创建一个Activity,并将这个Activity返回
还会调用attach方法,传入ActivityThread的线程
②、在各种生命周期的方法中,调用activity的各自的生命周期的方法
总结:
1、通过PackageManagerService将所有用到的资源加载进内存中
2、在Launcher中,将view等控件加载到ViewGroup中,点击每个item会有相应的操作
3、在公开的文档中是找不到具体调用startActivity的类的,而是由系统完成调用的,实现了Activity的启动
实际就是通过Context的实现类ContextImpl进行调用的,一步步转到底层(ActivityManagerNative)实现调用
4、另一个重要的类就是ActivityMonitor,监控Activity生命周期的;在其newActivity方法中创建了Activity,并调用了attach方法;
也就是说当一个Activity被创建的时候,就会绑定一个ActivityMonitor,用来监控Activity的生命周期
沉浸式状态栏实现思路
首先看下第一种方式
系统的方式沉浸式状态栏实现
步奏一
//当系统版本为4.4或者4.4以上时可以使用沉浸式状态栏
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
//透明状态栏
getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
//透明导航栏
getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION);
}
步奏二
布局加入: android:fitsSystemWindows=”true” android:clipToPadding=”true”
我们看下activity和布局文件
FirstActivity.java:
/**
* 沉浸式状态栏
*/
private void initState() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
//透明状态栏
getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
//透明导航栏
getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION);
}
}
activity_first.xml: