Android面试题(二)Android基础4

19、什么情况导致内存泄漏?

  • 资源释放问题:

程序代码的问题,长期保持某些资源,如Context,Cursor,IO流的引用,资源得不到释放造成内存泄漏.

  • 对象内存过大:

保存了多个耗用内存过大的对象,如 Bitmap,XML文件,造成内存超出限制。

  • static关键字的使用问题

static是java中的一个关键字,当用它修饰成员变量时,那么该变量就属于该类,而不是该类的实例。所以用static修饰的变量,它的生命周期是很长的,如果他用来引用一下资源耗费过多的实例(Context的情况最多),这时就要谨慎对待。

-------针对static的解决方案:1)应该尽量避免static成员变量引用资源耗费过多的实例。如Context。2)Context尽量使用ApplicationContext,因为Application的Context的生命周期比较长,引用它不会出现内训泄漏的问题。3)使用WeakReference代替引用,比如可以使用WeakReference mContextRef.

  • 线程导致内存溢出:

线程产生内存泄漏的主要原因是在于线程的生命周期不可控。

-------针对这种线程的内存泄漏问题的解决方案:1)将线程的内部类(因为非静态内部类拥有外部类对象的强引用,而静态类则不拥有)。2)在线程内部采用弱引用保存Context的引用。

  • 查询数据库没有关闭cursor:

程序中经常用到会进行查询数据库的操作,但是经常会使用完毕Cursor后没有关闭的情况,如果我们的查询结果集比较小,对内存的消耗不容易被发现,只有在长时间大量操作的情况下才会出现内存问题,这样就会给以后的测试和问题排查带来困难和风险。

六.构造Adapter没有复用ConvertView:

在使用listview的时候通常使用Adapter,那么 我们应该尽可能的使用ConvertView。为什么要复用ConvertView?当ContertView为空时,用setTag()方法为每一个View绑定一个存放控件的ViewHolder对象,当convertVIew不为空,重复利用已经创建的view的时候,使用getTag()方法获取绑定的ViewHolder对象,这样就避免了findViewById对控件的层层查询,而是快速定位到控件。

  • Bitmap不在使用时没有调用recycle()释放内存:

有时我们会手动的操作Bitmap对象比较占内存,当它不在被使用的时候,可以调用Bitmap.recycle()方法回收此对象的像素所占的内存,但这不是必须的,视情况而定。

20、内存泄漏和内存溢出区别?

内存溢出 out of memory,是指程序在申请内存时,没有足够的内存空间供其使用,出现out of memory;比如申请了一个integer,但给它存了long才能存下的数,那就是内存溢出。

内存泄露 memory leak,是指程序在申请内存后,无法释放已申请的内存空间,一次内存泄露危害可以忽略,但内存泄露堆积后果很严重,无论多少内存,迟早会被占光。

memory leak会最终会导致out of memory!

内存溢出就是你要求分配的内存超出了系统能给你的,系统不能满足需求,于是产生溢出。

21、如何通过广播拦截和abort一条短信?

短信接收方式也是通过广播来接收,而且这个广播是有序广播

  1. 首先添加接收短信的权限

  1. 在清单文件中注册广播接收器,设置该广播接收器优先级,尽量设高一点
  2. 创建一个BroadcastReceiver来实现广播的处理,并设置拦截器abortBroadcast();

22、广播是否可以请求网络?

子线程可以,主线程超过10s引起ANR

23、计算一个view的嵌套层级

while (view.getParents() != null) {

    count++;

    view = view.getParents();

}

24、Android线程有没有上限?

分析:

Android系统会给每个应用分配一个内存空间(不同的系统分配的内存大小不同),这块内存空间大小是有限的。

创建线程需要占用内存空间,

不可能拿有限的内存空间创建无限的线程。

结论:

Android线程是有上限的。如果应用创建线程的数量过多,而没有及时释放会导致OOM

25、线程池有没有上限?

线程池的由来:

创建太多线程,将会浪费一定的资源,有些线程未被充分使用。 销毁太多线程,将导致之后浪费时间再次创建它们。 创建线程太慢,将会导致长时间的等待,性能变差。 销毁线程太慢,导致其它线程资源饥饿

线程池从名字就可以看出,它是用来管理线程的,在线程池中,我们的线程不会被随意的创建出来,它可以缓存一定数量的线程,减少了资源的消耗,同时还可以指定线程的优先级,或者同时需要大量在耗时任务的时候,这些耗时操作是使用FIFO(先进先出)还是LIFO(后进先出)的策略。

总结来说,线程池的优点可以概括为以下三点:

(1)重用线程池中的线程,避免因为线程的创建和销毁所带来的性能开销

(2)能对线程进行简单的管理,并提供定时执行以及制定间隔循环执行等功能。

(3)能有效控制线程池的最大并发数,避免大量的线程之间因相互抢占系统资源而导致的阻塞现象

安卓真正的线程池实现是ThreadPoolExecutor,它提供了一系列参数来方便我们配置线程池,我们就来先看看这个ThreadPoolExecutor类线程池执行任务的规则

当向线程池中提交任务的时候,会满足以下规则:

如果线程池中的线程数量没有达到核心线程的数量,那么会直接启动一个核心线程来执行该任务。

如果线程池中的线程数量已经达到核心线程数,那么任务就会被插入到任务队列中排队等待执行,当核心线程空闲的时候,就会从任务队列中按照某种规则取出一个任务来执行

如果任务队列满了,或者由于其他原因,向线程池提交的任务不能插入到任务队列中的时候,这个时候就会去看线程池中的线程数是否达到线程池的上限,如果没有,就立即开启一个线程并执行。(增加非核心线程)

如果线程池中正在工作的线程数已经达到了线程池设置的上限,此时再向线程池中提交任务,线程池就会拒绝执行此任务

四类线程池

(1)FixedThreadPool

它是一种线程数量固定的线程池,它的核心线程和最大线程是相等的,即该线程池中的所有线程都是核心线程,所以它也并没有超时机制,而他的任务队列是无边界的任务队列,也就是可以添加无上限的任务,但是都会排队执行

(2)CachedThreadPool

它的核心线程数是0也就是说该线程池并没有核心线程,该线程池的最大线程数是没有上限的(int类型的上限),也就是说可以无限的创建线程。那么当新任务向线程池中提交的时候,如果有空闲线程,就会把任务放到空闲线程中去,如果没有空闲线程,就会开启一个新的线程来执行此任务,而它的队列SynchronousQueue是一个特殊的队列,在多数情况下,我们可以把它简单的理解为一个无法插入的队列(可能是直接创建线程执行了)。

比较适合执行大量的耗时少的任务,当线程池处于闲置状态的时候,线程池中的线程都会被销毁,这个时候该线程池几乎是不占用任何系统资源的

(3)ScheduledThreadPool

可以看出它的核心线程数是固定的,而最大线程数没有限制(int类型的上限),它与之前的线程池相区别的就是它的任务队列,DelayedWorkQueue能让任务周期性的执行,也就是说该线程池可以周期性的执行任务。

(4)SingleThreadExecutor

通过代码可以看出,这种线程池,就是FixedThreadPool但是实例化方法的参数是1的任务队列

在使用线程池的时候,我们确定线程池核心线程数的时候通常会根据CPU的核心数来确定的,通常会使用CPU核心数+1来定为当前线程池的核心线程数,在Android中的CPU核心数可以通过

Runtime.getRuntime().availableProcessors();来获得

26、Android为什么引入Parcelable?

实现Parcelable就是为了进行序列化,那么,为什么要序列化?

1)永久性保存对象,保存对象的字节序列到本地文件中;

2)通过序列化对象在网络中传递对象;

3)通过序列化在进程间传递对象。

 

(四)开发中常见的一些问题

1、ListView 中图片错位的问题是如何产生的?

图片错位问题的本质源于我们的 listview 使用了缓存 convertView,假设一种场景,一个 listview 一屏显示九个item,那么在拉出第十个 item 的时候,事实上该 item 是重复使用了第一个 item,也就是说在第一个 item 从网络中下载图片并最终要显示的时候,其实该 item 已经不在当前显示区域内了,此时显示的后果将可能在第十个 item 上输出图像,这就导致了图片错位的问题。所以解决之道在于可见则显示,不可见则不显示。

2、画出 Android 的大体架构图

Android面试题(二)Android基础4_第1张图片

3、Recycleview和ListView的区别

ListView

  • ListView的Adapter继承的是BaseAdapter;
  • ListView的ViewHolder不是强制要写的, 只是不写的话导致的后果就是如果数据一多,可能会导致OOM或者界面卡顿;
  • ListView的分割线直接在布局中设置 divider
  • ListView的点击事件直接是setOnItemClickListener

RecyclerView

  • RecyclerView的Adapter继承的是RecyclerView.Adapter
  • RecyclerView的ViewHolder是必须要写的,是强制的,如果不写的话,就不能重写RecyclerView.Adapter中的3个方法 getItemCount()、onCreateViewHolder()、onBindViewHolder()分别表示 总共显示多少条目、创建ViewHolder、绑定数据;
  • RecyclerView在setAdapter之前一定要设置显示的样式,否则数据不能显示

new LinearLayoutManager(this) ; -> 表示ListView

new GridLayoutManager(this , 3) ; -> 表示GridView,3表示1列显示的个数

new StraggeredGridLayoutManager() ;

  • RecyclerView不支持直接在布局中添加分割线
  • RecyclerView不支持点击事件,只能用回调接口来设置点击事件

在adapter中写

// 利用接口 -> 给RecyclerView设置点击事件 private ItemClickListener mItemClickListener ; public interface ItemClickListener{ public void onItemClick(int position) ; } public void setOnItemClickListener(ItemClickListener itemClickListener){ this.mItemClickListener = itemClickListener ; }

在绑定数据的onBindViewHolder中给所有控件设置完点击事件后判断mItemClickListener不为空的话,设置点击事件,利用回调接口来设置点击事件

@Override public void onBindViewHolder(CategoryListAdapter.ViewHolder holder, final int position) { // 在这里取出 Activity中请求接口的list集合数据,然后给 item 中 每个子控件去设置数据 ChannelListResult.DataBean. CategoriesBean.CategoryListBean item = mData.get(position) ; Glide.with(mContext).load(item.getIcon_url()).into(holder.channel_icon); // 名字 holder.channel_text.setText(item.getName()); // 内容 holder.channel_topic.setText(item.getIntro()); // 显示数据 因为最下边数据是:左边灰色,右边粉红色,所以这里使用html String str = item.getSubscribe_count() + " 订阅 | " + "总帖数 " + item.getTotal_updates() + ""; holder.channel_update_info.setText(Html.fromHtml(str)); // 点击事件一般都写在绑定数据这里,当然写到上边的创建布局时候也是可以的 if (mItemClickListener != null){ holder.itemView.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { // 这里利用回调来给RecyclerView设置点击事件 mItemClickListener.onItemClick(position); } }); } }

4、动态权限适配方案,权限组的概念

Android面试题(二)Android基础4_第2张图片

 动态权限申请流程图如上:对于在运行时需要申请权限的方法,在执行前需要做一系列的权限申请操作。

首先进行应用权限检查,检查应用是否有执行该方法所需要的权限,如有则可以正常运行,如没有则进行权限申请。

进行权限申请时,首先会检查一下用户之前是否有响应过该权限的申请,如没有,则显示权限申请对话框,如有,则检查用户之前是否有勾选“不再提醒”的选项,如没有,显示权限申请对话框,如有,则进行应用无权限异常运行。

显示权限对口框后,判断用户是否允许应用权限申请,如是,则进行应用有权限正常运行,如否,则进行应用无权限异常运行。

具体方法:

  1. 使用Context.checkSelfPermission()接口先检查权限是否授权。
  2. 使用Activity.shouldShowRequestPermissionRationale()接口检查用户是否勾选不再提醒。
  3. 第2步返回为true时,表示用户并未勾选不再提醒选项,使用Activity.requestPermissions()接口向系统请求权限。
  4. 第2步返回为false时,表示用户已勾选不再提醒选项,则应用该弹框提示用户。
  5. 第3步执行后,不论用户是否授予权限,都会回调Activity.onRequestPermissionsResult()的函数。在Activity中重载onRequestPermissionsResult()函数,在接收授权结果,根据不同的授权结果做相应的处理。

动态权限申请的适配方案:

对于低于M版本(安卓6.0)的Android系统,没有动态权限的申请问题,动态权限的申请流程对于低于M版本的Android系统也不再适用。所以适配方案,首先要考虑低于M版本的Android系统,因此对于Android版本小于M版本时,在检查权限时,直接返回true就OK,直接屏蔽后续流程。

对于M版本(安卓6.0)的Android系统,动态权限的申请流程会产生好几个分支处理逻辑,这样不善于管理和维护。所以对于此处,为了将写得代码更加整洁和更易于维护,我们可以将动态权限申请进行一次封装,

新建一个空白的activity用户权限申请和回调,然后在activity外包装一层管理内,限制一个调用的入口和出口,对于外部暴露唯一的入口和出口,这样对于外部逻辑代码需要调用权限时,将变得异常简单,并且由于将权限申请封装在了管理类中,

对于低于M版本的Android系统也将没有任何引用,在管理类中直接对于低于M版本的权限申请请求直接回调全部已授权即可。

实际App中动态权限申请代码如下:

  1. 由于权限的动态申请与管理应该是伴随着整个App的生命周期的,所以PermissionsManager设计为单例的,且初始化保存着applicationContext作为默认跳转方式。
  2. 在App代码中,应在自定义的Application中调用PermissionsManager中的initContext()方法。
  3. 在App代码中,在需要申请权限的地方调用PermissionsManager中的newRequestPermissions()方法即可,不论Android版本是低于M版本还是高于M版本,根据实际App是否拥有对应权限,回调传入的该次权限申请的PermissionsResultsCallback接口的onRequestPermissionsResult()方法。这样就将动态权限的申请结果处理流程给唯一化,将权限的申请的入口和出口都唯一化。
  4. 这样将权限申请的细节和Android系统版本适配的细节都封装在了PermissionsManager内部,对于应用外部只需要知道申请权限的入口以及申请权限接口的出口即可。

5、下拉状态栏是不是影响activity的生命周期

Android下拉通知栏不会影响Activity的生命周期方法

6、如果在onStop的时候做了网络请求,onResume的时候怎么恢复?

恢复的是网络请求暂停后恢复?还是页面更新?

stop的时候请求被暂停,onstart的时候检测重新恢复请求即可

如果是恢复页面请求后的页面数据,分两种,1 activity被销毁,那么使用saveInstanceState存储数据,onRestoreInstanceState()恢复数据,2,没有被销毁,那就不需要恢复

7、Bitmap 使用时候注意什么?

为了避免oom 那么我们应该怎么做呢

根据我总结的,

1,要选择合适的图片规格(bitmap类型),即:

        ALPHA_8  每个像素占用1byte内存

        ARGB_4444 每个像素占用2byte内存

        ARGB_8888 每个像素占用4byte内存  不设置的话默认这个。

        RGB_565 每个像素占用2byte内存

2,降低采样率。BitmapFactory.Options 参数inSampleSize的使用,先把options.inJustDecodeBounds设为true,只是去读取图片的大小,在拿到图片的大小之后和要显示的大小做比较通过calculateInSampleSize()函数计算inSampleSize的具体值,得到值之后。options.inJustDecodeBounds设为false读图片资源。

3,复用内存。即,通过软引用(内存不够的时候才会回收掉),复用内存块,不需要在重新给这个bitmap申请一块新的内存,避免了一次内存的分配和回收,从而改善了运行效率。

当一个Bitmap从内存缓存中移除掉的时候,把这个Bitmap加入到复用的Set集合里面去。判断是否有Bitmap可以复用的时候先去这个集合里面拿到Bitmap,然后按照复用图片的规则(Android4.4以下的平台,需要保证inBitmap和即将要得到decode的Bitmap的尺寸规格一致,Android4.4及其以上的平台,只需要满足inBitmap的尺寸大于要decode得到的Bitmap的尺寸规格即可)判断是否可以复用。

4,及时回收。即,recycle。

5,压缩图片。compress。

6,尽量不要使用setImageBitmap或setImageResource或BitmapFactory.decodeResource来设置一张大图,因为这些函数在完成decode后,最终都是通过java层的createBitmap来完成的,需要消耗更多内存,可以通过BitmapFactory.decodeStream方法,创建出一个bitmap,再将其设为ImageView的 source。

8、Bitmap的recycler()

如果只是使用少量的几张图片,回收与否关系不大。可是若有大量bitmap需要垃圾回收处理,那必然垃圾回收需要做的次数就更多也发生地更频繁,会对系统资源造成负荷。所以,这个时候还是自己试用recycle来释放的比较好。

Android面试题(二)Android基础4_第3张图片

9、Android中开启摄像头的主要步骤

  1. 获得摄像头管理器CameraManager mCameraManager,mCameraManager.openCamera()来打开摄像头
  2. 指定要打开的摄像头,并创建openCamera()所需要的CameraDevice.StateCallback stateCallback
  3. 在CameraDevice.StateCallback stateCallback中调用takePreview(),这个方法中,使用CaptureRequest.Builder创建预览需要的CameraRequest,并初始化了CameraCaptureSession,最后调用了setRepeatingRequest(previewRequest, null, childHandler)进行了预览
  4. 点击屏幕,调用takePicture(),这个方法内,最终调用了capture(mCaptureRequest, null, childHandler)
  5. 在new ImageReader.OnImageAvailableListener(){}回调方法中,将拍照拿到的图片进行展示

 

你可能感兴趣的:(Android,Java)