Android--整理一些面试题

Activity生命周期?保存activity的一些信息在哪个生命周期方法中?

生命周期函数 调用情景
oncreate() activity创建调用
onstart() 当前activity是可见状态,没有焦点,不能交互,一般可在当前方法做一些动画的初始化操作
onResume() 当前activity状态属于运行状态 (Running),可与用户进行交互
onPause() 当另外一个activity覆盖当前的acitivty时,当前activity是可见的,但不能与用户交互状态
onStop() 此时activity不可见,系统内存紧张的情况下,有可能会被回收。所以一般在当前方法可做资源回收
onDestory() activity即将被销毁
onRestart() 再次进入到当前activity的时候调用

横竖屏切换activity生命周期?
1、AndroidManifest.xml不设置Activity的android:configChanges时 或 设置Activity的android:configChanges="orientation"时,切屏会重新调用各个生命周期。生命周期如下:
onSaveInstanceState() -onPause() -onStop() -onDestory() -onCreate() -onStart() -onRestoreInstanceState() -onResume()
2、设置Activity的android:configChanges="orientation|screenSize"时,切屏不会重新调用各个生命周期,只会执行onConfigurationChanged() 方法。


说下Activity 的四种启动模式、应用场景 ?

standard 标准模式 : 每次启动一个 Activity 都会重新创建一个新的实例,不管这个实例是否已经存在,此模式的 Activity 默认会进入启动它的 Activity 所属的任务栈中;
singleTop 栈顶复用模式 : 如果新 Activity 已经位于任务栈的栈顶,那么此 Activity 不会被重新创建,同时会回调 onNewIntent方法,如果新 Activity 实例已经存在但不在栈顶,那么Activity 依然会被重新创建;
singleTask 栈内复用模式 : 只要 Activity 在一个任务栈中存在,那么多次启动此 Activity 都不会重新创建实例,并回调onNewIntent 方法,此模式启动 Activity A,系统首先会寻找是否存在 A 想要的任务栈,如果不存在,就会重新创建一个任务栈,然后把创建好 A 的实例放到栈中;
singleInstance单实例模式 : 这是一种加强的 singleTask 模式,具有此种模式的 Activity 只能单独地位于一个任务栈中,且此任务栈中只有唯一一个实例


说下 Activity 跟 跟 window , view 之间的关系?

Activity 创建时通过 attach()初始化了一个 Window 也就是PhoneWindow,一个 PhoneWindow 持有一个DecorView 的实例,DecorView 本身是一个 FrameLayout,继承于 View,Activty 通过setContentView 将xml 布局控件不断 addView()添加到 View 中,最终显示到 Window 于我们交互


Activity之间的数据通信方式

  • Intent
  • 借助类的静态变量
  • 借助全局变量/Application
  • 借助外部工具
    – 借助SharedPreference
    – 使用Android数据库SQLite
    – 赤裸裸的使用File
    – Android剪切板
  • 借助Service


Fragment中add与replace的区别?

add不会重新初始化fragment,replace每次都会;
添加相同的fragment时,replace不会有任何变化,add会报IllegalStateException 异常;
replace 先 remove 掉相同 id 的所有 fragment,然后在add 当前的这个 fragment,而 add 是覆盖前一个fragment。所以如果使用 add 一般会伴随 hide()和show(),避免布局重叠;
使用 add,如果应用放在后台,或以其他方式被系统销毁,再打开时,hide()中引用的 fragment 会销毁,所以依然会出现布局重叠 bug,可以使用 replace 或使用 add时,添加一个 tag 参数


Activity与Fragment之间通信

Handler
广播
接口
EventBus


service生命周期

生命周期 调用情景 工作内容
onCreate() 第一次创建时候调用
onStartComand() 服务启动时调用 一些数据加载工作
onBind() 服务被绑定时调用
onUnBind() 服务被解绑时调用
onDestroy() 服务停止时调用

startService()
通过这种方式调用startService,onCreate()只会被调用一次,多次调用startSercie会多次执行onStartCommand()和onStart()方法。如果外部没有调用stopService()或stopSelf()方法,service会一直运行。
bindService()
如果该服务之前还没创建,系统回调顺序为onCreate()→onBind()。如果调用bindService()方法前服务已经被绑定,多次调用bindService()方法不会多次创建服务及绑定。如果调用者希望与正在绑定的服务解除绑定,可以调用unbindService()方法,回调顺序为onUnbind()→onDestroy()。
注意:
Service默认并不会运行在子线程中,也不运行在一个独立的进程中,它同样执行在主线程中(UI线程)。不要在Service里执行耗时操作,除非手动打开一个子线程,否则有可能出现主线程被阻塞(ANR)的情况。

ActivityManagerService是Android中最核心的服务 ,负责系统中四大组件的启动、切换、调度及应用进程的管理和调度等工作,其职责与操作系统中的进程管理和调度模块类似。


IntentService和Service有什么区别?

Service不是独立的进程,也不是独立的线程,它是依赖于应用程序的主线程(比喻成没有界面的activity),也就是说,在更多时候不建议在Service中编写耗时的逻辑和操作,否则会引起ANR。
IntentService是Service的子类,IntentService在执行onCreate操作的时候,内部开了一个线程,去你执行你的耗时操作。通过Handler looper message的方式实现了一个多线程的操作,同时耗时操作也可以被这个线程管理和执行,同时不会产生ANR的情况。


Activity如何与Service通信

通过Binder实现通信:
Activity调用bindService(Intent service, ServiceConnection conn, int flags)方法,绑定一个继承Service的引用对象MsgService。通过实例化ServiceConnection接口内部类监听的方法获取MsgService中的Binder对象。如果想实现主动通知Activity的,还可以在MsgService中添加回调方法。
通过Broadcast实现通信:
Activity调用registerReceive(BroadcastReceiver recevier, IntentFilter filter)注册广播接收器,通过startService(Service service)启动一个集成Service的应用对象MsgService,之后MsgService可通过sendBoadcast(Intent intent)更新Actvity内容信息。


广播类型

普通广播
开发者自身定义 intent的广播(最常用),所有的广播接收器几乎会在同一时刻接受到此广播信息,接受的先后顺序随机;
有序广播
发送出去的广播被广播接收者按照先后顺序接收,同一时刻只会有一个广播接收器能够收到这条广播消息,当这个广播接收器中的逻辑执行完毕后,广播才会继续传递,且优先级(priority)高的广播接收器会先收到广播消息。有序广播可以被接收器截断使得后面的接收器无法收到它;
本地广播
仅在自己的应用内发送接收广播,也就是只有自己的应用能收到,数据更加安全,效率更高,但只能采用动态注册的方式;
粘性广播
这种广播会一直滞留,当有匹配该广播的接收器被注册后,该接收器就会收到此条广播;


广播发送和接收的原理

1.继承BroadcastReceiver,重写onReceive()方法。

2.通过Binder机制向ActivityManagerService注册广播。

3.通过Binder机制向ActivityMangerService发送广播。

4.ActivityManagerService查找符合相应条件的广播(IntentFilter/Permission)的BroadcastReceiver,将广播发送到BroadcastReceiver所在的消息队列中。

5.BroadcastReceiver所在消息队列拿到此广播后,回调它的onReceive()方法。


广播有几种创建方式,有什么区别?

两种,动态注册静态注册(Android8.0以后不能静态注册广播,官方说法是为了省电),区别:
动态注册的广播永远要快于静态注册的广播,不管静态注册的优先级设置的多高,不管动态注册的优先级有多低。

动态注册广播不是常驻型广播 ,也就是说广播跟随activity的生命周期。注意: 在activity调用ondestory(),移除广播接收器。静态注册是常驻型 (不能自动销毁),也就是说当应用程序关闭后,如果有信息广播来,程序也会被系统调用自动运行。

在同一个优先级下,谁先启动的快,谁将先接收到广播。


BroadcastReceiver,LocalBroadcastReceiver 区别
1.应用场景不同
BroadcastReceiver用于应用之间的传递消息;而LocalBroadcastManager用于应用内部传递消息,比broadcastReceiver更加高效。
2.使用安全性不同
BroadcastReceiver使用的Content API,所以本质上它是跨应用的,所以在使用它时必须要考虑到不要被别的应用滥用;LocalBroadcastManager不需要考虑安全问题,因为它只在应用内部有效。


ContentProvider、ContentResolver、ContentObserver之间的关系

ContentProvider :四大组件的内容提供者,主要用于对外提供数据
实现各个应用程序之间的(跨应用)数据共享,比如联系人应用中就使用了ContentProvider,你在自己的应用中可以读取和修改联系人的数据,不过需要获得相应的权限。其实它也只是一个中间人,真正的数据源是文件或者SQLite等
一个应用实现ContentProvider来提供内容给别的应用来操作,通过ContentResolver来操作别的应用数据,当然在自己的应用中也可以。
ContentResolver:内容解析者,用于获取内容提供者提供的数据
ContentResolver.notifyChange(uri)发出消息
ContentObserver:内容监听器,可以监听数据的改变状态
目的是观察(捕捉)特定Uri引起的数据库的变化,继而做一些相应的处理,它类似于数据库技术中的触发器(Trigger),当ContentObserver所观察的Uri发生变化时,便会触发它。触发器分为表触发器、行触发器,相应地ContentObsever也分为表ContentObserver、行ContentObserver,当然这是与它所监听的Uri MIME Type有关的
ContentResolver.registerContentObserver()监听消息
联系:使用ContentResolver来获取ContentProvider提供的数据,同时注册ContentObserver监听Uri数据的变化


Intent概述及使用

Android中的Intent是一个非常重要且常用的类,可以用来在一个组件中启动App中的另一个组件或者是启动另一个App的组件,这里所说的组件指的是Activity、Service以及Broadcast。
有两种类型的Intent: explicit(显式) 的和 implict(隐式) 的。

intent主要有以下几种重要用途:

  1. 启动Activity:可以将Intent对象传递给startActivity()方法或startActivityForResult()方法以启动一个Activity,该Intent对象包含了要启动的Activity的信息及其他必要的数据。
  2. 启动Service:可以将Intent对象传递给startService()方法或bindService()方法以启动一个Service,该Intent对象包含了要启动的Service的信息及其他必要的数据。关于使用startService()方法启动Service,可以参见博文《Android中startService的使用及Service生命周期》。关于使用bindService()方法启动Service,可以参见博文《Android中bindService的使用及Service生命周期》。
  3. 发送广播:广播是一种所有App都可以接收的信息。Android系统会发布各种类型的广播,比如发布开机广播或手机充电广播等。我们也可以给其他的App发送广播,可以将Intent对象传递给sendBroadcast()方法或sendOrderedBroadcast()方法或sendStickyBroadcast()方法以发送自定义广播。

Android Intent 传递数据大小限制:
Intent携带信息的大小其实是受Binder限制。数据以Parcel对象的形式存放在Binder传递缓存中。 如果数据或返回值比传递buffer大,则此次传递调用失败并抛出TransactionTooLargeException异常。

Binder传递缓存有一个限定大小,通常是1Mb。但同一个进程中所有的传输共享缓存空间。

多个地方在进行传输时,即时它们各自传输的数据不超出大小限制,TransactionTooLargeException异常也可能会被抛出。

在使用Intent传递数据时,1Mb并不是安全上限。因为Binder中可能正在处理其它的传输工作。 不同的机型和系统版本,这个上限值也可能会不同。

传输大量数据,可以考虑URL之类的方法。


数据类型

数值型 byte整数类型 8(1字节)
short整数类型 16(2字节)
ini整数类型 32(4字节)
long整数类型 64(8字节)
浮点型 float单精度浮点类型 32(4字节)
double双精度浮点类型 64(8字节)
字符型 char字符类型 16(2字节)
布尔类型 boolean布尔类型 8(1字节)

String在JAVA中不是基本数据类型,
toLowerCase() 方法用于将字符串中的大写字母转换成小写,
toUpperCase() 方法用于将字符串中的小写字母转换为大写。
基本数据类型= =比较的是值,非基本数据类型= =比较的是内存地址
equals比较的是内存地址


Android中的五大存储方式及其应用场景

SharedPreferences :轻量级存储,以键值对的形式存储在xml中,一般用来保存应用中的设置属性,适合保存少量的数据
文件存储 :存储多媒体文件, 文件缓存
Sqlite存储 :适合存储结构化数据
Content Provider :进程(应用程序)间共享数据的一种存储方式
网络存储 :xml,json格式等等,通过HttpUrlConnection,HttpClient,或者SOAP协议获取数据


Parcelable和Serializable的区别:

android自定义对象可序列化有两个选择一个是SerializableParcelable
所谓序列化就是将对象变成二进制流,便于存储和传输。

一、对象为什么需要序列化
1.永久性保存对象,保存对象的字节序列到本地文件。
2.通过序列化对象在网络中传递对象。
3.通过序列化对象在进程间传递对象。

二、当对象需要被序列化时如何选择所使用的接口
1.在使用内存的时候Parcelable比Serializable的性能高。
2.Serializable在序列化的时候会产生大量的临时变量,从而引起频繁的GC(内存回收)。
3.Parcelable不能使用在将对象存储在磁盘上这种情况,因为在外界的变化下Parcelable不能很好的保证数据的持续性。


跨进程通信的几种方式

Intent
ContentProvider
Broadcast
AIDL


String,StringBuffer,StringBuilder的区别

String是常量
StringBuffer是变量,线程安全
StringBuilder是变量,线程不安全,执行效率高

当字符赋值少使用String
字符赋值频繁使用StringBuilder
多个线程同步操作数据使用StringBuffer


View和SurfaceView的区别

View基于主线程刷新UI
SurfaceView子线程又可以刷新UI


View的绘制原理

measure----测量 :系统会先根据xml布局文件和代码中对控件属性的设置,来获取或者计算出每个View和ViewGrop的尺寸,并将这些尺寸保存下来。
Layout----布局 :根据测量出的结果以及对应的参数,来确定每一个控件应该显示的位置。
Draw----绘制 :确定好位置后,就将这些控件绘制到屏幕上。


View的事件分发

dispatchTouchEvent -> onTouch(setOnTouchListener) -> onTouchEvent -> onClick
onTouch和onTouchEvent的区别
两者都是在dispatchTouchEvent中调用的,onTouch优先于onTouchEvent,如果onTouch返回true,那么onTouchEvent则不执行,及onClick也不执行。
onInterceptTouchEvent()是ViewGroup独有的


View的分发机制,滑动冲突

View的事件传递顺序有3个重要的方法:
dispatchTouchEvent() 是否消耗了本次事件,
onInterceptTouchEvent() 是否拦截了本次事件,
onTouchEvent() 是否处理本次事件,

滑动冲突分为同方向滑动冲突、不同方向滑动冲突:
例如ScrollView和ListView,同方向滑动冲突,可以计算ListView高度而动态设置ListView的高度,ScrollView高度可变。
例如ViewPager和ListView,不同方向滑动冲突,一个是横向滑动一个是竖直滑动,不同方向滑动可以判断滑动的x,y轴是横向还是竖直滑动,如果判断得到是横向滑动,就拦截ListView的事件,竖则反之。


requestLayout、invalidate与postInvalidate区别

requestLayout
从方法名字可以知道,“请求布局”,那就是说,如果调用了这个方法,那么对于一个子View来说,应该会重新进行布局流程。但是,真实情况略有不同,如果子View调用了这个方法,其实会从View树重新进行一次测量、布局、绘制这三个流程,最终就会显示子View的最终情况。
invalidate
当子View调用了invalidate方法后,会为该View添加一个标记位,同时不断向父容器请求刷新,父容器通过计算得出自身需要重绘的区域,直到传递到ViewRootImpl中,最终触发performTraversals方法,进行开始View树重绘流程(只绘制需要重绘的视图)。
postInvalidate
postInvalidate是在非UI线程中调用,invalidate则是在UI线程中调用。

Hander原理

Handler,loop轮询检测发送消息到MessagerQuery,MessageQuery对Message入列,Handler回调方法处理消息,重写handMessage方法刷新ui


线程有几种状态,分别是哪些?(调用run()和调用start()的区别)

1、新建状态(new) :新创建一个线程对象。
2、就绪状态(Runnable) :线程对象创建后,其他线程调用了该对象的start()方法。该状态的线程位于“可运行线程池”中,变得可运行,只等待获取CPU的使用权。即在就绪状态的进程除CPU之外,其它的运行所需资源都已全部获得。
3、运行状态(Running) :就绪状态的线程获取了CPU,执行程序代码。
4、阻塞状态(Blocked) :阻塞状态是线程因为某种原因放弃CPU使用权,暂时停止运行。直到线程进入就绪状态,才有机会转到运行状态。
5、死亡状态(Dead) :线程执行完了或者因异常退出了run()方法,该线程结束生命周期。

Thread类中run()和start()方法的区别:
run() 方法:在线程内调用该Runnable对象的run()方法,可以重复多次调用;
start() 方法:启动一个线程,调用该Runnable对象的run()方法,不能多次启动一个线程;

wait和 sleep 的区别
wait 是Object的方法,是对象锁,锁定方法不让继续执行,执行notify方法后继续执行
sleep 是Thread的方法,sleep使线程睡眠,让出cpu,结束后自动继续执行


Android系统中的几种进程

五种进程:前台进程可见进程服务进程后台进程空进程
它们的回收顺序从先到后分别是:空进程,后台进程,服务进程,可见进程,前台进程。


用两种方式实现单例模式,要求线程安全

public class SingleTon (){
		private static SingleTon instance = new SingleTon();
		private SingleTon (){}
		public static Test getInstance(){
			return instance;
		}
}//饿汉式
public class SingleTon {
    private static SingleTon singleTon = null;
    private SingleTon() {}
    public static SingleTon getInstance() {
        if( singleTon == null ) {
            synchronized( SingleTon.class ) {
                if( singleTon == null ) {
                    singleTon = new SingleTon();
                }
            }
        }
        return singleTon;
    }
}//懒汉式


Android 系统的架构

android 的系统架构和其操作系统一样,采用了分层的架构。从架构图看, android 分为四个层,从高层到低层分别是应用程序层、应用程序框架层、系统运行库层和 linux 核心层。


如果防止过度绘制,如何做布局优化?

1.使用include复用布局文件。

2.使用merge标签避免嵌套布局。

3.使用stub标签仅在需要的时候在展示出来。


屏幕适配

屏幕分辨率
含义:手机在横向、纵向上的像素点数总和
单位:px(pixel),1px=1像素点(UI设计师的设计图会以px作为统一计量单位)
Android手机常见的分辨率:320x480、480x800、720x1280、1080x1920

屏幕像素密度
含义:每英寸的像素点数 单位:dpi(dots per ich)
假设设备内每英寸有160个像素,那么该设备的屏幕像素密度=160dpi

适配方法
1.支持各种屏幕尺寸: 使用wrap_content, match_parent, weight
2.使用相对布局,禁用绝对布局。
3.使用LinearLayout的weight属性
4.使用.9图片


怎样计算Bitmap的内存占用和Bitmap加载优化、如何在不压缩的情况下加载高清大图

影响内存大小的三要素:图片宽高色彩空间缩放比
Bitamp 占用内存大小 = 宽度像素 x (inTargetDensity / inDensity) x 高度像素 x (inTargetDensity / inDensity)x 一个像素所占的内存.

常见的Bitmap优化:
修改主动缩放比:目标是修改最终图片加载的宽高,进而优化内存占用。具体就是设置inSampleSize值,如在适当的View上缩放显示适合的bitmap,实现bitmap的高效加载(Glide图片框架就是这样,让显示组件View的宽高的参与缩放比计算)。
修改被动缩放比:理解了被动缩放比的计算,设备dpi层级和图片dpi层级关系。最好的情况就是同样的图片在drawable文件不同的dpi层级包中都有适应具体大小的图片(这样在运行不同设备时,被动缩放比都是1)。否则在设备没有直接对应的dpi的drawable文件时,会影响被动缩放比,而且大多数时候是导致图片放大,且导致内存增大二次方。如刚才的计算过程如果把图片从drawable-xxhdpi(480dpi)移到drawable文件包(默认160)中,同样的加载代码最后内存就会放大9倍。
修改色彩空间:在明确的不需要透明度的情况,使用RGB_565替换ARGB_8888,可以直接达到内存缩小一半的功效。缺点就是有限定条件。(从网络上的博文描述,不推荐使用ARGB_4444替换,因为这样的图片质量太差)

如何在不压缩的情况下加载高清大图:
使用BitmapRegionDecoder进行布局加载。


Android动画有几类,它们的特点和区别是什么?

帧动画(Frame Animation)
类似于一帧帧图片组成的电影,xml中多张图片组成,在UI线程中播放这个xml形成的动画。
补间动画(Tweened Animation)
分为四种形式,分别是 alpha(淡入淡出),translate(位移),scale(缩放大小),rotate(旋转)。补间动画的实现,一般会采用xml 文件的形式;当然也可以用Java代码直接实现。
属性动画(Property Animation)
这是3.0之后加入的动画,为了弥补前面两种动画的不足。属性动画可以实现很多数学函数的路径动画。属性动画的运行机制是通过不断地对值进行操作来实现的,而初始值和结束值之间的动画过渡就是由ValueAnimator这个类来负责计算的。它的内部使用一种时间循环的机制来计算值与值之间的动画过渡,我们只需要将初始值和结束值提供给ValueAnimator,并且告诉它动画所需运行的时长,那么ValueAnimator就会自动帮我们完成从初始值平滑地过渡到结束值这样的效果。除此之外,ValueAnimator还负责管理动画的播放次数、播放模式、以及对动画设置监听器等。


方法重载和覆盖的区别?(Overload与Override的区别)

重载 :方法名相同,参数不同;
重载返回类型不做限制;
重载用于同一个类的所有方法;
一个方法在所在的类中可以被重载多次

覆盖 :父类和子类有相同的方法和参数;
覆盖要求返回类型必须一致;
覆盖只能用于子类覆盖父类的方法;
父类的一个方法只能被子类覆盖一次


有没有遇到64k问题,为什么,如何解决?

在DEX文件中,method、field、class等的个数使用short类型来做索引,即两个字节(65535),method、field、class等均有此限制。

APK在安装过程中会调用dexopt将DEX文件优化成ODEX文件,dexopt使用LinearAlloc来存储应用信息,关于LinearAlloc缓冲区大小,不同的版本经历了4M/8M/16M的限制,超出
缓冲区时就会抛出INSTALL_FAILED_DEXOPT错误。

解决方案是Google的MultiDex方案


JVM(虚拟机)内存结构

Java对象实例化过程中,主要使用到虚拟机栈、Java堆和方法区。
Java文件经过编译之后首先会被加载到jvm方法区中,jvm方法区中很重的一个部分是运行时常量池,用以存储class文件类的版本、字段、方法、接口等描述信息和编译期间的常量和静态常量。


JVM基本结构

方法区 :用于存储类结构信息的地方,包括常量池、静态变量、构造函数等。

Java堆(heap) :存储Java实例或者对象的地方。这块是gc的主要区域。

Java栈(stack) :Java栈总是和线程关联的,每当创建一个线程时,JVM就会为这个线程创建一个对应的Java栈。在这个java栈中又会包含多个栈帧,每运行一个方法就创建一个栈帧,用于存储局部变量表、操作栈、方法返回值等。每一个方法从调用直至执行完成的过程,就对应一个栈帧在java栈中入栈到出栈的过程。所以java栈是线程私有的。

程序计数器 :用于保存当前线程执行的内存地址,由于JVM是多线程执行的,所以为了保证线程切换回来后还能恢复到原先状态,就需要一个独立的计数器,记录之前中断的地方,可见程序计数器也是线程私有的。
本地方法栈 :和Java栈的作用差不多,只不过是为JVM使用到的native方法服务的。


Java对象引用

强引用 :通常可以认为是通过new出来的对象,即使内存不足,GC进行垃圾收集的时候也不会主动回收。
Object obj = new Object();

软引用 :在内存不足的时候,GC进行垃圾收集的时候会被GC回收。
Object obj = new Object();
SoftReference softReference = new SoftReference<>(obj);

弱引用 :无论内存是否充足,GC进行垃圾收集的时候都会回收。
Object obj = new Object();
WeakReference weakReference = new WeakReference<>(obj);

虚引用 :和弱引用类似,主要区别在于虚引用必须和引用队列一起使用。
Object obj = new Object();
ReferenceQueue referenceQueue = new ReferenceQueue<>();
PhantomReference phantomReference = new PhantomReference<>(obj, referenceQueue);

引用队列:如果软引用和弱引用被GC回收,JVM就会把这个引用加到引用队列里,如果是虚引用,在回收前就会被加到引用队列里。


简述TCP,UDP,Socket

TCP是经过3次握手,4次挥手完成一串数据的传送
UDP是无连接的,知道IP地址和端口号,向其发送数据即可,不管数据是否发送成功
Socket是一种不同计算机,实时连接,比如说传送文件,即时通讯


简述JNI

java和c语言之间的桥梁,由于java是一种半解释语言,可以被反编译出来,一种重要涉及安全的代码就使用了C编程,再者很多底层功能调用C语言都实现了Java没必要重复造轮子,所以定义了JNI接口的实现


Java和JS的相互调用怎么实现,有做过什么优化吗?

jockeyjs:https://github.com/tcoulter/jockeyjs
对协议进行统一的封装和处理。


如何提交代码质量?

1.避免创建不必要的对象,尽可能避免频繁的创建临时对象,例如在for循环内,减少GC的次数。
如果有需要拼接的字符串,那么可以优先考虑使用StringBuffer或者StringBuilder来进行拼接,而不是加号连接符,因为使用加号连接符会创建多余的对象,拼接的字符串越长,加号连接符的性能越低。
当一个方法的返回值是String的时候,通常需要去判断一下这个String的作用是什么,如果明确知道调用方会将返回的String再进行拼接操作的话,可以考虑返回一个StringBuffer对象来代替,因为这样可以将一个对象的引用进行返回,而返回String的话就是创建了一个短生命周期的临时对象。
尽可能地少创建临时对象,越少的对象意味着越少的GC操作。

2.尽量使用基本数据类型代替引用数据类型。

3.静态方法调用效率高于动态方法,也可以避免创建额外对象。

4.对于基本数据类型和String类型的常量要使用static final修饰,这样常量会在dex文件的初始化器中进行初始化,使用的时候可以直接使用。

5.多使用系统API,例如数组拷贝System.arrayCopy()方法,要比我们用for循环效率快9倍以上,因为系统API很多都是通过底层的汇编模式执行的,效率比较高。

6.使用增强型for循环语法


APK打包流程

编译打包步骤:
1. 打包资源文件,生成R.java文件
打包资源的工具是aapt(The Android Asset Packaing Tool)(E:\Documents\Android\sdk\build-tools\25.0.0\aapt.exe)。

在这个过程中,项目中的AndroidManifest.xml文件和布局文件XML都会编译,然后生成相应的R.java,另外AndroidManifest.xml会被aapt编译成二进制。

存放在APP的res目录下的资源,该类资源在APP打包前大多会被编译,变成二进制文件,并会为每个该类文件赋予一个resource id。对于该类资源的访问,应用层代码则是通过resource id进行访问的。Android应用在编译过程中aapt工具会对资源文件进行编译,并生成一个resource.arsc文件,resource.arsc文件相当于一个文件索引表,记录了很多跟资源相关的信息。

2. 处理aidl文件,生成相应的Java文件
这一过程中使用到的工具是aidl(Android Interface Definition Language),即Android接口描述语言(E:\Documents\Android\sdk\build-tools\25.0.0\aidl.exe)。

aidl工具解析接口定义文件然后生成相应的Java代码接口供程序调用。如果在项目没有使用到aidl文件,则可以跳过这一步。

3. 编译项目源代码,生成class文件
项目中所有的Java代码,包括R.java和.aidl文件,都会变Java编译器(javac)编译成.class文件,生成的class文件位于工程中的bin/classes目录下。

4. 转换所有的class文件,生成classes.dex文件
dx工具生成可供Android系统Dalvik虚拟机执行的classes.dex文件,该工具位于(E:\Documents\Android\sdk\build-tools\25.0.0\dx.bat)。

任何第三方的libraries和.class文件都会被转换成.dex文件。dx工具的主要工作是将Java字节码转成成Dalvik字节码、压缩常量池、消除冗余信息等。

  1. 打包生成APK文件
    所有没有编译的资源,如images、assets目录下资源(该类文件是一些原始文件,APP打包时并不会对其进行编译,而是直接打包到APP中,对于这一类资源文件的访问,应用层代码需要通过文件名对其进行访问);编译过的资源和.dex文件都会被apkbuilder工具打包到最终的.apk文件中。

打包的工具apkbuilder位于 android-sdk/tools目录下。apkbuilder为一个脚本文件,实际调用的是(E:\Documents\Android\sdk\tools\lib)文件中的com.android.sdklib.build.ApkbuilderMain类。

  1. 对APK文件进行签名==
    一旦APK文件生成,它必须被签名才能被安装在设备上。

在开发过程中,主要用到的就是两种签名的keystore。一种是用于调试的debug.keystore,它主要用于调试,在Eclipse或者Android Studio中直接run以后跑在手机上的就是使用的debug.keystore。

另一种就是用于发布正式版本的keystore。

7. 对签名后的APK文件进行对齐处理
如果你发布的apk是正式版的话,就必须对APK进行对齐处理,用到的工具是zipalign(E:\Documents\Android\sdk\build-tools\25.0.0\zipalign.exe)

对齐的主要过程是将APK包中所有的资源文件距离文件起始偏移为4字节整数倍,这样通过内存映射访问apk文件时的速度会更快。对齐的作用就是减少运行时内存的使用。


了解插件化和热修复吗,它们有什么区别,理解它们的原理吗?

插件化:插件化是体现在功能拆分方面的,它将某个功能独立提取出来,独立开发,独立测试,再插入到主应用中。依次来较少主应用的规模。

热修复:热修复是体现在bug修复方面的,它实现的是不需要重新发版和重新安装,就可以去修复已知的bug。

热修复原理:利用PathClassLoader和DexClassLoader去加载与bug类同名的类,替换掉bug类,进而达到修复bug的目的,原理是在app打包的时候阻止类打上CLASS_ISPREVERIFIED标志,然后在热修复的时候动态改变BaseDexClassLoader对象间接引用的dexElements,这样才能抢先代替Bug类,完成系统不加载旧的Bug类.

插件化:插件化只是增肌新的功能类或者是资源文件,所以不涉及抢先加载旧的类这样的使命,就避过了阻止相关类去打上CLASS_ISPREVERIFIED标志和还有在热修复时动态改变BaseDexClassLoader对象间接引用的dexElements.

所以插件化比热修复简单,热修复是在插件化的基础上在进行替旧的Bug类


对设计模式的理解,简单说几种?

23种。
Builder模式(方便维护,使用者可以不用清楚内部构成情况就能直接调用方法;比如系统弹窗)
观察者模式(被观察者与观察者,只要有订阅关系,当被观察者发生改变时,就能通知观察者们;比如Event )
单例模式(一个进程/项目中只存在1个实例,为了节约内存资源)
工厂模式


MCV模式

MVC 是 Model,View,Controller 的缩写,从上图可以看出 MVC 包含三个部分:
模型( Model )对象:是应用程序的主体部分,所有的业务逻辑都应该写在该层。
视图( View )对象:是应用程序中负责生成用户界面的部分。也是在整个
MVC 架构中用户唯一可以看到的一层,接收用户的输入,显示处理结果。
控制器( Control )对象:是根据用户的输入,控制用户界面数据显示及更新
Model 对象状态的部分,控制器更重要的一种导航功能,想用用户出发的相
关事件,交给 M 层处理。

Android 鼓励弱耦合和组件的重用,在 Android 中 MVC 的具体体现如下:

  1. 视图层( view ) :一般采用 xml 文件进行界面的描述,使用的时候可以非常方便的引入,当然,如何你对 android 了解的比较的多了话,就一定可 以想到在 android 中也可以使用javascript+html 等的方式作为 view 层,当然这里需要进行 java 和 javascript 之间的通 信,幸运的是, android 提供了它们之间非常方便的通信实现。
  2. 控制层( controller ): android 的控制层的重 任通常落在了众多的 acitvity 的肩上,这句话也就暗含了不要在 acitivity 中写代码,要通过 activity 交割 model 业务逻辑层处理, 这样做的另外一个原因是 android 中的 acitivity 的响应时间是 5s ,如果耗时的操作放在这里,程序就很容易被回收掉。
  3. 模型层( model ): 对数据库的操作、对网络等的操作都应该在 model 里面处理,当然对业务计算等操作也是必须放在的该层的。


MVP模式

模型层(Model):主要是获取数据功能,业务逻辑和实体模型。

视图层(View):对应于Activity或Fragment,负责视图的部分展示和业务逻辑用户交互

控制层(Presenter):负责完成View层与Model层间的交互,通过P层来获取M层中数据后返回给V层,使得V层与M层间没有耦合。

在MVP中 ,Presenter层完全将View层和Model层进行了分离,
把主要程序逻辑放在Presenter层实现,Presenter与具体的View层(Activity)是没有直接的关联,是通过定义接口来进行交互的,
从而使得当View层(Activity)发生改变时,Persenter依然可以保持不变。
View层接口类只应该只有set/get方法,及一些界面显示内容和用户输入,除此之外不应该有多余的内容。
绝不允许View层直接访问Model层,这是与MVC最大区别之处,也是MVP核心优点。


MVC、MVP与MVVM之间的对比分析?

MVC:PC时代就有的架构方案,在Android上也是最早的方案,Activity/Fragment这些上帝角色既承担了V的角色,也承担了C的角色,小项目开发起来十分顺手,大项目就会遇到
耦合过重,Activity/Fragment类过大等问题。

MVP:为了解决MVC耦合过重的问题,MVP的核心思想就是提供一个Presenter将视图逻辑I和业务逻辑相分离,达到解耦的目的。

MVVM:使用ViewModel代替Presenter,实现数据与View的双向绑定,这套框架最早使用的data-binding将数据绑定到xml里,这么做在大规模应用的时候是不行的,不过数据绑定是

一个很有用的概念,后续Google又推出了ViewModel组件与LiveData组件。ViewModel组件规范了ViewModel所处的地位、生命周期、生产方式以及一个Activity下多个Fragment共享ViewModel数据的问题。LiveData组件则提供了在Java层面View订阅ViewModel数据源的实现方案。


内存泄露如何查看和解决

概念:有些对象只有有限的生命周期,当他们的任务完成之后,它们将被垃圾回收,如果在对象的生命周期本该结束的时候,这个对象还被一系列的引用,着就会导致内存泄露。

解决方法:使用开源框架LeakCanary检测针对性解决

常见的内存泄露有:
单例造成的内存泄露,例如单例中的Context生命周期大于本身Context生命周期
线程使用Hander造成的内存卸扣,当activity已经结束,线程依然在运行更新UI
非静态类使用静态变量导致无法回收释放造成泄露
WebView网页过多造成内存泄露
资源未关闭造成泄露,例如数据库使用完之后关闭连接


内存溢出

产生原因:
1.内存中加载的数据量过于庞大,如一次性从数据库中取出过多的数据
2.集合类中有对对象的引用,使用后未清空
3.代码中存在死循环或循环过多产生过多重复的实体对象
4.使用的第三方软件中的bug
5.启动参数内存值设定的过小

解决方案:
1.修改JVM启动参数,直接增加内存(-Xms -Xms参数不要忘记加)
2.检查错误日志查看OutOfMemory错误前是否有其他异常或错误
3.对代码进行分步分析,找出可能发生溢出的位置


怎样避免和解决ANR

出现的原因有三种:
a)KeyDispatchTimeout(5 seconds) 主要类型按键或触摸事件在特定时间内无响应
b)BroadcastTimeout(10 seconds) BoradcastReceiver在特定的时间内无法处理
c)ServiceTimeout(20 seconds) 小概率类型Service在特定的时间内无法处理完成

避免ANR最核心的一点就是在主线程减少耗时操作。通常需要从那个以下几个方案下手:
a)使用子线程处理耗时IO操作
b)降低子线程优先级,使用Thread或者HandlerThread时,调用Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND)设置优先级,否则仍然会降低程序响应,因为默认Thread的优先级和主线程相同
c)使用Handler处理子线程结果,而不是使用Thread.wait()或者Thread.sleep()来阻塞主线程
d)Activity的onCreate和onResume回调中尽量避免耗时的代码
e)BroadcastReceiver中onReceiver代码也要尽量减少耗时操作,建议使用intentService处理。intentService是一个异步的,会自动停止的服务,很好解决了传统的Service中处理完耗时操作忘记停止并销毁Service的问题


如何做性能优化?

1.节制的使用Service,当启动一个Service时,系统总是倾向于保留这个Service依赖的进程,这样会造成系统资源的浪费,可以使用IntentService,执行完成任务后会自动停止。

2.当界面不可见时释放内存,可以重写Activity的onTrimMemory()方法,然后监听TRIM_MEMORY_UI_HIDDEN这个级别,这个级别说明用户离开了页面,可以考虑释放内存和资源。

3.避免在Bitmap浪费过多的内存,使用压缩过的图片,也可以使用Fresco等库来优化对Bitmap显示的管理。

4.使用优化过的数据集合SparseArray代替HashMap,HashMap为每个键值都提供一个对象入口,使用SparseArray可以免去基本对象类型转换为引用数据类想的时间。


平时开发中设计到哪些性能优化,你是从哪些地方来优化,你是通过什么工具来分析的?

省电优化、内存优化、网络优化、图片优化、UI优化。
UI优化
(1)减少层级,合理使用 RelativeLayout 和 LinerLayout,合理使用Merge,Include。
(2)提高显示速度,使用 ViewStub,它是一个看不见的、不占布局位置、占用资源非常小的视图对象。
(3)布局复用,可以通过标签来提高复用。
(4)尽可能少用wrap_content,wrap_content 会增加布局 measure 时计算成本,在已知宽高为固定值时,不用wrap_content 。
(5)删除控件中无用的属性。
省电优化
(1)避免浮点运算。
(2)根据客户端图片的大小要求叫UI做相应大小的图提供给服务器,避免过大消耗更多流量和电量。
(3)不用的广播,服务记得及时关闭。
内存优化
(?)增加相应的判断,以及异常处理。
(?)避免在主线程做耗时操作。
(1)对象引用:强引用、软引用、弱引用、虚引用四种引用类型,根据业务需求合理使用不同,选择不同的引用类型。
(2)减少不必要的内存开销:注意自动装箱,增加内存复用,比如有效利用系统自带的资源、视图复用、对象池、Bitmap对象的复用。
(3)使用最优的数据类型:比如针对数据类容器结构,可以使用ArrayMap数据结构,避免使用枚举类型,使用缓存Lrucache等。
(4)图片内存优化:点9图减少图片大小以及可以设置位图规格,根据采样因子做压缩,用一些图片缓存方式对图片进行管理等。
APK瘦身:安装包小可以降低用户的安装成本。
(1)做混淆优化代码。
(2)删除无用的代码及图片相应的本地库。
(3)Lint优化。
(4)zip压缩。


||和|、&&和&的区别

|| 和 | 都表示“或”,区别是 || 只要满足第一个条件,后面的条件就不再判断。 | 要对所有的条件进行判断

&& 和 & 都表示“与”,区别是&&只要第一个条件不满足,后面条件就不再判断。&要对所有条件进行判断


equals和==的区别

==是判断两个变量或实例是不是指向同一个内存空间
equals是判断两个变量或实例所指向的内存空间的值是不是相等


你可能感兴趣的:(Android开发)