面试题总结-Android部分

1、Handler

1.1 简单介绍:

Handler是用来结合线程的消息队列来发送、处理“Message对象”和“Runnable对象”的工具。每一个Handler实例之后会关联一个线程和该线程的消息队列。当你创建一个Handler的时候,从这时开始,它就会自动关联到所在的线程/消息队列,然后它就会陆续把Message/Runnalbe分发到消息队列,并在它们出队的时候处理掉。
Looper
每一个线程只有一个Looper,每个线程在初始化Looper之后,然后Looper会维护好该线程的消息队列,用来存放Handler发送的Message,并处理消息队列出队的Message。它的特点是它跟它的线程是绑定的,处理消息也是在Looper所在的线程去处理,所以当我们在主线程创建Handler时,它就会跟主线程唯一的Looper绑定,从而我们使用Handler在子线程发消息时,最终也是在主线程处理,达到了异步的效果。
MessageQueue
MessageQueue是一个消息队列,用来存放Handler发送的消息。每个线程最多只有一个MessageQueue。MessageQueue通常都是由Looper来管理,而主线程创建时,会创建一个默认的Looper对象,而Looper对象的创建,将自动创建一个MessageQueue。其他非主线程,不会自动创建Looper。
Message: 消息对象,就是MessageQueue里面存放的对象,一个MessageQueu可以包括多个Message。当我们需要发送一个Message时,我们一般不建议使用new Message()的形式来创建,更推荐使用Message.obtain()来获取Message实例,因为在Message类里面定义了一个消息池,当消息池里存在未使用的消息时,便返回,如果没有未使用的消息,则通过new的方式创建返回,所以使用Message.obtain()的方式来获取实例可以大大减少当有大量Message对象而产生的垃圾回收问题。

1.2 内存泄漏

原因
(1)非静态内部类是会隐式持有外部类的引用,所以当其他线程持有了该Handler,线程没有被销毁,则意味着Activity会一直被Handler持有引用而无法导致回收。
(2)MessageQueue中如果存在未处理完的Message,Message的target也是对Activity等的持有引用,也会造成内存泄漏。
解决办法
(1)使用静态内部类+弱引用的方式
(2)在外部类对象被销毁时,将MessageQueue中的消息清空。例如,在Activity的onDestroy时将消息清空

  • 在使用Handler时,通常是通过Handler.obtainMessage()来获取Message对象的,而其内部调用的是Message.obtain()方法,那么问题来了,为什么不直接new一个Message,而是通过Message的静态方法obtain()来得到的呢?
    使用obtain获取Message对象是因为Message内部维护了一个数据缓存池,回收的Message不会被立马销毁,而是放入了缓存池, 在获取Message时会先从缓存池中去获取,缓存池为null才会去创建新的Message。
    ThreadLocal
    ThreadLocal 为解决多线程程序的并发问题提供了一种新的思路。使用这个工具类可以很简洁地编写出优美的多线程程序。当使用ThreadLocal 维护变量时,ThreadLocal 为每个使用该变量的线程提供独立的变量副本,所以每一个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本。

1.3 handler.postdelay方法中设置延时时间 这个时间准确么?为什么

当上一个消息存在耗时任务的时候,会占用延时任务执行的时机,此时是不准确的。由于Loop.loop里面消息是串行取出并发给handler.dispatchMessage的,那么轮到处理第二个延时runnable的时候,MessageQueue类的next方法再执行到if(now < msg.when)的时候,就立刻return了该msg,然后由handler.dispatchMessage处理,执行到该runnable的run方法

1.4 主线程中什么时候创建looper 为什么主线程中looper的死循环不会阻塞主线程
//ActivityThread.main()
public static void main(String[] args) {
        .... 
        //创建Looper和MessageQueue对象,用于处理主线程的消息
        Looper.prepareMainLooper(); 
        //创建ActivityThread对象
        ActivityThread thread = new ActivityThread();  
        //建立Binder通道 (创建新线程)
        thread.attach(false); 
        Looper.loop(); //消息循环运行
        throw new RuntimeException("Main thread loop unexpectedly exited");
    }
复制代码

ActivityThread的main方法主要就是做消息循环,一旦退出消息循环,那么你的应用也就退出了。Activity的生命周期都是依靠主线程的Looper.loop,当收到不同Message时则采用相应措施。我们的代码其实就是在这个循环里面去执行的,当然不会阻塞了。而且主线程Looper从消息队列读取消息,当读完所有消息时,主线程阻塞。子线程往消息队列发送消息,并且往管道文件写数据,主线程即被唤醒,从管道文件读取数据,主线程被唤醒只是为了读取消息,当消息读取完毕,再次睡眠。因此loop的循环并不会对CPU性能有过多的消耗。

1.5 handler内存泄漏持有的引用链

当使用内部类(包括匿名类)来创建Handler的时候,Handler对象会隐式地持有一个外部类对象(通常是一个Activity)的引用(不然你怎么可能通过Handler来操作Activity中的View?)。而Handler通常会伴随着一个耗时的后台线程(例如从网络拉取图片)一起出现,这个后台线程在任务执行完毕(例如图片下载完毕)之后,通过消息机制通知Handler,然后Handler把图片更新到界面。然而,如果用户在网络请求过程中关闭了Activity,正常情况下,Activity不再被使用,它就有可能在GC检查时被回收掉,但由于这时线程尚未执行完,而该线程持有Handler的引用(不然它怎么发消息给Handler?),这个Handler又持有Activity的引用,就导致该Activity无法被回收(即内存泄露),直到网络请求结束(例如图片下载完毕)。另外,如果你执行了Handler的postDelayed()方法,该方法会将你的Handler装入一个Message,并把这条Message推到MessageQueue中,那么在你设定的delay到达之前,会有一条MessageQueue -> Message -> Handler -> Activity的链,导致你的Activity被持有引用而无法被回收。处理方法:静态内部类+弱引用,还有一个就是handler.removeCallbacksAndMessages(null);,就是移除所有的消息和回调,简单一句话就是清空了消息队列。

2 优化

2.1 lru算法原理

LRU(Least Recently Used)是近期最少使用的算法,它的核心思想是当缓存满时,会优先淘汰那些近期最少使用的缓存对象。LruCache中维护了一个集合LinkedHashMap,该LinkedHashMap是以访问顺序排序的。当调用put()方法时,就会在结合中添加元素,并调用trimToSize()判断缓存是否已满,如果满了就用LinkedHashMap的迭代器删除队尾元素,即近期最少访问的元素。当调用get()方法访问缓存对象时,就会调用LinkedHashMap的get()方法获得对应集合元素,同时会更新该元素到队头。

2.2 内存优化
  • 内存泄露

    • 单例(主要原因还是因为一般情况下单例都是全局的,有时候会引用一些实际生命周期比较短的变量,导致其无法释放) 静态变量(同样也是因为生命周期比较长)
    • Handler内存泄露
    • 非静态内部类、匿名内部类(会引用外部类,导致无法释放,比如各种回调)
    • 资源使用完未关闭(BraodcastReceiver,ContentObserver,File,Cursor,Stream,Bitmap)
    • 集合中的对象未清理
  • 图片Bitmap相关

    • 使用完毕后释放图片资源
      • bitmap recycle()
      • 采用软引用(softreference)
    • 根据分辨率适配&缩放图片
      • 设置多套图片资源
      • BitmapFactory.decodeResource()
      • BitmapFactory.inSampleSize
  • 内存抖动 大量临时小对象频繁创建会导致内存碎片,程序频繁分配内存&垃圾收集器频繁回收内存。导致卡顿甚至内存溢出。

  • 代码质量 & 数量
    代码本身的质量(如 数据结构、数据类型等) & 数量(代码量的大小)可能会导致大量的内存问题,如占用内存大、内存利用率低等 日常不正确使用

  • 内存泄漏原因: 没有用的对象无法回收的现象就是内存泄露。当一个对象已经不需要再使用了,本该被回收时,而有另外一个正在使用的对象持有它的引用从而就导致,对象不能被回收。这种导致了本该被回收的对象不能被回收而停留在堆内存中,就产生了内存泄漏。
2.2 视图优化
  • 避免复杂的View层级。布局越复杂就越臃肿,就越容易出现性能问题,寻找最节省资源的方式去展示嵌套的内容;

  • 尽量避免在视图层级的顶层使用相对布局 RelativeLayout 。相对布局 RelativeLayout 比较耗资源,因为一个相对布局 RelativeLayout 需要两次度量来确保自己处理了所有的布局关系

  • 布局层级一样的情况建议使用线性布局 LinearLayout 代替相对布局 RelativeLayout,因为线性布局 LinearLayout 性能要更高一些

  • 不要使用绝对布局 AbsoluteLayout

  • 将可重复使用的组件抽取出来并用include标签进行重用。如果应用多个地方的 UI用到某个布局,就将其写成一个布局部件,便于各个 UI 重用

  • 使用 merge 标签减少布局的嵌套层次
    帮助include标签排除多余的一层ViewGroup容器,减少view hierarchy的结构,提升UI渲染的性能

    • 根布局是FrameLayout且不需要设置background或padding等属性,可以用merge代替,因为Activity的ContentView父元素就是FrameLayout,所以可以用merge消除只剩一个.
    • 因为merge标签并不是View,所以在通过LayoutInflate.inflate()方法渲染的时候,第二个参数必须指定一个父容器,且第三个参数必须为true,也就是必须为merge下的视图指定一个父亲节点.由于merge不是View所以对merge标签设置的所有属性都是无效的.
  • 使用 ViewStub 标签来加载一些不常用的布局
    ViewStub也可以用来加载布局文件,但与include标签完全不同。ViewStub是一个不可见的View类,用于在运行时按需懒加载资源,只有在代码中调用了viewStub.inflate()或者viewStub.setVisible(View.visible)方法时才内容才变得可见。

    • ViewStub标签不支持merge标签。因此这有可能导致加载出来的布局存在着多余的嵌套结构,具体如何去取舍就要根据各自的实际情况来决定了。
    • ViewStub的inflate只能被调用一次,第二次调用会抛出异常。
    • 虽然ViewStub是不占用任何空间的,但是每个布局都必须要指定layout_width和layout_height属性,否则运行就会报错。
  • 优化应用的启动速度。当应用启动一个应用时,界面的尽快反馈显示可以给用户一个良好的体验。为了启动更快,可以延迟加载一些 UI 以及避免在应用 Application 层级初始化代码。

  • 使用include标签提高布局重用性
    将一些通用的视图提取到一个单独的layout文件中,然后使用标签在需要使用的其他layout布局文件中加载进来,比如我们自己App导航栏等。这样,便于对相同视图内容进行统一的控制管理,提高布局重用性。

    *标签当中,可以重写所有layout属性的,如上面include中指定的layout属性将会覆盖掉titlebar中指定的layout属性。 而非layout属性则无法在标签当中进行覆写。另外需要注意的是,如果我们想要在标签当中覆写layout属性, 必须要将layout_width和layout_height这两个属性也进行覆写,否则覆写效果将不会生效

    • 一个xml布局文件有多个include标签需要设置ID,才能找到相应子View的控件,否则只能找到第一个include的layout布局,以及该布局的控件。

3 View

3.1 如何阻止父view拦截子view
  • 外部拦截法 重写父容器的onInterceptTouchEvent方法

  • 内部拦截法

 //重写这个方法,并且在方法里面请求所有的父控件都不要拦截他的事件
    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        getParent().requestDisallowInterceptTouchEvent(true);
        return super.dispatchTouchEvent(ev);
    }
复制代码
3.2 surfaceView textureView区别
            SurfaceView        TexttureView
层级        单独               普通view
能否动画    否                 能                       
硬件加速                       必须
能否覆盖    否
消耗内存    少                 多
使用版本                       4.0以后
复制代码
3.3 onTouch里面处理了onClick还能接收到吗

onTouchListener的onTouch方法优先级比onTouchEvent高,会先触发。假如onTouch方法返回false会接着触发onTouchEvent,反之onTouchEvent方法不会被调用。只要view的CLICKABLE和LONG_CLICKABLE有一个为true,那么他就会消耗这个事件,即onTouchEvent方法返回true,不管它是不是DISABLE状态。然后就是当ACTION_UP事件发生时,会出发performClick方法,如果View设置了OnClickListener,那么performClick方法内部会调用它的onClick方法。总结:onTouch—–>onTouchEvent—>onclick

3.4 卡顿问题分析
  • xml 写的一个布局是如何加载到Acitivty/Fragment中并最终 display

CPU 会先把 Layout 中的 UI 组件计算成 polygons(多边形)和 textures(纹理),然后经过 OpenGL ES 处理。OpenGL ES处理完后再交给 GPU 进行栅格化渲染,渲染后 GPU 再将数据传送给屏幕,由屏幕进行绘制显示。

  • 关于VSYNC
    Vertical Synchronization,就是所谓的“垂直同步”。我们也可以把它理解为“帧同步”。就是为了保证 CPU、GPU 生成帧的速度和 Display 刷新的速度保持一致。在 VSYNC 开始发出信号时,CPU和GPU已经就开始准备下一帧的数据了,赶在下个 VSYNC 信号到来时,GPU 渲染完成,及时传送数据给屏幕,Display 绘制显示完成。

  • 双缓冲机制
    双缓冲技术一直贯穿整个 Android 系统。因为实际上帧的数据就是保存在两个 Buffer 缓冲区中,A 缓冲用来显示当前帧,那么 B 缓冲就用来缓存下一帧的数据,同理,B显示时,A就用来缓冲!这样就可以做到一边显示一边处理下一帧的数据。

  • 丢帧
    由于某些原因,比如我们应用代码上逻辑处理过于负责或者过于复杂的布局,过度绘制(Overdraw),UI线程的复杂运算,频繁的GC等,导致下一帧绘制的时间超过了16ms,用户很明显感知到了卡顿的出现,也就是所谓的丢帧情况。

    • 1、当 Display 显示第 0 帧数据时,此时 CPU 和 GPU 已经开始渲染第 1 帧画面,并将数据缓存在缓冲 B 中。但是由于某些原因,就好像上面说的,导致系统处理该帧数据耗时过长或者未能及时处理该帧数据。

    • 2、当 VSYNC 信号来时,Display 向 B 缓冲要数据,因为缓冲 B 的数据还没准备好,B缓冲区这时候是被锁定的,Display 表示你没准备好,只能继续显示之前缓冲 A 的那一帧,此时缓冲 A 的数据也不能被清空和交换数据。这种情况就是所谓的“丢帧”,也被称作“废帧”;当第 1 帧数据(即缓冲 B 数据)准备完成后,它并不会马上被显示,而是要等待下一个 VSYNC,Display 刷新后,这时用户才看到画面的更新。

    • 3、当某一处丢帧后,大概率会影响后面的绘制也出现丢帧,最后给用户感觉就是卡顿了。最严重的直接造成ANR。

  • 卡顿处理

    • 过于复杂的布局
    • 过度绘制( Overdraw )
    • UI 线程的复杂运算
    • 频繁的 GC
3.5判断点击还是移动

在up事件里相应判断

3.6 Android Y轴旋转动画

public class RotateYAnimation extends Animation {
    int centerX, centerY;
    Camera camera = new Camera();
 
    /**
     * 获取坐标,定义动画时间
     * @param width
     * @param height
     * @param parentWidth
     * @param parentHeight
     */
    @Override
    public void initialize(int width, int height, int parentWidth, int parentHeight) {
        super.initialize(width, height, parentWidth, parentHeight);
        //获得中心点坐标
        centerX = width / 2;
        centerY = width / 2;
        //动画执行时间 自行定义
        setInterpolator(new OvershootInterpolator());
    }
 
    /**
     * 旋转的角度设置
     * @param interpolatedTime
     * @param t
     */
 
    @Override
    protected void applyTransformation(float interpolatedTime, Transformation t) {
        final Matrix matrix = t.getMatrix();
        camera.save();
        //设置camera的位置
        camera.setLocation(0,0,180);
        camera.rotateY(180 * interpolatedTime);
        //把我们的摄像头加在变换矩阵上
        camera.getMatrix(matrix);
        //设置翻转中心点
        matrix.preTranslate(-centerX, -centerY);
        matrix.postTranslate(centerX,centerY);
        camera.restore();
    }
 
}
复制代码
3.7 列表优化
  • 数据处理
    很多时候从我们要对服务器上获取下来的列表数据进行一次二次加工,以便转化为我们界面上要显示的数据,这些操作可能会比较耗时。比如字符串拼接、时间格式化等操作都是比较耗时的操作。比较好的实践是将列表数据的加工在notifyDataSetChanged()之前在后台线程做好,在Adapter中只做数据的绑定。

  • 界面优化 优化布局层级减少过渡绘制、优化布局方式提高布局效率。

  • 避免非必要的刷新
    如列表控件,在滑动时不要去加载图片,可以在滑动监听里停止图片的加载。

3.8 安卓中invalidate和requestLayout的实现和区别

3.9 android自定义view重写及调用的一些方法
  • onMeasure
    测量本质就是测量本身有多大,也就是给mMeasuredWidth和mMeasuredHeight这两个属性赋值,也就是调用setMeasuredDimension这个方法。另外父view测量子view的时候调用的measure方法,还有一些衍生方法如measureChildWithMargins。

  • onLayout
    作用是子view应该怎样放置,也就是设置子view的mLeft、mTop、mRight、mBottom属性。该方法在View中是空实现,很显然主要用于ViewGroup。父view放置子view的时候调用layout方法。

  • onDraw
    具体长什么样。

4 四大组件(除Activity)

4.1 Broadcast
  • 广播类型
    • Normal Broadcast:普通广播
    • System Broadcast: 系统广播
    • Ordered broadcast:有序广播
    • Sticky Broadcast:粘性广播
    • Local Broadcast:App应用内广播
4.2 contentprovider
  • contentprovider是运行在哪个进程里面的
    contentprovider的oncreate方法,运行在ui线程。但是其他的方法,运行在非ui线程,例如call、query、delete、insert、upate等方法

  • 别的主线程调用它的时候会被阻塞吗?
    别的主线程调contentprovider里面方法的时候,虽然他的call、query、delete、insert、upate等方法运行在非ui线程,但是其他调用方法是会被阻塞的。比如你在activity的oncreate方法中调用contentprovider的query等方法,oncreate方法会被阻塞

  • 如果不同的其他应用,同时调用了这个contentprovider的同一个方法,它们会相互阻塞吗?比如有三个应用同时都在调用这个provider的插入方法,它们会相互阻塞还是并发运行
    不管Provider访问者是同一个进程的不同线程,还是不同的进程,Provider方实际上是同一个Provider对象实例,当并发访问CP的同一个方法的时候,这个方法运行在不同的线程中,不会相互影响

4.3 Service
  • 两种服务启动区别

    • 启动状态,主要用于执行后台计算。startservice启动服务后,程序退出,服务依旧存在
    • 绑定状态,主要用于其他组件和service交互。bindservice启动服务后程序退出unbindservice,服务就会销毁
  • 同时用两种方式启动同一个service 会产生几个service实例?
    同时调用两种方法启动同一个方法,只会启动一个服务,但是其生命周期有所不同,取决于两种方法启动服务的先后顺序。

  • 绑定的service 页面关闭后未解除绑定 如何销毁
    bindService时LoadApk将ServiceConnection用map保存了起来,当Activity被destroy时会执行removeContextRegistrations来清除 该context的相关注册。所以Activity退出时服务也被解绑。

  • Intentservice原理
    介绍:
    IntentService是继承于Service并处理异步请求的一个类,在IntentService内有一个工作线程来处理耗时操作,启动IntentService的方式和启动传统Service一样,同时,当任务执行完后,IntentService会自动停止,而不需要我们去手动控制。另外,可以启动IntentService多次,而每一个耗时操作会以工作队列的方式在IntentService的onHandleIntent回调方法中执行,并且,每次只会执行一个工作线程,执行完第一个再执行第二个,以此类推。
    原理:
    IntentService第一次启动时会调用onCreate方法,创建一个HandlerThread对象,并开启HandlerThread线程,然后使用HandlerThread的Looper对象初始化mServiceHandler对象,通过mServiceHandler发送消息在HandlerThread中执行操作。每次启动IntentService都会调用onStartCommand()方法,onStartCommand方法会调用onStart方法。onStart方法中只是调用mServiceHandler发送了一个消息,交给HandlerThread处理,HandlerThread在handleMsg方法里调用handleIntent处理消息,完成后调用stop方法停止任务。

5 框架原理

  • OKHttp:使用了责任链模式,通过一层一层的拦截器最终将请求处理完全返回响应回调
  • Retrofit:动态代理
  • Dagger:注解反射
  • RxJava:响应式编程+观察者模式
5.1 okhttp拦截器

5.2 okhttp运行流程

5.3 Retrofit的本质流程

6 多线程问题

6.1 IPC 都有哪些方式
* Bundle     
四大组件中的三大组件(Activity,BroadcaseReceiver,Service)都是支持在Intent中传递Bundle数据的,由于Bundle实现了Parcelable接口,所以可以很方便的在不同进程间传输。
* 使用文件共享     
享文件也是一种不错的进程间通信方式,两个进程通过读/写同一个文件来交换数据。但有一点要注意:android系统是基于Linux的,使得其并发读/写文件可以没限制地进行,甚至两线程同时对同一文件进行写操作都是允许的,尽管这可能出问题。So,重点:文件共享方式适合在对数据同步要求不高的进程间进行通信,并且要妥善处理并发读/写问题。
* Messenger     
Messenger译为信使,顾名思义,主要作用就是传递消息。通过它可在不同进程中传递Message对象,在Message中放入要传递的数据,即可轻松地实现数据的进程间传递了。Messenger是一种轻量级的IPC方案,底层实现是AIDL。
* AIDL     
上面说到Messenger,其是以串行的方式处理客户端发来的消息,如果有大量的并发请求,那么使用Messenger就不太合适了。同时Messenger主要作用就是传递消息,很多时候我们可能需要跨进程调用服务端的方法,这种情形Messenger就无法做到了,但是我们可以使用AIDL来实现跨进程的方法调用。
* ContentProvider     
ContentProvider是Android中提供的专门用于不同应用间进行数据共享的方式,从这一点,它天生就适合进程间通信。和Messenger一样,contentProvider的底层实现同样是Binder,由此可见,Binder在Android中是何等的重要。虽然ContentProvider底层是用Binder,但它的使用过程要比AIDL 简单许多,因为系统已经做了封装。系统预置了许多ContentProvider,比如通讯录信息,日程变信息等,要跨进程访问这些信息,只需要通过ContentResolver的query、update、insert和delete方法即可。
* Socket     
Socket 也称为“套接字”。是网络通信中的概念,它分为流式套接字和用户数据报套接字两种,分别对应于网络传输控制层中的TCP和UDP协议。TCP是面向连接的协议,提供稳定的双向通信功能,TCP连接的建立需要经过“三次握手”才能完成,为了提供稳定的数据传输功能,其本身提供了超时重传机制,因此具有很高的稳定性。而UDP是无连接的,提供不稳定的单向通信功能,当然UDP也能实现双向通信功能。在性能上,UDP 具有更高的效率,缺点是不保证数据一定能够正确传输,尤其是在网络阻塞的情况下。
复制代码
6.2 aild 具体实现过程
* 服务端    
服务端首先要创建一个Service用来监听客户端的连接请求,然后创建一个AIDL文件,将暴露给客户端的接口在这个AIDL文件中声明,最后在Service中实现这个接口即可。&emsp;&emsp;&emsp;&emsp;
* 客户端    
客户端所要做的事情就稍微简单一些,首先需要绑定服务Service,绑定成功后,将服务端的Binder对象转成AIDL借口所属的类型,接着就可以调用AIDL中的方法了。
* AIDL接口创建     
创建一个后缀为AIDL的文件,里面声明接口和方法。
* 远程服务端Service的实现    
创建一个Binder对象并在onBind中返回它,这个对象继承自.Stub并实现了它内部的AIDL方法
* 客户端的实现    
首先绑定远程服务,绑定成功后将服务端返回的Binder对象转换成AIDL接口,然后就可以通过这个接口去调用服务端的远程方法
复制代码
6.3 Android 为什么用Binder实现ipc机制

Android系统是基于Linux系统的,理论上应该使用Linux内置的IPC方式。Linux中的IPC方式有管道、信号量、共享内存、消息队列、Socket,Android使用的Binder机制不属于Linux。Android不继承Linux中原有的IPC方式,而选择使用Binder,说明Binder具有一定的优势。 Android系统为了向应用开发者提供丰富的功能,广泛的使用了Client-Server通信方式,如媒体播放、各种传感器等,Client-Server的通信方式是Android IPC的核心,应用程序只需要作为Client端,与这些Server建立连接,即可使用这些功能服务。

  下面通过一些功能点,一一排除Linux系统中五种IPC方式,解释为什么Android选择使用Binder:

  • 从通信方式上说,我们希望得到的是一种Client-Server的通信方式,但在Linux的五种IPC机制中,只有Socket支持这种通信方式。虽然我们可以通过在另外四种方式的基础上架设一些协议来实现Client-Server通信,但这样增加了系统的复杂性,在手机这种条件复杂、资源稀缺的环境下,也难以保证可靠性;

  • 从传输性能上说,Socket作为一款通用接口,其传输效率低,开销大,主要用在跨网络的进程间通信和本机上进程间的低速通信;消息队列和管道采用存储-转发方式,即数据先从发送方拷贝到内存开辟的缓存区中,然后再从内核缓存区拷贝到接收方缓存区,至少有两次拷贝过程;共享内存虽然无需拷贝,但控制复杂,难以使用;而Binder只需要拷贝一次;

  • 从安全性上说,Android作为一个开放式的平台,应用程序的来源广泛,因此确保只能终端的安全是非常重要的。Linux传统的IPC没有任何安全措施,完全依赖上层协议来确保,具体有以下两点表现:

    • 第一,传统IPC的接收方无法获得对方可靠的UID/PID(用户ID/进程ID),从而无法鉴别对方身份,使用传统IPC时只能由用户在数据包里填入UID/PID,但这样不可靠,容易被恶意程序利用;
    • 第二,传统IPC的访问接入点是开放的,无法建立私有通信,只要知道这些接入点的程序都可以和对端建立连接,这样无法阻止恶意程序通过猜测接收方的地址获得连接。   基于以上原因,Android需要建立一套新的IPC机制来满足系统对通信方式、传输性能和安全性的要求,这就是Binder。

  综上,Binder是一种基于Client-Server通信模式的通信方式,传输过程只需要一次拷贝,可以为发送方添加UID/PID身份,支持实名Binder和匿名Binder,安全性高。

  

6.4 子线程回调主线程的方式
  • view.post(Runnable action)
  • activity.runOnUiThread(Runnable action)
  • Handler

7 Activity和Fragment

7.1 生命周期
  • Singletask 生命周期 launchMode为singleTask的时候,通过Intent启到一个Activity,如果系统已经存在一个实例,系统就会将请求发送到这个实例上,但这个时候,系统就不会再调用通常情况下我们处理请求数据的onCreate方法,而是调用onNewIntent方法。
    onNewIntent->onRestart->onStart->onResume

  • Android onPause和onStop的比较

    • onPause():Activity失去焦点,但仍然可见。
    • onStop():Activity在后台,不可见(完全被另一个Activity挡住,或者程序后台运行)。
7.2 FragmentManager
  • Fragmentmanager和supportfm 区别

    • 3.0以下:getSupportFragmentManager()

    • 3.0以上:getFragmentManager()

  • 嵌套fragment获取manager getChildFragmentManager()是fragment中的方法, 返回的是管理当前fragment内部子fragments的manage

7.3 Fragment生命周期

7.4 Fragment回退键监听
  • 在Fragment中onResume监听返回键事件
@Override
public void onResume() {
    super.onResume();
    getView().setFocusableInTouchMode(true);
    getView().requestFocus();
    getView().setOnKeyListener(new View.OnKeyListener() {
        @Override
        public boolean onKey(View view, int i, KeyEvent keyEvent) {
            if(keyEvent.getAction() == KeyEvent.ACTION_DOWN && i == KeyEvent.KEYCODE_BACK){
                Toast.makeText(getActivity(), "按了返回键", Toast.LENGTH_SHORT).show();
                return true;
            }
            return false;
        }
    });
}
复制代码
  • 优雅的方法 *
// ①先定义接口BackHandleInterface
public interface BackHandleInterface {

    void onSelectedFragment(BackHandleFragment backHandleFragment);

}
复制代码
// ②定义公用的Fragment
public abstract class BackHandleFragment extends Fragment {

    private BackHandleInterface backHandleInterface;

    /**
     * 所有继承BackHandledFragment的子类都将在这个方法中实现物理Back键按下后的逻辑
     * FragmentActivity捕捉到物理返回键点击事件后会首先询问Fragment是否消费该事件
     * 如果没有Fragment消息时FragmentActivity自己才会消费该事件
     */
    public abstract boolean onBackPressed();

    @Override
    public void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        if(getActivity() instanceof BackHandleInterface){
            this.backHandleInterface = (BackHandleInterface)getActivity();
        }else{
            throw new ClassCastException("Hosting Activity must implement BackHandledInterface");
        }
    }

    @Override
    public void onStart() {
        super.onStart();
        backHandleInterface.onSelectedFragment(this);
    }
}
复制代码
//需要实现监听的Fragment的Activity实现接口
//主要的是onSelectedFragment()和onBackPressed()其他方法可以忽略
public class EdittextActivity extends AppCompatActivity implements BackHandleInterface {

    private BackHandleFragment backHandleFragment;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_layout);
        addFragment(R.id.fragmentContainer, new EdittextFragment());
    }

    public void addFragment(int containerViewId, Fragment fragment){
        final FragmentTransaction transaction = this.getSupportFragmentManager().beginTransaction();
        transaction.add(containerViewId, fragment);
        transaction.commit();
    }

    @Override
    public void onSelectedFragment(BackHandleFragment backHandleFragment) {
        this.backHandleFragment = backHandleFragment;
    }

    @Override
    public void onBackPressed() {
        //if判断里面就调用了来自Fragment的onBackPressed()
        //一样!!,如果onBackPressed是返回false,就会进入条件内进行默认的操作
        if(backHandleFragment == null || !backHandleFragment.onBackPressed()){
            if(getSupportFragmentManager().getBackStackEntryCount() == 0){
                super.onBackPressed();
            }else{
                getSupportFragmentManager().popBackStack();
            }
        }
    }
}
复制代码
//需要监听的Fragment
public class EdittextFragment extends BackHandleFragment {

    @Override
    public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.fragment_edittext, container, false);
        return view;
    }

    @Override
    public boolean onBackPressed() {
        Toast.makeText(getActivity(), "按了返回键", Toast.LENGTH_SHORT).show();
        return true;//因为这里return true 所以不会返回上一个页面,方便我截图
    }
}
复制代码
7.5 全局弹窗怎么获取当前activity引用
  • 利用系统对话框AlertDialog的简单实现    

alter为AlertDialog类型对象,注意要在alter.show()语句前加入

alert.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT);
复制代码

然后在AndroidManifest.xml中加入权限:

<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"></uses-permission> 
复制代码
  • Application.ActivityLifecycleCallbacks

8 其他

8.1 MVP框架模型
  • 优点:

    • 1、降低耦合度,隐藏数据逻辑,减轻Activity或是Fragment的压力,让其更多的只关注处理生命周期任务,使得代码更加简洁明
    • 2、模块职责划分明显,视图逻辑和业务逻辑分别抽象到V层和P层的接口中去,提高代码可阅读性,复用度较高,灵活性高
    • 3、方便测试驱动开发
    • 4、减少Activity的内存泄露问题
  • 缺点:
    最明显的创建一个Activity需要配合创建多个接口类和实现类,每个操作都需要通过接口回调的方式进行,虽然逻辑清晰代码,同时也造成了类的增多和代码量的加大。

8.2 Sp.commit.apply区别
  • apply没有返回值而commit返回boolean表明修改是否提交成功
  • apply是将修改数据原子提交到内存, 而后异步真正提交到硬件磁盘, 而commit是同步的提交到硬件磁盘,因此,在多个并发的提交commit的时候,他们会等待正在处理的commit保存到磁盘后在操作,从而降低了效率。而apply只是原子的提交到内容,后面有调用apply的函数的将会直接覆盖前面的内存数据,这样从一定程度上提高了很多效率。
  • apply方法不会提示任何失败的提示。 由于在一个进程中,sharedPreference是单实例,一般不会出现并发冲突,如果对提交的结果不关心的话,建议使用apply,当然需要确保提交成功且有后续操作的话,还是需要用commit的。
8.3 sp的进程安全与线程安全

官方文档明确指出,SharedPreferences不支持多线程,进程也是不安全的 如果想要实现线程安全需重新实现其接口,如下:

private static final class SharedPreferencesImpl implements SharedPreferences {
...
    public String getString(String key, String defValue) {
        synchronized (this) {
            String v = (String)mMap.get(key);
            return v != null ? v : defValue;
        }
   }
...
    public final class EditorImpl implements Editor {
        public Editor putString(String key, String value) {
            synchronized (this) {
                mModified.put(key, value);
                return this;
            }
        }
    ...
    }
}
复制代码
8.4 dexclassloader和pathclassloader 双亲委托模型

PathClassLoader和DexClassLoader都是继承与BaseDexClassLoader,BaseDexClassLoader继承与ClassLoader。DexClassLoader:能够加载自定义的jar/apk/dex PathClassLoader:只能加载系统中已经安装过的apk 所以Android系统默认的类加载器为PathClassLoader,而DexClassLoader可以像JVM的ClassLoader一样提供动态加载。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值