Activity的加载模式
standard,singleTop,singleTask,singleInstance。standard为普通模式只要调用就会在栈中创建实例,singleTop栈顶复用模式如果次Activity在栈顶直接复用不再创建实例一般用作SettingsActivity的启动,singleTask任务栈模式创建此实例在其之上的实例都会被退出一般用作HomeActivity的启动,singleInstance单独创建一个新的任务栈,单例模式一般用作接受分享的ShareActivity。
Activity的生命周期
Activity
从未被初始化到出现在Window中可见再到销毁所经历的生命周期:onCreate()-->onStart()-->onResume()-->onPause()-->onStop()-->onDestroy()。在面试中会提出一个场景让你描述一下生命周期,注意back键和home键的区别,
- Activity启动到可见:onCreate()-->onStart()-->onResume()
- 按下back键执行过程:onPause()-->onStop()-->onDestroy()
- 按下home键:onPause()-->onStop()
- 按下home键回到桌面在进入:onPause()-->onStop()-->onStart()-->onResume()
- 息屏:onPause()-->onStop()
- 息屏到点亮:onPause()-->onStop()-->onStart()-->onResume()
- 当第二
Activity
出现在它上面:onPause()-->(新开页面onCreate()-->onStart()-->onResume())-->onStop() - 从新开页面退回:onPause()-->(目标页面onStart()-->onResume())-->onStop()-->onDestroy()
Fragment生命周期
Fragment
是为了解决Android碎片化的问题是一段具有自己完整的生命周期并依附在其他具有生命周的组件上的可执行片段。
生命周期 onAttach()-->onCreate()-->onCreateView()-->onActivityCreated()-->onStart()-->onResume()-->onPause()-->onStop()-->onDestroyView()-->onDestroy()--onDetach()
- 从不可见到可见:onAttach()-->onCreate()-->onCreateView()-->onActivityCreated()-->onStart()-->onResume()
- 如果加入到任务栈按返回键:onPause()-->onStop()-->onDestroyView()-->onDestroy()--onDetach()
- 按home键:onPause()-->onStop()
- 再回到Window:onStart()-->onResume()
Service的两种启动模式生命周期
- startService()
- bindService()
Service是Android四大组件之一,也是可执行的程序,有自己的生命周期。创建、配置Service和创建、配置Activity的过程相似。和Activity一样,都是从Context派生出来的。
区别按启动方式:
start方式:
生命周期:onCreate()--->onStartCommand()(onStart()方法已过时) ---> onDestory()
说明:如果服务已经开启,不会重复的执行onCreate(),而是会调用onStart()和onStartCommand()。服务停止的时候调用 onDestory()。服务只会被停止一次。
特点:一旦服务开启跟调用者(开启者)就没有任何关系了。开启者退出了,开启者挂了,服务还在后台长期的运行。开启者不能调用服务里面的方法。
bind方式:
生命周期:onCreate()--->onBind()----->onunBind() ---> onDestory()
注意:绑定服务不会调用onstart()或者onstartcommand()方法
特点:bind的方式开启服务,绑定服务,调用者挂了,服务也会跟着挂掉。绑定者可以调用服务里面的方法。
exported属性
Android四大组件中Activity
,Service
,BroadcastReceiver
创建后需要在manifest
文件中注册
<!-- 显式启动 -->
<activity
android:name=".weather.WeatherStandActivity"
android:configChanges="orientation|screenSize"
android:exported="false"
android:label="@string/weather_info"
android:launchMode="singleTask"
android:theme="@style/Theme.Dark.Weather"
android:windowSoftInputMode="adjustResize|stateHidden">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<!-- 隐式启动 -->
<activity
android:name="com.master.android.test.LaunchModeActivity"
android:configChanges="orientation|screenSize"
android:windowSoftInputMode="adjustResize|stateHidden">
<intent-filter>
<action android:name="android.intent.action.LaunchMode" />
<category android:name="android.intent.category.DEFAULT" />
<data android:scheme="info" />
</intent-filter>
</activity>
<!-- service -->
<service
android:name="com.master.android.test.aidl.AIDLService"
android:exported="true">
<intent-filter>
<action android:name="com.master.android.test.aidl.IMyAidlInterface" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</service>
注意:防止屏幕旋转导致Activity
重新onCreate()
配置时加入属性android:configChanges="orientation|screenSize"
,对软盘的配置android:windowSoftInputMode="adjustResize|stateHidden"
保证内容区域不被挤压。如果此组件可供外部调用则都需要配置android:exported="true"
属性。
Handler机制?Handler,Looper,Message,MessageQueue,Thread之间的关系
Handler机制即事Android线程之间通讯机制,Handler允许您发送和处理{@link Message}和Runnable
与线程的{@link MessageQueue}相关联的对象。每个处理者实例与单个线程和该线程的消息相关联队列。当你创建一个新的处理程序时,它绑定到线程/正在创建它的线程的消息队列-从那时起,它会将消息和可运行文件传递到该消息队列并执行他们从消息队列中出来。
1.Handler依赖与线程
2.主线程已经有自己的消息循环器(Looper.getMainLooper())
3.在子线程中更新消息必须手动创建Looper,线程不会帮你创建
4.具有消息循环功能的子线程的Handler就可以在任何线程发送消息
5.一个线程是否只有一个Looper?不一定,但是应该尽力保证他只有一个。参考:ThreadLocal
多线程的方式有哪些?
- new Thread()
- AsyncTask
- Handler
- IntentService
- ThreadPoolExecutor
在子线程更新UI的方式
- runOnUiThread(new Runnable());
- View.post(new Runnable())
- View.postDelayed(new Runnable(), long delayMillis)
- Handler.sendMessage(message)或Handler.sendEmptyMessage(int what)
其实前面三种方法最终都是调用主线程Handler发送了一个非掩饰的空消息Handler.sendEmptyMessage(int what)
ANR异常发生条件和处理方式
- 5s内没有响应用户输入事件
- 10s内广播接收器没有处理完毕
- 20s内服务没有处理完毕
处理方式:log+trace.txt
事件处理
- 分发,dispatchTouchEvent(MotionEvent ev)
- 拦截,onInterceptTouchEvent(MotionEvent ev)
- 处理,onTouchEvent(MotionEvent event)
注:只是View没有拦截处理。ViewGroup具有拦截事件,View和Activity没有ViewGroup类中的源码实现就是{return false;}表示不拦截该事件,事件将向下传递(传递给其子View);若手动重写该方法,使其返回true则表示拦截,事件将终止向下传递,事件由当前ViewGroup类来处理,就是调用该类的onTouchEvent()方法
关于事件处理的方法:
float rawX = event.getRawX(); // 对屏幕而言,绝对
float rawY = event.getRawY();
float x = event.getX(); // 对起点坐标而言,相对
float y = event.getY();
// 两次滑动距离常量最小值 , 用于过滤掉滑动距离太短的值
int scaledTouchSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop();
// 速度追踪
VelocityTracker velocityTracker = VelocityTracker.obtain();
velocityTracker.addMovement(event);
velocityTracker.computeCurrentVelocity(1000); // 计算当前速度,时间T为1000ms
float xVelocity = velocityTracker.getXVelocity(); // 横向水平速度
float yVelocity = velocityTracker.getYVelocity(); // 纵向水平速度
velocityTracker.recycle(); // 回收
/**
* 手势检测,用于检测用户的单击、滑动、长按、双击等行为。
*/
private GestureDetector mGestureDetector = new GestureDetector(getContext(), onGestureListener);
mGestureDetector.setOnDoubleTapListener(onDoubleTapListener);
/**
* 手势监听
*/
private GestureDetector.OnGestureListener onGestureListener = new GestureDetector.OnGestureListener() {
/**
* 手指轻触的一瞬间 触发MotionEvent.ACTION_DOWN
*/
@Override
public boolean onDown(MotionEvent e) {
return false;
}
/**
* 手指轻触的一瞬间 尚未松开或拖动 触发MotionEvent.ACTION_DOWN
*/
@Override
public void onShowPress(MotionEvent e) {
}
/**
* 手指轻触屏幕后松开 这是单击行为 触发MotionEvent.ACTION_UP
*/
@Override
public boolean onSingleTapUp(MotionEvent e) {
return false;
}
/**
* 手指按下屏幕并拖动 这是拖动行为 触发MotionEvent.ACTION_DOWN
*和一系列MotionEvent.ACTION_MOVE
*/
@Override
public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
return false;
}
/**
* 用户长久的按着屏幕不放 这是长安行为
*/
@Override
public void onLongPress(MotionEvent e) {
}
/**
* 用户按下屏幕快速滑动后松开 这是快速滑动行为 触发一个MotionEvent.ACTION_DOWN
* 多个MotionEvent.ACTION_MOVE和一个MotionEvent.ACTION_UP
*/
@Override
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
return false;
}
};
/**
* 监听双击事件
*/
private GestureDetector.OnDoubleTapListener onDoubleTapListener = new GestureDetector.OnDoubleTapListener() {
/**
* 严格的单击行为
*/
@Override
public boolean onSingleTapConfirmed(MotionEvent e) {
return false;
}
/**
* 双击 两次连续的单击 它不可能和onSingleTapConfirmed共存
*/
@Override
public boolean onDoubleTap(MotionEvent e) {
return false;
}
/**
* 双击行为 并触发MotionEvent.ACTION_DOWN,MotionEvent.ACTION_MOVE,MotionEvent.ACTION_UP
*/
@Override
public boolean onDoubleTapEvent(MotionEvent e) {
return false;
}
};
/**
* 弹性滑动对象,实现弹性的过度效果
*/
private Scroller mScroller = new Scroller(getContext());
四大引用
- 强 (StrongReference)
- 软 (SoftReference)
- 弱(WeakReference)
- 虚 (PhantomReference)
弄懂对GC的影响,
- 强引用(StrongReference)
我们使用的大部分引用实际上都是强引用,这是使用最普遍的引用。如果一个对象具有强引用,那就类似于必不可少的生活用品,垃圾回收器绝不会回收它。当内存空间不足,Java虚拟机宁愿抛出OutOfMemoryError错误,使程序异常终止,也不会靠随意回收具有强引用的对象来解决内存不足问题。 - 软引用(SoftReference)
如果内存空间足够,垃圾回收器就不会回收它,如果内存空间不足了,就会回收这些对象的内存。只要垃圾回收器没有回收它,该对象就可以被程序使用。 - 弱引用(WeakReference)
在垃圾回收器线程扫描它所管辖的内存区域的过程中,一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存。不过,由于垃圾回收器是一个优先级很低的线程,因此不一定会很快发现那些只具有弱引用的对象。 弱引用可以和一个引用队列(ReferenceQueue)联合使用,如果弱引用所引用的对象被垃圾回收,Java虚拟机就会把这个弱引用加入到与之关联的引用队列中。 - 虚引用(PhantomReference)
如果一个对象仅持有虚引用,那么它就和没有任何引用一样,在任何时候都可能被垃圾回收。虚引用主要用来跟踪对象被垃圾回收的活动。虚引用与软引用和弱引用的一个区别在于:虚引用必须和引用队列(ReferenceQueue)联合使用。当垃 圾回收器准备回收一个对象时,如果发现它还有虚引用,就会在回收对象的内存之前,把这个虚引用加入到与之关联的引用队列中。程序可以通过判断引用队列中是否已经加入了虚引用,来了解被引用的对象是否将要被垃圾回收。程序如果发现某个虚引用已经被加入到引用队列,那么就可以在所引用的对象的内存被回收之前采取必要的行动。
动画
- View动画(补间动画)
- 属性动画
- 帧动画
补间动画和属性动画的区别:补间动画改变的是View的位置和状态但本质上它的原始坐标并未改变,属性动画是通过改变View具有get/set属性的值位置和状态发生本质变化。属性动画的多样性更多不只移动,旋转,伸缩,透明度的变化。
Dalvik和Art区别?
- Just In Time(即时编译技术)和Ahead Of Time(预编译技术)的区别
- Dalvik基于寄存器 JVM基于栈
- Art(Android Runtime)
参考:Android 中的Dalvik和ART是什么,有啥区别?
自定义View和ViewGroup
- onMeasure(int widthMeasureSpec, int heightMeasureSpec)
- onDraw(Canvas canvas)
- onLayout(boolean changed, int left, int top, int right, int bottom)
最要的三个方法,onMeasure
的widthMeasureSpec
,heightMeasureSpec
是父布局封装后传过来的规格通过MeasureSpec获取模式和尺寸
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
在通过模式来控制View的宽高,MeasureSpec.EXACTLY
模式相当于macth_parent
那么View的宽度或高度就等于widthSize,heightSize。MeasureSpec.AT_MOST
模式相当于wrap_content
则View的宽高就等于绘制区域或实际或设置的宽高。MeasureSpec.UNSPECIFIED
模式一般很少用到。
switch (mode) {
case MeasureSpec.EXACTLY:
width = widthSize;
break;
case MeasureSpec.UNSPECIFIED:
case MeasureSpec.AT_MOST:
break;
}
在自定义View时在布局文件中设置宽度为macth_parent
和wrap_content
是等效的,原因是:View源码MeasureSpec.AT_MOST
和MeasureSpec.EXACTLY
时宽高都等于屏幕剩余的宽高。
public static int getDefaultSize(int size, int measureSpec) {
int result = size;
int specMode = MeasureSpec.getMode(measureSpec);
int specSize = MeasureSpec.getSize(measureSpec);
switch (specMode) {
case MeasureSpec.UNSPECIFIED:
result = size;
break;
case MeasureSpec.AT_MOST:
case MeasureSpec.EXACTLY:
result = specSize;
break;
}
return result;
}
怎么获取绘制文本的宽高,首先在构造方法中执行TextPaint.getTextBounds方法,然后通过Rect区域实例获取宽高。
TextPaint.getTextBounds(text, 0, text.length(), mRect); // 获取文本宽高
int width = mRect.width();
int height = mRect.height();
关于坐标系:Android系统的坐标系和其他平台一样采用相对坐标,并以矩形构图。对父布局而言左上角是原始起点(0,0),height相当于y轴坐标,width相当于x轴坐标。
在onDraw方法中注意过度绘制(OverDraw)
onLayout在自定义View就不需要重写了,关键是自定义ViewGroup时子控件的测量与摆放。
注意:自定义View时颜色尽量转化为ColorStateList
,字体的大小注意dp与px的转化使用
TypedValue.applyDimension(unit, size, DisplayMetrics)
转换,在面试中被问到自定义控件时讲一个自己自定义的控件就好了
GC算法
- 标记清除(Mark & Sweep 算法)
- 引用计数算法
- 节点复制算法
- 分代回收
基本概念:有向可达图与根集(GC Roots),引用链,可达性,标记位。如果节点向下检索未找到对象的引用链则没有可达性建议回收,这时也不是一定会被回收,GC会做一次标记,如果第二次依然被标记才会被回收。
什么是过渡绘制,如何防止过渡绘制
定义:过度绘制造成UI卡顿的原因是因为它浪费大量的CPU以及GPU资源。手机原本为了保持视觉的流畅度,其屏幕刷新频率是60hz
,即在1000/60=16.67ms
内更新一帧。如果没有完成任务,就会发生掉帧的现象,也就是我们所说的卡顿。
怎么查看过度绘制:调试手机开发者选项打开强制GPU渲染
怎么避免过度绘制:
- 尽量多使用
RelativeLayout
和LinearLayout
,不要使用绝对布局AbsoluteLayout
, - 在布局层次一样的情况下,建议使用
LinearLayout
代替RelativeLayout
,因为LinearLayout
性能要稍高一点. - 在完成相对较复杂的布局时,建议使用
RelativeLayout
,RelativeLayout
可以简单实现LinearLayout
嵌套才能实现的布局. - 将可复用的组件抽取出来并通过
include
标签使用; - 使用
ViewStub
标签来加载一些不常用的布局; - 动态地
inflationview
性能要比SetVisiblity
性能要好.当然用VIewStub
是最好的选择. - 使用
merge
标签减少布局的嵌套层次 - 去掉多余的背景颜色
- 对于有多层背景颜色的
Layout
来说,留最上面一层的颜色即可,其他底层的颜色都可以去掉 - 对于使用Selector当背景的
Layout
(比如ListView
的Item
,会使用Selector
来标记点击,选择等不同的状态),可以将normal
状态的color
设置为@android:color/transparent
,来解决对应的问题 - 内嵌使用包含
layout_weight
属性的LinearLayout
会在绘制时花费昂贵的系统资源,因为每一个子组件都需要被测量两次。在使用ListView
与GridView
的时候,这个问题显的尤其重要,因为子组件会重复被创建.所以要尽量避免使用Layout_weight
- 使得
Layout
宽而浅,而不是窄而深(在HierarchyViewer
的Tree
视图里面体现)
线程与进程的区别
- 线程
- 进程
- synchronized
进程:当一个程序第一次启动的时候,Android会启动一个LINUX进程和一个主线程。默认的情况下,所有该程序的组件都将在该进程和线程中运行。 同时,Android会为每个应用程序分配一个单独的LINUX用户。Android会尽量保留一个正在运行进程,只在内存资源出现不足时,Android会尝试停止一些进程从而释放足够的资源给其他新的进程使用, 也能保证用户正在访问的当前进程有足够的资源去及时地响应用户的事件。
我们可以将一些组件运行在其他进程中,并且可以为任意的进程添加线程。组件运行在哪个进程中是在manifest
文件里设置的,其中Activity
,Service
,receiver
和provider
都有一个process
属性来指定该组件运行在哪个进程之中。我们可以设置这个属性,使得每个组件运行在它们自己的进程中,或是几个组件共同享用一个进程,或是不共同享用。application
元素也有一个process
属性,用来指定所有的组件的默认属性。
进程按级别分类: 分为5个级别
- 前台进程
- 其中运行着正与用户交互的Activity(Activity对象的 onResume() 方法已被调用)。
- 其中运行着被正与用户交互的activity绑定的服务Service。
- 其中运行着“前台”服务Service——服务以startForeground()方式被调用。
- 其中运行着正在执行生命周期回调方法(onCreate()、onStart()或onDestroy())的服务Service。
- 其中运行着正在执行onReceive()方法的BroadcastReceiver。
- 可见进程
- 其中运行着不在前台的Activity,但用户仍然可见到此activity(onPause()方法被调用了)。比如以下场合就可能发生这种情况:前台activity打开了一个对话框,而之前的activity还允许显示在后面。
- 其中运行着被可见(或前台)activity绑定的服务Service。
- 服务进程
- 后台进程
- 空进程
线程: 应用程序启动时,系统会为它创建一个名为main
的主线程。主线程非常重要,因为它负责把事件分发给相应的用户界面widget——包括屏幕绘图事件。它也是应用程序与Android UI
组件包(来自android.widget
和android.view
包)进行交互的线程。因此,主线程有时也被叫做UI
线程。
系统并不会为每个组件的实例都创建单独的线程。运行于同一个进程中的所有组件都是在UI线程中实例化的,对每个组件的系统调用也都是由UI
线程分发的。因此,对系统回调进行响应的方法(比如报告用户操作的onKeyDown()
或生命周期回调方法)总是运行在UI
线程中。
如果UI线程需要处理每一件事情,那些耗时很长的操作——诸如访问网络或查询数据库等——将会阻塞整个UI
(线程)。一旦线程被阻塞,所有事件都不能被分发,包括屏幕绘图事件。从用户的角度看来,应用程序看上去像是挂起了。更糟糕的是,如果UI
线程被阻塞超过一定时间(目前大约是5秒钟),用户就会被提示(ANR)对话框。如果引起用户不满,他可能就会决定退出并删除这个应用程序。
Andoid的UI组件包并不是线程安全的。因此不允许从工作线程中操作UI——只能从UI线程中操作用户界面。于是,Andoid的单线程模式必须遵守两个规则:1.不要阻塞UI线程。2.不要在UI线程之外访问Andoid的UI组件包。
synchronized: Synchronized
是Java中解决并发问题的一种最常用的方法,也是最简单的一种方法。
作用:
- 确保线程互斥的访问同步代码
- 保证共享变量的修改能够及时可见
- 有效解决重排序问题
用途:
- 修饰普通方法
- 修饰静态方法
- 修饰代码块
volatile
缓存一致性问题,一旦一个共享变量(类的成员变量、类的静态成员变量)被volatile
修饰之后,那么就具备了两层语义:
- 保证了不同线程对这个变量进行操作时的可见性,即一个线程修改了某个变量的值,这新值对其他线程来说是立即可见的。
- 禁止进行指令重排序。
在多进程中,Application会启动几次
这个问题涉及到Android进程分配问题,在默认情况下Application
只在一个默认的进程中启动,只有显示的指定process
进程中启动,所在多进程中Application
会启动多次。
获取进程名方法:
private String getProcessName(Context context) {
ActivityManager activityManager = (ActivityManager) context.
getSystemService(Context.ACTIVITY_SERVICE);
List<ActivityManager.RunningAppProcessInfo> runningAppProcesses =
activityManager.getRunningAppProcesses();
for (ActivityManager.RunningAppProcessInfo process : runningAppProcesses) {
if (process.processName != null) {
return process.processName;
}
}
return null;
}
在onCreate()
方法中将获取的进程与配置的进程进行比较,如果是在该进程中启动的就做该进程的事。
跨进程间通讯
- Binder机制
由于Android系统对进程的设计保证了应用程序之间不能共享内存,但有时需要不同程序之间要进行数据交互,所Android推出了跨进程间通讯的几种机制。
什么是Binder机制:传统的IPC
机制是不安全,完全依赖上层协议。传统IPC
的接收方无法获得对方进程可靠的UID
和PID
(用户ID进程ID),从而无法鉴别对方身份。Android为每个安装好的应用程序分配了自己的UID
,故进程的UID
是鉴别进程身份的重要标志。使用传统IPC
只能由用户在数据包里填入UID
和PID
,但这样不可靠,容易被恶意程序利用。可靠的身份标记只有由IPC
机制本身在内核中添加。其次传统IPC访问接入点是开放的,无法建立私有通道。比如命名管道的名称,systemV
的键值,socket
的ip
地址或文件名都是开放的,只要知道这些接入点的程序都可以和对端建立连接,不管怎样都无法阻止恶意程序通过猜测接收方地址获得连接。所以Android需要建立一套新的IPC机制来满足系统对通信方式,传输性能和安全性的要求,这就是Binder。Binder基于Client-Server通信模式,传输过程只需一次拷贝,为发送发添加UID/PID身份,既支持实名Binder也支持匿名Binder,安全性高。对Binder
而言,Binder
可以看成Server
提供的实现某个特定服务的访问接入点,Client
通过这个‘地址’向Server
发送请求来使用该服务;对Client
而言,Binder
可以看成是通向Server
的管道入口,要想和某个Server
通信首先必须建立这个管道并获得管道入口。
其中Activity可以跨进程调用其他应用程序的Activity
;Content Provider
可以跨进程访问其他应用程序中的数据(以Cursor
对象形式返回),当然,也可以对其他应用程序的数据进行增、删、改操 作;Broadcast
可以向android系统中所有应用程序发送广播,而需要跨进程通讯的应用程序可以监听这些广播;Service
和Content Provider
类似,也可以访问其他应用程序中的数据,但不同的是,Content Provider
返回的是Cursor
对象,而Service
返回的是Java对象,这种可以跨进程通讯的服务叫AIDL
服务。
Activity的跨进程间访问
ComponentName componentName = new ComponentName(String pkg, String cls);
MyParcelable parcel = new MyParcelable();
Intent intent = new Intent();
intent.setComponent(componentName);
intent.putExtra("parcel_key", parcel);
startActivity(intent);
Service和AIDL结合
// 第一步定义一个AIDL接口
interface IMyAidlInterface {
/**
* Demonstrates some basic types that you can use as parameters
* and return values in AIDL.
*/
void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat,
double aDouble, String aString);
}
// 与Service相关联
public class AIDLService extends Service {
private static final String TAG = "AIDLService";
IMyAidlInterface.Stub stub;
@Override
public void onCreate() {
super.onCreate();
}
@Nullable
@Override
public IBinder onBind(Intent intent) {
stub = new IMyAidlInterface.Stub() {
@Override
public void basicTypes(int anInt, long aLong, boolean aBoolean,
float aFloat, double aDouble, String aString) throws RemoteException {
Log.e(TAG, "basicTypes: " + anInt + aLong + aBoolean + aFloat + aDouble + aString);
}
};
return stub;
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
return super.onStartCommand(intent, flags, startId);
}
@Override
public void onDestroy() {
super.onDestroy();
}
}
// client端,第一步需要在同包下写一个相同aidl文件
private IMyAidlInterface anInterface;
private boolean mBound = false;
private ServiceConnection connection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
anInterface = IMyAidlInterface.Stub.asInterface(service);
mBound = true;
}
@Override
public void onServiceDisconnected(ComponentName name) {
mBound = false;
}
};
private void initAIDL() {
Intent intent = new Intent();
intent.setAction("com.master.android.test.aidl.IMyAidlInterface");
intent.setPackage("com.master.android.test.aidl");
bindService(intent, connection, Context.BIND_AUTO_CREATE);
}
@Override
protected void onStart() {
super.onStart();
if (!mBound) {
initAIDL();
}
}
@Override
protected void onStop() {
super.onStop();
if (mBound) {
unbindService(connection);
mBound = false;
}
}
// 调用方法
anInterface.basicTypes(1, 2, true, 3, 4, "hello aidl");
// Stub类中asInterface函数作用是通过binder拿到aidl的Java类
总结: 跨进程间通讯不管你是使用Messager+Handler+Service还是Service+aidl其本质都是以Binder机制为基础
单例模式,双锁原理,volatile原理,静态内部类实现单例的原理###
单例模式懒汉普通版:
public class DateUtil {
private static DateUtil INSTANCE;
private DateUtil() {}
public static DateUtil getInstance() {
if (INSTANCE == null) {
INSTANCE = new DateUtil();
}
return INSTANCE;
}
}
单例模式懒汉开挂模式:双锁原理,volatile原理。双锁原理保证了在多线程下线程绝对安全,volatile又保证了在栈中取出对象地址和new对象顺序的一致性。
public class DateUtil {
private static volatile DateUtil INSTANCE;
private DateUtil() {
}
public static synchronized DateUtil getInstance() {
if (INSTANCE == null) {
synchronized (DateUtil.class) {
INSTANCE = new DateUtil();
}
}
return INSTANCE;
}
}
单例模式饿汉模式
public class DateUtil {
private static DateUtil INSTANCE = new DateUtil();
private DateUtil(){}
public static DateUtil getInstance() {
return INSTANCE;
}
}
静态内部类实现单例的原理:将这个单实例的引用变量定义在静态内部类中,来实现单例,这样可以做到不用if条件进行判断,并且是多线程安全的(由jvm保证)
public class DateUtil {
private DateUtil() {
}
private static class Singleton {
private static DateUtil INSTANCE;
static {
INSTANCE = new DateUtil();
}
}
public static DateUtil getInstance() {
return Singleton.INSTANCE;
}
}
ListView的优化
ListView 的核心原理就是重用 View。ListView 中有一个回收器,Item 滑出界面的时候 View 会回收到这里,需要显示新的 Item 的时候,就尽量重用回收器里面的 View。
- 在adapter中的getView方法中尽量少使用逻辑
- 尽最大可能避免GC(将创建对象放到ViewHolder里)
- 滑动的时候不加载图片
- 将ListView的scrollingCache和animateCache设置为false
- item的布局层级越少越好
- 使用ViewHolder
- 善用convertView(容器)
- 有图片加载时滑出屏幕不加载
// 滑动时不加载图片
listView.setOnScrollListener(new OnScrollListener() {
@Override
public void onScrollStateChanged(AbsListView listView, int scrollState) {
//停止加载图片
if (scrollState == AbsListView.OnScrollListener.SCROLL_STATE_FLING) {
imageLoader.stopProcessingQueue();
} else {
//开始加载图片
imageLoader.startProcessingQueue();
}
}
@Override
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
// TODO Auto-generated method stub
}
});
HashMap的实现原理
概述: HashMap
是基于哈希表的Map
接口的非同步实现。此实现提供所有可选的映射操作,并允许使用null
值和null
键。此类不保证映射的顺序,特别是它不保证该顺序恒久不变。
数据结构:在java编程语言中,最基本的结构就是两种,一个是数组,另外一个是模拟指针(引用),所有的数据结构都可以用这两个基本结构来构造的,HashMap
也不例外。HashMap
实际上是一个“链表散列”的数据结构,即数组和链表的结合体。最上层是一组数组每个元素在数组中的位置(即下标)对应一个向下的链表,数组中的位置相当于key
链表纵深相当于value
每次put数据时新的放到最上层老数据向下排。
解决hash冲突:
- 开放寻址法
- 再散列法
- 拉链法(链地址法)
- 建立公共溢出区
Android中的内存泄漏和内存溢出有什么区别?
内存溢出: 是指程序在申请内存的时候,没有足够的内存可以分配,导致Out Of Memory
错误,也就是OOM
。
内存泄漏:对象都有生命周期的,在生命周期完成之后,就该被垃圾回收和释放,如果得不到及时的释放,就会一直占用内存,造成内存泄漏。随着内存泄漏的堆积,可用的内存空间越来越少,最后会导致内存溢出。导致内存泄漏有很多原因,最常见的有内部类的使用,因为内部类持有外部引用。还有就是对Activity Contex
t的使用,如果没有特别的要求,尽量使用Application context.
避免其他地方持有Activity而得不到释放,如果必须要用Activity Context
,可以用弱引用。
在Activity中如何保存/恢复状态?
分别调用onSaveInstanceState和onRestoreInstanceState保存和恢复状态。
Android 中序列化有哪些方式?区别?
- Serializable(Java自带)
- Parcelable(android 专用)
除了Serializable
之外,使用Parcelable
也可以实现相同的效果,不过不同于将对象进行序列化,Parcelable
方式的实现原理是将一个完整的对象进行分解,而分解后的每一部分都是Intent所支持的数据类型,这样也就实现传递对象的功能了。
区别:Parcelable
比Serializable
性能高,所以应用内传递数据推荐使用Parcelable
,但是Parcelable
不能使用在要将数据存储在磁盘上的情况,因为Parcelable
不能很好的保证数据的持续性在外界有变化的情况下。尽管Serializable
效率低点,但此时还是建议使用Serializable
。
MVP架构的优缺点?
优点:
-
View
和Model
之间的耦合度降低,使其更关注自身业务逻辑,结构清晰,维护方便; - 便于单元测试;
- 代码复用率提高;
- 代码框架更适用于快速迭代开发;
缺点:
-
View
层过大,Activity
复杂,加入模板方法,分离出BaseActivity
用于处理公共逻辑 -
Model
层过大,做好模块划分,进行接口隔离,在内部进行分层。 - 还有一个问题是,MVP额外的增加了很多类和接口,这个可以根据项目实际情况进行相应地优化
OkHttp 的addInterceptor 和 addNetworkInterceptor 的区别?
addInterceptor(应用拦截器):
- 不需要担心中间过程的响应,如重定向和重试.
- 总是只调用一次,即使HTTP响应是从缓存中获取.
- 观察应用程序的初衷. 不关心OkHttp注入的头信息如: If-None-Match.
- 允许短路而不调用 Chain.proceed(),即中止调用.
- 允许重试,使 Chain.proceed()调用多次.
addNetworkInterceptor(网络拦截器):
- 能够操作中间过程的响应,如重定向和重试.
- 当网络短路而返回缓存响应时不被调用.
- 只观察在网络上传输的数据.
- 携带请求来访问连接.