Android面试题及其答案(二)
程序员文章站
2022-05-06 16:50:54
...
内存相关的问题在面试中被问到的概率还是比较大的,而且内存优化对于一个程序的性能而言也是至关重要的,现在就让我们一起来学习吧!
不废话,直接上干货~
一、内存泄漏
内存泄漏就是我们对某一内存空间的使用完成后没有释放。主要原因:导致内存泄漏最主要的原因就是某些长存对象持有了一些其它应该被回收的对象的引用,导致垃圾回收器无法去回收掉这些对象。
出现的场景:
1.数据库的cursor没有关闭;
2.构造adapter时,没有使用缓存contentview;
3.Bitmap对象不使用时采用recycle()释放内存;
4.activity中的对象的生命周期大于activity;
二、内存溢出
内存溢出通俗理解就是软件(应用)运行需要的内存,超出了它可用的最大内存。
出现的场景:
1、使用大的Bitmap图片;
三、内存优化方法:
1、应该尽量避免static成员变量引用资源耗费过多的实例,比如Context。Context尽量使用Application Context,因为Application的Context的生命周期比较长,引用它不会出现内存泄露的问题。使用WeakReference代替强引用;
2、线程也是造成内存泄露的一个重要的源头。线程产生内存泄露的主要原因在于线程生命周期的不可控。将线程内部类,改为静态内部类;
3、Bitmap问题:可以说出现OutOfMemory问题的绝大多原因都是因为Bitmap的问题。因为Bitmap占用的内存实在是太多了,它是一个“超级大胖子”,特别是分辨率大的图片,如果要显示多张那问题就更显著了;
如何解决Bitmap带给我们的内存问题?
1)及时的销毁,虽然系统能够确认Bitmap分配的内存最终会被销毁,但是由于它占用的内存过多,所以很可能会超过java堆的限制。因此,在用完Bitmap时,要及时的recycle掉。recycle并不能确定立即就会将Bitmap释放掉,但是会给虚拟机一个暗示:“该图片可以释放了”。
2)设置一定的采样率,有时候,我们要显示的区域很小,没有必要将整个图片都加载出来,而只需要记载一个缩小过的图片,这时候可以设置一定的采样率,那么就可以大大减小占用的内存。如下面的代码:
BitmapFactory.Options options = new BitmapFactory.Options(); options.inSampleSize = 2;//图片宽高都为原来的二分之一,即图片为原来的四分之一
3)使用LruCache缓存。4、巧妙的运用软引用(SoftRefrence),有些时候,我们使用Bitmap后没有保留对它的引用,因此就无法调用Recycle函数。这时候巧妙的运用软引用,可以使Bitmap在内存快不足时得到有效的释放;
5、尽量使用9path图片,Adapter要使用convertView复用等等;
6、比较耗资源的对象及时的关闭,例如Cursor,各种传感器,Service 等等;
7、使用IntentService代替service,这种Service的最大特点就是当后台任务执行结束后会自动停止,在极大程度上避免了Service内存泄漏的可能性;
8、使用ProGuard,它除了混淆代码之外,还具有压缩和优化代码的功能。ProGuard会对我们的代码进行检索,删除一些无用的代码,并且会对类、字段、方法等进行重名,重命名之后的类、字段和方法名都会比原来简短很多,这样的话也就对内存的占用变得更少了;
ListView的优化方案:
1>、复用contentView:就是自定义适配器在getView方法中要考虑方法传进来的参数contentView是否为null,如果为null就创建contentView并返回,如果不为null则直接使用;在这个方法中尽可能少创建view;
2>、异步加载图片:给contentView设置tag(setTag()),传入一个viewHolder对象,下次可以直接调用getTag()显示缓存中的数据,可以达到图像数据异步加载的效果;
3>.快速滑动列表时不显示图片:
当快速滑动列表时(SCROLL_STATE_FLING),item中的图片获取需要消耗资源的view,可以不显示出来;而处于其他两种状态:空闲(SCROLL_STATE_IDLE)和低俗拖动(SCROLL_STATE_TOUCH_SCROLL),则将那些view显示出来。
如果 listview 需要显示的 item 很多,就要考虑分页加载。比如一共要显示100条或者更多的时候,我们可以考虑先加载20条,等用户拉到列表底部的时候,再去加载接下来的20 条。
4>.如果自定义的item中有图片,需要处理图片(减少图片所占内存);
1.对图片进行边界压缩 2.用option类来保存图片大小 3.避免图片的实时缩放,最好预先缩放到视图大小
5>.尽量避免在listview适配器中使用线程,线程是产生内存泄露的主要原因,因为线程的生命周期不可控;
我们用具体的代码来看看,怎么避免OOM
事例一:
Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
mImageView.setImageBitmap(mBitmap);
}
}
在一个Activity中有一个Handler的内部类,Handler对象会隐式地持有该Activity的引用,而Handler通常会伴随着一个耗时的后台线程(例如网络下载图片)一起出现,这个后台线程在任务执行完毕之后,通过消息机制通知Handler,然后Handler把图片更新到界面。然而,如果用户在网络请求过程中关闭了Activity,正常情况下,Activity不再被使用,它就有可能在GC检查时被回收掉,但由于这时线程尚未执行完,而该线程持有Handler的引用,这个Handler又持有Activity的引用,就导致该Activity无法被回收(即内存泄露),直到网络请求结束。
要解决这个问题有两种办法:
方法一:通过程序逻辑来进行保护。
1).在关闭Activity的时候停掉你的后台线程。线程停掉了,就相当于切断了Handler和外部连接的线,Activity自然会在合适的时候被回收。
2).如果你的Handler是被delay的Message持有了引用(调用了postDelayed,那么使用Handler的removeCallbacks()或removeCallbacksAndMessages(null)方法把消息对象从消息队列移除就行了。
方法二:将Handler声明为静态类,并且加上Activity的弱引用。
静态类不持有外部类的对象,所以你的Activity就可以被回收了。代码如下:
static class MyHandler extends Handler {
WeakReference<Activity > mActivityReference;
MyHandler(Activity activity) {
mActivityReference= new WeakReference<Activity>(activity);
}
@Override
public void handleMessage(Message msg) {
final Activity activity = mActivityReference.get();
ImageView imageView= (ImageView)activity.findViewById(R.id.imageview);
if (activity != null) {
mImageView.setImageBitmap(mBitmap);
}
}
}
事例二、
new Thread(new Runnable() {
@Override
public void run() {
SystemClock.sleep(10000);
}
}).start();
在Activity里声明了一个匿名内部类,如果Activity在销毁之前,线程的任务还未完成, 那么将导致Activity的内存资源无法回收,造成内存泄漏。
解决办法就是使用静态内部类,并且及时关闭线程,如下:
private static class MyThread extends Thread {
private boolean mRunning = false;
@Override
public void run() {
mRunning = true;
while (mRunning) {
SystemClock.sleep(1000);
}
}
public void close() {
mRunning = false;
}
}
我们在Activity退出时,可以在onDestroy()方法中显示的调用mThread.close();以此来结束该线程,这样在避免Activity OOM的同时也避免了线程的内存泄漏问题。
每一个非静态内部类实例都会持有一个外部类的引用,若该引用是Activity的引用,那么该Activity在被销毁时将无法被回收。如果你的静态内部类需要一个相关Activity的引用以确保功能能够正常运行,那么你得确保你在对象中使用的是一个Activity的弱引用。
这样看来static好像是解决OOM问题的重要武器,那是不是每个内部类都使用static来修改就好了呢,其实不然,只有当此类在全局多处用到时才这样做,因为static声明变量的生命周期其实是和APP的生命周期一样的,有点类似于Application。如果大量的使用的话,就会占据内存空间不释放,积少成多也会造成内存的不断开销,直至挂掉。static一般用来修饰基本数据类型或者轻量级对象,尽量避免修饰集合或者大对象,常用作修饰全局配置项、工具类方法、内部类。
GC回收的对象?
如果GC发现一个或一组对象为不可到达状态,则将该对象从内存中回收。也就是说,一个对象不被任何引用所指向,则该对象会在被GC发现的时候被回收;另外,如果一组对象中只包含互相的引用,而没有来自它们外部的引用(例如有两个对象A和B互相持有引用,但没有任何外部对象持有指向A或B的引用),这仍然属于不可到达,同样会被GC回收。
什么是WeakReference? OOM问题解决方法?
WeakReference弱引用,与强引用(即我们常说的引用)相对,它的特点是,GC在回收时会忽略掉弱引用,即就算有弱引用指向某对象,但只要该对象没有被强引用指向(实际上多数时候还要求没有软引用),该对象就会在被GC检查到时回收掉。如果对内存的开销比较关注的APP,可以考虑使用WeakReference,当GC回收扫过这块内存区域时就会回收;如果不是那么关注的话,可以使用SoftReference,它会在内存申请不足的情况下自动释放,同样也能解决OOM问题。同时Android自3.0以后也推出了LruCache类,使用LRU算法就释放内存,一样的能解决OOM,如果要兼容3.0以下的版本,需要导入v4包。
转载于:https://my.oschina.net/abcijkxyz/blog/724098