Java知识点
抽象类与接口的区别:
- 抽象类可以有方法的具体实现,接口不可以。
- 由于Java单继承的特性,子类只能继承一个抽象类,但可以实现多了接口。
- 接口是为类的具体实现提供一个通用的规范,抽象类是为子类提供一个公共的类型,是为了类的继承而存在的。
说明:接口可以被接口继承,接口所有的方法自动被声明为public,接口可以定义成员变量,自动被声明为public static final类型。
集合继承结构
1.SparseArray、ArrayList对比
SparseArray是稀疏数组,主要是包括两个数组分别用来存放key、value,其中get和put元素时用到了二分查找。
ArrayList比较简单,就用一个对象数组来存储元素。
2. ArrayMap、HashMap的对比
HashMap:
Java库里的HashMap其实是一个连续的链表数组,通过key计算hash值后插入对应的index里。当hash值发生碰撞时,可以采用线性探测,二次hash,或者后面直接变成链表的结构来避免碰撞。
说明:hash值不是连续的,所以hashmap实际需要占用的大小会比它实际能装的item的容量要大。
ArrayMap:
先看看ArrayMap的结构图:
ArrayMap用两个数组来模拟Map,第一个数组存放存放item的hash值,第二数组是把key,value连续的存放在数组里。通过先算hash,在第一个数组里找到它的hash index,然后根据这个index在去第二个数组里找到这个key-value。
这里,在第一个数组里查找hash index的方法是用二分查找(binary search),过程如下图所示。
说明:
查找元素达不到HashMap的O(1)时间复杂度。
3. HashMap、HashTable的区别
1). HashTable是继承Dictionary类,实现Map接口,HashMap是继承AbstractMap类并实现了Map接口,AbstractMap类也实现了Map接口。
2). HashTable的方法是同步的,HashMap是非同步。
3). HashTable、HashMap使用的哈希值的不同,HashTable直接使用对象的hashCode,而HashMap则重新计算了Hash值。
String、StringBuffer、StringBuilder的区别
- String是字符串常量,StringBuffer、StringBuilder是字符串变量。
- StringBuffer是线程安全的,StringBuilder是非线程安全的。
- 对于内容经常改变的字符串最好使用StringBuffer或者StringBuilder,因为StringBuffer或者StringBuilder是对字符串对象本身做修改,String是每次都会生成一个新的对象。
==、equals、hashCode的区别
- == : 它的作用是判断两个变量(基本变量)的值是否相等,或者判断两个对象的地址是不是相等,即判断两个对象是不是同一个对象。
- equals:不重写equals的时候,比较的是引用(Reference),也就是内存地址。
public boolean equals(Object o) {
return this == o;
}
- equals相等,则hasCode一定相等,而hashCode相等,equals不一定相等。因此,一般重写equals,就必须重写hashCode。
Android知识点
本地广播、全局广播的区别
本地广播仅在应用内发送,全局广播是在系统内发送。
本地广播不用担心隐私数据泄露的问题,全局广播需要防止别的应用伪造广播,从而造成不必要的安全隐患。
Service的启动方式
Service的启动方式有两种:startService,bindService,这两种启动的方式的区别如下:
1)startService来启动某个service,其生命周期与启动方(如Activity)无关,当Activity销毁后,service还在后台运行,除非执行stopService方法。
2)bindService来启动某个service,其生命周期与启动方(如Activity)有关,当Activity销毁后,service也跟着销毁,当然也可以调用unbind方法解除绑定。
3)生命周期的回调方法不同,如下:
startService: onCreate -> onStartCommand -> onDestroy
bindService:onCreate -> onBind -> onUnbind -> onDestroy
说明:Service仍然是在主线程中调用,还是要开线程才能处理长时间的工作。
引申:IntentService是处理异步任务,实现多线程。
Android动画类型
Android动画包括帧动画(Frame Animation)、补间动画(Tween Animation)、属性动画。
帧动画:连续地播放图片序列,有点类似于gif图,在Android中用AnimationDrawable来加载XML资源文件并进行播放。
补间动画:主要分为scale(缩放)、rotate(旋转)、translate(位移)、alpha(透明度渐变),一般是在XML文件中写好,然后通过AnimationUtils进行加载。
属性动画:主要是ObjectAnimator 这个类,其继承自ValueAnimator,使用这个类可以对任意对象的任意属性进行动画操作,可以很好地实现补间动画。
内存泄漏
1. 内存泄漏
我们知道Java虚拟机会帮我们进行垃圾回收操作,即GC操作,如果一些无用的对象没有被回收,就会发生了内存泄漏,在讲内存泄漏之前,我们先来看看Java四种引用类型。
强引用: 如果对象具有强引用,即便内存不足抛出OOM,也不会回收该对象,强引用一般是这样的:Object object = new Object()。
软引用: 如果对象具有软引用,当内存不足时,虚拟机会回收该对象。
弱引用:软引用发生在垃圾回收时,不管内存是否不足,都会对弱引用的对象进行回收。
虚引用:如果一个对象具有虚引用,其实跟没有任何引用一样,任何时候都会被垃圾回收器回收。
2. 内存泄漏分析
1)长生命周期对象持有短生命周期对象的引用
public class SingleInstance {
private Context mContext;
private static SingleInstance instance;
public SingleInstance(Context context){
mContext = context;
}
public static SingleInstance getInstance(Context context){
if(instance == null){
instance = new SingleInstance(context);
}
return instance;
}
}
假设,我们这里传入的是Activity-A的Context,在Activity-A的生命周期内,上面的单例能够正常使用,如果Activity-A销毁了,我们在其它地方使用上面的单例时,就会出现内存泄漏。
我们知道单例的生命周期一般是跟整个应用的生命周期一样长,因此,我们应该传入Application的Context,为了让每次使用单例时不用额外传入一个变量,上面的代码可以修改如下:
public class SingleInstance {
private final Context mContext = Application.appContext;
private static SingleInstance instance;
private SingleInstance(){
}
public static SingleInstance getInstance(){
if(instance == null){
instance = new SingleInstance();
}
return instance;
}
}
2) 匿名内部类的使用
在Activity或者Fragment中,我们一般会使用Handler更新UI操作,先来看看下面的代码有什么问题没。
public class MainActivity extends Activity {
private Handler mHandler = new Handler(){
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mHandler.sendEmptyMessageDelayed(0, 6*1000);
}
}
上面代码,在onCreate方法里面发送一个延迟Message,这时如果Activity销毁,则会发生内存泄漏。因为非静态的内部类持有外部类的引用,而mHandler的生命周期还没结束,其持有MainActivity的引用,但这时Activity已经销毁。
对于上面的代码,我们可以进行如下修改:
public class MainActivity extends Activity {
private final Handler mHandler = new MyHandler(this);
public static class MyHandler extends Handler{
private final WeakReference<MainActivity> mActivity;
public MyHandler(MainActivity activity){
mActivity = new WeakReference<MainActivity>(activity);
}
@Override
public void handleMessage(Message msg) {
if(mActivity.get() != null){
}
}
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mHandler.sendEmptyMessageDelayed(0, 6*1000);
}
}
引申:线程造成的内存溢出
异步任务AsyncTask和Runnable都是一个匿名内部类,它们对当前Activity都有一个隐式引用。如果Activity在销毁之前,任务还未完成, 那么将导致Activity的内存资源无法回收,造成内存泄漏。
3) 非静态的内部类创建静态实例
下面的代码中,我们创建了一个静态的资源对象mResouce,每次Activity启动都会使用该资源的数据,避免了重复创建,但是这样会造成内存泄漏,原因如下:
~ 非静态内部类默认会持有外部类的引用。
~ 使用了该非静态内部类创建了一个静态的实例。
~ 静态实例的生命周期和应用的一样长,这就导致了该静态实例一直会持有该Activity的引用,导致Activity的内存资源不能正常回收。
private static XMLResource mResource = null;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
if(mResource == null){
mResource = new XMLResource();
}
}
class XMLResource {
}
解决方案:将非静态内部类XMLResource修改为静态内部类。
4)使用了静态的Activity和View
解决方案:应该及时将静态的应用 置为null,而且一般不建议将View及Activity设置为静态。
5)其它
注册了系统的服务,但onDestory没有注销,不需要用的监听器未移除。
3. 应用的 Context数量
Context是一个抽象类,子类有ContextWrapper,ContextWrapper 的子类有ContextThemeWrapper,ContextWrapper有一个成员变量mBase,类型是Context,下面我们来看看有哪些组件间接继承ContextWrapper。
1) Activity
public class Activity extends ContextThemeWrapper
implements LayoutInflater.Factory2,
Window.Callback, KeyEvent.Callback,
OnCreateContextMenuListener, ComponentCallbacks2,
Window.OnWindowDismissedCallback, WindowControllerCallback,
AutofillManager.AutofillClient
2) Service
public abstract class Service extends ContextWrapper implements ComponentCallbacks2
3) Application
public class Application extends ContextWrapper implements ComponentCallbacks2
综上可知:
APP Context总数 = Application数 + Activity数 + Service数
说明:多进程下Application可能被创建多次,因此多进程环境下,Application的Context数可能不止一个。