面试题基础

定位项目中,如何选取定位方案,如何平衡耗电与实时位置的精度?

重要

开始定位,Application 持有一个全局的公共位置对象,然后隔一定时间自动刷新位置,每次刷新成功都把新的位置信息赋值到全局的位置对象, 然后每个需要使用位置请求的地方都使用全局的位置信息进行请求。

该方案好处:请求的时候无需再反复定位,每次请求都使用全局的位置对象,节省时间。

该方案弊端:耗电,每隔一定时间自动刷新位置,对电量的消耗比较大。复制代码

按需定位,每次请求前都进行定位。这样做的好处是比较省电,而且节省资源,但是请求时间会变得相对较长。

Activity间通过Intent传递数据大小有没有限制?

重要

Intent在传递数据时是有大小限制的,这里官方并未详细说明,不过通过实验的方法可以测出数据应该被限制在1MB之内(1024KB),笔者采用的是传递Bitmap的方法,发现当图片大小超过1024(准确地说是1020左右)的时候,程序就会出现闪退、停止运行等异常(不同的手机反应不同),因此可以判断Intent的传输容量在1MB之内。

子线程发消息到主线程进行更新 UI,除了 handler 和 AsyncTask,还有什么?

一般

用 Activity 对象的 runOnUiThread 方法更新

在子线程中通过 runOnUiThread()方法更新 UI:

如果在非上下文类中(Activity),可以通过传递上下文实现调用;复制代码

用 View.post(Runnable r)方法更新 UI


SQLite支持事务吗? 添加删除如何提高性能?

一般

在sqlite插入数据的时候默认一条语句就是一个事务,有多少条数据就有多少次磁盘操作 比如5000条记录也就是要5000次读写磁盘操作。

添加事务处理,把多条记录的插入或者删除作为一个事务


什么是 IntentService?有何优点?

重要

IntentService 是 Service 的子类,比普通的 Service 增加了额外的功能。先看 Service 本身存在两个问题:

Service 不会专门启动一条单独的进程,Service 与它所在应用位于同一个进程中;

Service 也不是专门一条新线程,因此不应该在 Service 中直接处理耗时的任务;复制代码

IntentService 特征

会创建独立的 worker 线程来处理所有的 Intent 请求;

会创建独立的 worker 线程来处理 onHandleIntent()方法实现的代码,无需处理多线程问题;

所有请求处理完成后,IntentService 会自动停止,无需调用 stopSelf()方法停止 Service;

为 Service 的 onBind()提供默认实现,返回null;

为 Service 的 onStartCommand 提供默认实现,将请求 Intent 添加到队列中


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

重要

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


Manifest.xml文件中主要包括哪些信息?

重要

manifest:根节点,描述了package中所有的内容。

uses-permission:请求你的package正常运作所需赋予的安全许可。

permission: 声明了安全许可来限制哪些程序能你package中的组件和功能。

instrumentation:声明了用来测试此package或其他package指令组件的代码。

application:包含package中application级别组件声明的根节点。

activity:Activity是用来与用户交互的主要工具。

receiver:IntentReceiver能使的application获得数据的改变或者发生的操作,即使它当前不在运行。

service:Service是能在后台运行任意时间的组件。

provider:ContentProvider是用来管理持久化数据并发布给其他应用程序使用的组件。复制代码


Activity的形态

重要

Active/Running:

Activity处于活动状态,此时Activity处于栈顶,是可见状态,可与用户进行交互。

Paused:

当Activity失去焦点时,或被一个新的非全屏的Activity,或被一个透明的Activity放置在栈顶时,Activity就转化为Paused状态。但我们需要明白,此时Activity只是失去了与用户交互的能力,其所有的状态信息及其成员变量都还存在,只有在系统内存紧张的情况下,才有可能被系统回收掉。

Stopped:

当一个Activity被另一个Activity完全覆盖时,被覆盖的Activity就会进入Stopped状态,此时它不再可见,但是跟Paused状态一样保持着其所有状态信息及其成员变量。

Killed:

当Activity被系统回收掉时,Activity就处于Killed状态。

Activity会在以上四种形态中相互切换,至于如何切换,这因用户的操作不同而异。了解了Activity的4种形态后,我们就来聊聊Activity的生命周期。


Fragemnt

非常重要

fragemnt

创建方式

(1)静态创建

首先我们需要创建一个xml文件,然后创建与之对应的java文件,通过onCreatView()的返回方法进行关联,最后我们需要在Activity中进行配置相关参数即在Activity的xml文件中放上fragment的位置。

        android:name="xxx.BlankFragment"

        android:layout_width="match_parent"

        android:layout_height="match_parent">


(2)动态创建

动态创建Fragment主要有以下几个步骤:

创建待添加的fragment实例。

获取FragmentManager,在Activity中可以直接通过调用 getSupportFragmentManager()方法得到。

开启一个事务,通过调用beginTransaction()方法开启。

向容器内添加或替换fragment,一般使用repalce()方法实现,需要传入容器的id和待添加的fragment实例。

提交事务,调用commit()方法来完成。

Adapter对比

FragmnetPageAdapter在每次切换页面时,只是将Fragment进行分离,适合页面较少的Fragment使用以保存一些内存,对系统内存不会多大影响。

FragmentPageStateAdapter在每次切换页面的时候,是将Fragment进行回收,适合页面较多的Fragment使用,这样就不会消耗更多的内存

Activity生命周期

Activity的生命周期如下图:

(1)动态加载:

动态加载时,Activity的onCreate()调用完,才开始加载fragment并调用其生命周期方法,所以在第一个生命周期方法onAttach()中便能获取Activity以及Activity的布局的组件;

(2)静态加载:

1.静态加载时,Activity的onCreate()调用过程中,fragment也在加载,所以fragment无法获取到Activity的布局中的组件,但为什么能获取到Activity呢?

2.原来在fragment调用onAttach()之前其实还调用了一个方法onInflate(),该方法被调用时fragment已经是和Activity相互结合了,所以可以获取到对方,但是Activity的onCreate()调用还未完成,故无法获取Activity的组件;

3.Activity的onCreate()调用完成是,fragment会调用onActivityCreated()生命周期方法,因此在这儿开始便能获取到Activity的布局的组件;

与Activity通信

fragment不通过构造函数进行传值的原因是因为横屏切换的时候获取不到值。

Activity向Fragment传值:

Activity向Fragment传值,要传的值放到bundle对象里;

在Activity中创建该Fragment的对象fragment,通过调用setArguments()传递到fragment中;

在该Fragment中通过调用getArguments()得到bundle对象,就能得到里面的值。

Fragment向Activity传值:

第一种:

在Activity中调用getFragmentManager()得到fragmentManager,,调用findFragmentByTag(tag)或者通过findFragmentById(id),例如:

FragmentManager fragmentManager = getFragmentManager();

Fragment fragment = fragmentManager.findFragmentByTag(tag);

第二种:

通过回调的方式,定义一个接口(可以在Fragment类中定义),接口中有一个空的方法,在fragment中需要的时候调用接口的方法,值可以作为参数放在这个方法中,然后让Activity实现这个接口,必然会重写这个方法,这样值就传到了Activity中

Fragment与Fragment之间是如何传值的:

第一种:

通过findFragmentByTag得到另一个的Fragment的对象,这样就可以调用另一个的方法了。

第二种:

通过接口回调的方式。

第三种:

通过setArguments,getArguments的方式。

api区别

add

一种是add方式来进行show和add,这种方式你切换fragment不会让fragment重新刷新,只会调用onHiddenChanged(boolean isHidden)。

replace

而用replace方式会使fragment重新刷新,因为add方式是将fragment隐藏了而不是销毁再创建,replace方式每次都是重新创建。

commit/commitAllowingStateLoss

两者都可以提交fragment的操作,唯一的不同是第二种方法,允许丢失一些界面的状态和信息,几乎所有的开发者都遇到过这样的错误:无法在activity调用了onSaveInstanceState之后再执行commit(),这种异常时可以理解的,界面被系统回收(界面已经不存在),为了在下次打开的时候恢复原来的样子,系统为我们保存界面的所有状态,这个时候我们再去修改界面理论上肯定是不允许的,所以为了避免这种异常,要使用第二种方法。

3.懒加载

我们经常在使用fragment时,常常会结合着viewpager使用,那么我们就会遇到一个问题,就是初始化fragment的时候,会连同我们写的网络请求一起执行,这样非常消耗性能,最理想的方式是,只有用户点开或滑动到当前fragment时,才进行请求网络的操作。因此,我们就产生了懒加载这样一个说法。

Viewpager配合fragment使用,默认加载前两个fragment。很容易造成网络丢包、阻塞等问题。

在Fragment中有一个setUserVisibleHint这个方法,而且这个方法是优于onCreate()方法的,它会通过isVisibleToUser告诉我们当前Fragment我们是否可见,我们可以在可见的时候再进行网络加载。

从log上看setUserVisibleHint()的调用早于onCreateView,所以如果在setUserVisibleHint()要实现懒加载的话,就必须要确保View以及其他变量都已经初始化结束,避免空指针。

使用步骤:

申明一个变量isPrepare=false,isVisible=false,标明当前页面是否被创建了

在onViewCreated周期内设置isPrepare=true

在setUserVisibleHint(boolean isVisible)判断是否显示,设置isVisible=true

判断isPrepare和isVisible,都为true开始加载数据,然后恢复isPrepare和isVisible为false,防止重复加载。



Handler.post和View.post的区别

重要

在Android开发中,我们经常会见到下面的代码,比如:

protected void onCreate(Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);

        System.out.println("onCreate===");

        setContentView(R.layout.activity_main);

        rootBtn = findViewById(R.id.rootBtn);

        // 代码1

        UIHandler.post(new Runnable() {

            @Override

            public void run() {

                System.out.println("Handler.post===");

            }

        });

        // 代码2

        rootBtn.post(new Runnable() {

            @Override

            public void run() {

                System.out.println("View.post===");

            }

        });

    }

你曾经有没有想过这两者到底有什么区别?我该使用哪种呢?

常见的Handler.post揭秘

Handler的工作机制,网上介绍的文章太多了,这里我就不赘述了。一句话总结就是通过Handler对象,不论是post Msg还是Runnable,最终都是构造了一个Msg对象,插入到与之对应的Looper的MessageQueue中,不同的是Running时msg对象的callback字段设成了Runnable的值。稍后这条msg会从队列中取出来并且得到执行,UI线程就是这么一个基于事件的循环。所以可以看出Handler.post相关的代码在onCreate里那一刻时就已经开始了执行(加入到了队列,下次循环到来时就会被真正执行了)。

View.post揭秘

要解释它的行为,我们就必须深入代码中去找答案了,其代码如下:


public boolean post(Runnable action) {

        final AttachInfo attachInfo = mAttachInfo;

        if (attachInfo != null) {

            // 注意这个判断,这个变量时机太早的话是没值的,

          // 比如在act#onCreate阶段

            return attachInfo.mHandler.post(action);

        }

        // 仔细阅读下面这段注释!!!

        // Postpone the runnable until we know on which thread it needs to run.

        // Assume that the runnable will be successfully placed after attach.

        getRunQueue().post(action);

        return true;

    }

从上面的源码,我们大概可以看出mAttachInfo字段在这里比较关键,当其有值时,其实和普通的Handler.post就没区别了,但有时它是没值的,比如我们上面示例代码里的onCreate阶段,那么这时执行到了getRunQueue().post(action);这行代码,从这段注释也大概可以看出来真正的执行会被延迟(这里的Postpone注释);我们接着往下看看getRunQueue相关的代码,如下:


/**  其实这段注释已经说的很清楚明了了!!!

    * Queue of pending runnables. Used to postpone calls to post() until this

    * view is attached and has a handler.

    */

private HandlerActionQueue mRunQueue;

private HandlerActionQueue getRunQueue() {

        if (mRunQueue == null) {

            mRunQueue = new HandlerActionQueue();

        }

        return mRunQueue;

    }

从上面我们可以看出,mRunQueue就是View用来处理它还没attach到window(还没对应的handler)时,客户代码发起的post调用的,起了一个临时缓存的作用。不然总不能丢弃吧,这样开发体验就太差了!!!

紧接着,我们继续看下HandlerActionQueue类型的定义,代码如下:


public class HandlerActionQueue {

    private HandlerAction[] mActions;

    private int mCount;

    public void post(Runnable action) {

        postDelayed(action, 0);

    }

    public void postDelayed(Runnable action, long delayMillis) {

        final HandlerAction handlerAction = new HandlerAction(action, delayMillis);

        synchronized (this) {

            if (mActions == null) {

                mActions = new HandlerAction[4];

            }

            mActions = GrowingArrayUtils.append(mActions, mCount, handlerAction);

            mCount++;

        }

    }

    public void executeActions(Handler handler) {

        synchronized (this) {

            final HandlerAction[] actions = mActions;

            for (int i = 0, count = mCount; i < count; i++) {

                final HandlerAction handlerAction = actions[i];

                handler.postDelayed(handlerAction.action, handlerAction.delay);

            }

            mActions = null;

            mCount = 0;

        }

    }

    private static class HandlerAction {

        final Runnable action;

        final long delay;

        public HandlerAction(Runnable action, long delay) {

            this.action = action;

            this.delay = delay;

        }

        public boolean matches(Runnable otherAction) {

            return otherAction == null && action == null

                    || action != null && action.equals(otherAction);

        }

    }

}

注意:这里的源码部分,我们只摘录了部分关键代码,其余不太相关的直接略去了。

从这里可以看出,我们前面的View.post调用里的Runnable最终会被存储在这里的mActions数组里,这里最关键的一点就是其executeActions方法,因为这个方法里我们之前post的Runnable才真正通过handler.postDelayed方式使其进入handler对应的消息队列里等待执行;

到此为止,我们还差知道View里的mAttachInfo字段何时被赋值以及这里的executeActions方法是什么时候被触发的,答案就是在View的dispatchAttachedToWindow方法,其关键源码如下:


void dispatchAttachedToWindow(AttachInfo info, int visibility) {

        mAttachInfo = info;

        ...

        // Transfer all pending runnables.

        if (mRunQueue != null) {

            mRunQueue.executeActions(info.mHandler);

            mRunQueue = null;

        }

        performCollectViewAttributes(mAttachInfo, visibility);

        onAttachedToWindow();

        ...

}

而通过之前的文章,我们已经知道了此方法是当Act Resume之后,在ViewRootImpl.performTraversals()中触发的,参考View.onAttachedToWindow调用时机分析

总结

Handler.post,它的执行时间基本是等同于onCreate里那行代码触达的时间;

View.post,则不同,它说白了执行时间一定是在Act#onResume发生后才开始算的;或者换句话说它的效果相当于你上面的View.post方法是写在Act#onResume里面的(但只执行一次,因为onCreate不像onResume会被多次触发);

当然,虽然这里说的是post方法,但对应的postDelayed方法区别也是类似的。

平时当你项目很小,MainActivity的逻辑也很简单时是看不出啥区别的,但当act的onCreate到onResume之间耗时比较久时(比如2s以上),就能明显感受到这2者的区别了,而且本身它们的实际含义也是很不同的,前者的Runnable真正执行时,可能act的整个view层次都还没完整的measure、layout完成,但后者的Runnable执行时,则一定能保证act的view层次结构已经measure、layout并且至少绘制完成了一次。


横竖屏切换的Activity 生命周期变化?

重要

不设置 Activity 的 android:configChanges 时,切屏会销毁当前Activity,然后重新加载调用各个生命周期,切横屏时会执行一次,切竖屏时会执行两次;onPause()→onStop()→onDestory()→onCreate()→onStart()→onResume()

设置 Activity 的 android:configChanges=" orientation",经过机型测试

在 Android5.1 即 即 API 3 23 级别下,切屏还是会重新调用各个生命周期,切横、竖屏时只会执行一次

在 Android9 即 即 API 8 28 级别下,切屏不会重新调用各个生命周期,只会执行 onConfigurationChanged方法

官方纠正后,原话如下

如果您的应用面向 Android 2 3.2 即 即 API 级别 3 13 或更

高级别(按照 minSdkVersion 和 targetSdkVersion)


说下Activity 的四种启动模式、应用场景 ?

重要

standard 标准模式: 每次启动一个 Activity 都会重新创建一个新的实例,不管这个实例是否已经存在,此模式的 Activity 默认会进入启动它的 Activity 所属的任务栈中;

singleTop 栈顶复用模式: 如果新 Activity 已经位于任务栈的栈顶,那么此 Activity 不会被重新创建,同时会回调 onNewIntent方法,如果新 Activity 实例已经存在但不在栈顶,那么Activity 依然会被重新创建;

singleTask 栈内复用模式: 只要 Activity 在一个任务栈中存在,那么多次启动此 Activity 都不会重新创建实例,并回调onNewIntent 方法,此模式启动 Activity A,系统首先会寻找是否存在 A 想要的任务栈,如果不存在,就会重新创建一个任务栈,然后把创建好 A 的实例放到栈中;

singleInstance单实例模式: 这是一种加强的 singleTask 模式,具有此种模式的 Activity 只能单独地位于一个任务栈中,且此任务栈中只有唯一一个实例;


FragmentPagerAdapter 与 与 FragmentStatePagerAdapter 的区别与使用场景?

重要

FragmentPagerAdapter 的每个 Fragment 会持久的保存在 FragmentManager 中,只要用户可以返回到页面中,它都不会被销毁。因此适用于那些数据 相对静态的页,Fragment 数量也比较少的那种;FragmentStatePagerAdapter 只保留当前页面,当页面不可见时,该 Fragment 就会被消除,释放其资源。因此适用于那些 数据动态性较大、 占用内存较多,多 Fragment 的情况;


Android 中如何捕获未捕获的异常

重要

UncaughtExceptionHandler

自 定 义 一 个 Application , 比 如 叫 MyApplication 继 承 Application 实 现UncaughtExceptionHandler。

覆写 UncaughtExceptionHandler 的 onCreate 和 uncaughtException 方法。 注意:上面的代码只是简单的将异常打印出来。在 onCreate 方法中我们给 Thread 类设置默认异常处理handler,如果这句代码不执行则一切都是白搭。在 uncaughtException 方法中我们必须新开辟个线程进行我们异常的收集工作,然后将系统给杀死。

在 AndroidManifest 中配置该 Application:

Bug 收集工具 Crashlytics

Crashlytics是专门为移动应用开发者提供的保存和分析应用崩溃的工具。国内主要使用的是友盟做数据统计。

Crashlytics的好处:

1.Crashlytics不会漏掉任何应用崩溃信息。

2.Crashlytics可以象Bug管理工具那样,管理这些崩溃日志。

3.Crashlytics可以每天和每周将崩溃信息汇总发到你的邮箱,所有信息一目了然


activity与fragment区别

重要

生命周期:

fragment从创建倒销毁整个生命周期依次为onAttach()→onCreate()→onCreateView()→onActivityCreated()→onStart()→onResume()→onPause()→onStop()→onDestroyView()→onDestroy()→onDetach()

与activity不同的方法有

onAttach():当Fragment和Activity建立关联的时候调用;

onCreateView():当Fragment创建视图调用;

onActivityCreated:与Fragment相关联的Activity完成onCreate()之后调用;

onDestoryView():在Fragment中的布局被移除时调用;

onDetach():当Fragment和Activity解除关联时调用;

activity常用的生命周期只有以下几个;

onCreate(): 表示 Activity 正在被创建,常用来 初始化工作,比如调用 setContentView 加载界面布局资源,初始化 Activity 所需数据等;

onRestart():表示 Activity 正在重新启动,一般情况下,当前Acitivty 从不可见重新变为可见时,OnRestart就会被调用;

onStart(): 表示 Activity 正在被启动,此时 Activity 可见但不在前台,还处于后台,无法与用户交互;

onResume(): 表示 Activity 获得焦点,此时 Activity 可见且在前台并开始活动,这是与 onStart 的区别所在;

onPause(): 表示 Activity 正在停止,此时可做一些 存储数据、停止动画等工作,但是不能太耗时,因为这会影响到新 Activity的显示,onPause 必须先执行完,新 Activity 的 onResume 才会执行;

onStop(): 表示 Activity 即将停止,可以做一些稍微重量级的回收工作,比如注销广播接收器、关闭网络连接等,同样不能太耗时;

onDestroy(): 表示 Activity 即将被销毁,这是 Activity 生命周期中的最后一个回调,常做 回收工作、资源释放;

区别:

Fragment比Activity多出四个回调周期,控制操作上更灵活;

Fragment可以在xml文件中直接写入,也可以在Activity中动态添加;

Fragment可以使用show()/hide()或者replace()对Fragment进行切换,切换的时候不会出现明显的效果,Activity切换的时候会有明显的翻页或其他效果;


View的分发机制,滑动冲突

重要

View的事件传递顺序有3个重要的方法,dispatchTouchEvent()是否消耗了本次事件,onInterceptTouchEvent()是否拦截了本次事件,onTouchEvent()是否处理本次事件,滑动冲突分为同方向滑动冲突,例如ScrollView和ListView,同方向滑动冲突,可以计算ListView高度而动态设置ListView的高度,ScrollView高度可变。例如ViewPager和ListView,不同方向滑动冲突,一个是横向滑动一个是竖直滑动,不同方向滑动可以判断滑动的x,y轴是横向还是竖直滑动,如果判断得到是横向滑动,就拦截ListView的事件,竖则反之。


音视频相关类

一般

总体来说,分为几个类

视频录制方面,Camear摄像头录制视频类,MediaProjection屏幕录制视频类

编码方面,MediaCodec,MediaRecorder

预览方面,SurfaceView,GLSurfaceView,TextureView,VideoView


Message.obtain() 是从消息池取 Message,消息池其实是使用 Message 链表结构实现,消息池默认最大值 50。 Message.obtain() 每次都是把消息池表头的 Message 取走 ,再把表头指向 next。

public static Message obtain() {

    synchronized (sPoolSync) {

        if (sPool != null) {

            Message m = sPool;

            sPool = m.next;

            m.next = null;  //从sPool中取出一个Message对象,并消息链表断开

            m.flags = 0; // 清除in-use flag

            sPoolSize--; //消息池的可用大小进行减1操作

            return m;

        }

    }

    return new Message(); // 当消息池为空时,直接创建Message对象

}

消息在 loop 中被 handler 分发消费之后会执行回收的操作,将该消息内部数据清空并添加到消息链表的表头。

public void recycle() {

    if (isInUse()) { //判断消息是否正在使用

        if (gCheckRecycle) { //Android 5.0以后的版本默认为true,之前的版本默认为false.

            throw new IllegalStateException("This message cannot be recycled because it is still in use.");

        }

        return;

    }

    recycleUnchecked();

}

//对于不再使用的消息,加入到消息池

void recycleUnchecked() {

    //将消息标示位置为IN_USE,并清空消息所有的参数。

    flags = FLAG_IN_USE;

    what = 0;

    arg1 = 0;

    arg2 = 0;

    obj = null;

    replyTo = null;

    sendingUid = -1;

    when = 0;

    target = null;

    callback = null;

    data = null;

    synchronized (sPoolSync) {

        if (sPoolSize < MAX_POOL_SIZE) { //当消息池没有满时,将Message对象加入消息池

            next = sPool;

            sPool = this;

            sPoolSize++; //消息池的可用大小进行加1操作

        }

    }

}


你了解 HandlerThread 吗?

重要

HandlerThread 继承自 Thread,它是一种可以使用 Handler 的 Thread,它的实现也很简单,在 run方法中也是通过 Looper.prepare() 来创建消息队列,并通过Looper.loop()来开启消息循环(与我们手动创建方法基本一致),这样在实际的使用中就允许在 HandlerThread 中创建 Handler 了。

public class HandlerThread extends Thread {

    @Override

    public void run() {

        mTid = Process.myTid();

        Looper.prepare();

        synchronized (this) {

            mLooper = Looper.myLooper();

            notifyAll();

        }

        Process.setThreadPriority(mPriority);

        onLooperPrepared();

        Looper.loop();

        mTid = -1;

    }

}

由于 HandlerThread 的run方法是一个无限循环,因此当不需要使用的时候通过quit或者quitSafely方法来终止线程的执行。


你对 IdleHandler 有多少了解?

一般

IdleHandler 是一个接口, 这个接口方法是在消息队列全部处理完成后或者是在阻塞的过程中等待更多的消息的时候调用的,返回值 false 表示只回调一次,true 表示可以接收多次回调。

Looper.myQueue().addIdleHandler(new MessageQueue.IdleHandler() {

    @Override

    public boolean queueIdle() {

        return false;

    }

});


MessageQueue 是队列吗?它是什么数据结构?

重要

MessageQueue 不是队列,它内部使用一个 Message 链表实现消息的存和取。 链表的排列依据是 Message.when,表示 Message 期望被分发的时间,该值是 SystemClock. uptimeMillis() 与 delayMillis 之和。

##7、 handler.postDelayed() 函数延时执行计时是否准确?

当上一个消息存在耗时任务的时候,会占用延时任务执行的时机,实际延迟时间可能会超过预设延时时间,这时候就不准确了。


Looper 死循环为什么不会导致应用卡死,会消耗大量资源吗?

重要

对于线程即是一段可执行的代码,当可执行代码执行完成后,线程生命周期便该终止了,线程退出。而对于主线程,我们是绝不希望会被运行一段时间,自己就退出,那么如何保证能一直存活呢?简单做法就是可执行代码是能一直执行下去的,死循环便能保证不会被退出,例如,binder 线程也是采用死循环的方法,通过循环方式不同与 Binder 驱动进行读写操作,当然并非简单地死循环,无消息时会休眠。但这里可能又引发了另一个问题,既然是死循环又如何去处理其他事务呢?通过创建新线程的方式。真正会卡死主线程的操作是在回调方法 onCreate/onStart/onResume 等操作时间过长,会导致掉帧,甚至发生ANR,looper.loop本身不会导致应用卡死。

主线程的死循环一直运行是不是特别消耗CPU资源呢? 其实不然,这里就涉及到 Linux pipe/epoll 机制,简单说就是在主线程的 MessageQueue 没有消息时,便阻塞在 Loop 的 queue.next() 中的 nativePollOnce() 方法里,此时主线程会释放 CPU 资源进入休眠状态,直到下个消息到达或者有事务发生,通过往 pipe 管道写端写入数据来唤醒主线程工作。这里采用的epoll机制,是一种IO多路复用机制,可以同时监控多个描述符,当某个描述符就绪(读或写就绪),则立刻通知相应程序进行读或写操作,本质同步 I/O,即读写是阻塞的。 所以说,主线程大多数时候都是处于休眠状态,并不会消耗大量 CPU 资源。


可参见:https://www.zhihu.com/question/34652589


既然线程中创建 Handler 时需要 Looper 对象,为什么主线程不用调用 Looper.prepare() 创建 Looper 对象?

重要

在 App 启动的时候系统默认启动了一个主线程的 Looper(ActivityThread 的 main 方法中),Loop.prepareMainLooper 方法也是调用了 Looper.prepare方法,里面会创建一个不可退出的 Looper, 并 set 到 sThreadLocal 对象当中。

public static void main(String[] args) {

    Looper.prepareMainLooper();

    Looper.loop();

}


可以在子线程直接创建一个 Handler 吗?会出现什么问题,那该怎么做?

重要

不能在子线程直接 new 一个 Handler。因为 Handler 的工作依赖于 Looper,而 Looper 又是属于某一个线程的,其他线程不能访问,所以在线程中使用 Handler 时必须要保证当前线程中 Looper 对象并且启动循环。不然会抛出异常。

throw new RuntimeException("Can't create handler inside thread " + Thread.currentThread() + " that has not called Looper.prepare()");

1

正确做法是:

class LooperThread extends Thread {

    public Handler mHandler;

    public void run() {

        Looper.prepare();   // 为线程创建 Looper 对象

        mHandler = new Handler() {  

            public void handleMessage(Message msg) {


            }

        };

        Looper.loop();   // 启动消息循环

    }

}


可以在子线程直接创建一个 Handler 吗?会出现什么问题,那该怎么做?

重要

不能在子线程直接 new 一个 Handler。因为 Handler 的工作依赖于 Looper,而 Looper 又是属于某一个线程的,其他线程不能访问,所以在线程中使用 Handler 时必须要保证当前线程中 Looper 对象并且启动循环。不然会抛出异常。

throw new RuntimeException("Can't create handler inside thread " + Thread.currentThread() + " that has not called Looper.prepare()");

1

正确做法是:

class LooperThread extends Thread {

    public Handler mHandler;

    public void run() {

        Looper.prepare();   // 为线程创建 Looper 对象

        mHandler = new Handler() {  

            public void handleMessage(Message msg) {


            }

        };

        Looper.loop();   // 启动消息循环

    }

}


一个线程可以有几个 Looper、几个 MessageQueue 和几个 Handler?

重要

在 Android 中,Looper 类利用了 ThreadLocal 的特性,保证了每个线程只存在一个 Looper 对象。

关于 ThreadLocal 可以看:理解 ThreadLocal

static final ThreadLocalsThreadLocal = new ThreadLocal();

private static void prepare(boolean quitAllowed) {

    if (sThreadLocal.get() != null) {

        throw new RuntimeException("Only one Looper may be created per thread");

    }

    sThreadLocal.set(new Looper(quitAllowed));

}

Looper 构造函数中创建了 MessageQueue 对象,因此一个线程只有一个 MessageQueue。

private Looper(boolean quitAllowed) {

        mQueue = new MessageQueue(quitAllowed);

        mThread = Thread.currentThread();

}

可以有多个 Handler。

Handler 在创建时与 Looper 和 MessageQueue 关联起来:

public Handler(Callback callback, boolean async) {

    ...

    mLooper = Looper.myLooper();

    if (mLooper == null) {

        throw new RuntimeException(

            "Can't create handler inside thread that has not called Looper.prepare()");

    }

    mQueue = mLooper.mQueue;

    ...

}

Handler 发送消息是将消息传递给 MessageQueue:

private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {

    msg.target = this;

    if (mAsynchronous) {

        msg.setAsynchronous(true);

    }

    return queue.enqueueMessage(msg, uptimeMillis);

}

注意 msg.target = this;, 这里将当前的 Handler 赋值给 Message 对象,在后面处理消息时就能依据 msg.target 区分不同的 Handler。


什么是协程?

重要

官方描述:协程通过将复杂性放入库来简化异步编程。程序的逻辑可以在协程中顺序地表达,而底层库会为我们解决其异步性。该库可以将用户代码的相关部分包装为回调、订阅相关事件、在不同线程(甚至不同机器)上调度执行,而代码则保持如同顺序执行一样简单。

协程就像非常轻量级的线程。线程是由系统调度的,线程切换或线程阻塞的开销都比较大。而协程依赖于线程,但是协程挂起时不需要阻塞线程,几乎是无代价的,协程是由开发者控制的。所以协程也像用户态的线程,非常轻量级,一个线程中可以创建任意个协程。

协程很重要的一点就是当它挂起的时候,它不会阻塞其他线程。协程底层库也是异步处理阻塞任务,但是这些复杂的操作被底层库封装起来,协程代码的程序流是顺序的,不再需要一堆的回调函数,就像同步代码一样,也便于理解、调试和开发。它是可控的,线程的执行和结束是由操作系统调度的,而协程可以手动控制它的执行和结束。


什么是AIDL?

重要

什么是AIDL?

AIDL是Android中IPC的方式的一种。AIDL的作用是让你可以在自己的APP里绑定一个其他的APP的Service,这样你的APP可以与其他APP交互。

AIDL解决了什么问题?

只有当你允许来自不同的客户端访问你的服务并且需要处理多线程问题时才必须用AIDL。

AIDL如何使用?

服务端:

(1)、定义一个*.aidl的文件

(2)、实现AIDL文件生成的java接口

(3)、定义一个自己的Service,在实现Service时,为了其他应用可以通过bindService来和我们的Service进行交互,我们都要实现Service中的onBind()方法,并且返回一个继承了Binder的内部类。

(4)、同一应用中的Activity为该Service赋值,使用Service

客户端:

(1)、客户端要想使用该服务,需要知道服务在aidl文件中提供了什么服务,所以需要将服务端的aidl文件拷贝到客户端,且包名和文件名需要与服务端一致。

(2)、通过bindService方法与Service交互,该方法中有一个ServiceConnection类型的参数,主要代码便是在该接口中实现。

(3)、在ServiceConnection实现类中onServiceConnected方法中通过*.asInterface(service)获取一个aidl接口的实例。

https://www.cnblogs.com/yoyohong/p/5660406.html


进程间通信的方式?

重要

(1)、AIDL:功能强大,支持进程间一对多的实时并发通信,并可实现RPC(远程过程调用)

(2)、Messenger:支持一对多的串行实时通信,AIDL的简化版本。

(3)、Bundle:四大组件的进程通信方式,只能传输Bundle支持的数据类型。

(4)、ContentProvider:强大的数据源访问支持,主要支持CRUD操作,一对多进程间数据共享。

(5)、Broadcast Receiver:广播,但只能单向通信,接收者只能被动接受信息。

(6)、文件共享:在非高并发的情况下共享简单的数据。

(7)、Socket:通过网络传输数据。


jni如何调用java层代码?

重要

C调用java中的方法使用的是反射

(1)、获取字节码文件(jclass(FindClass)(JNIEnv,const char*);)

(2)、通过字节码对象找到方法对象(jmethodID(GetMethodID)(JNIEnv,jclass,const char,const char);)

(3)、通过字节码文件创建一个object对象(该方法可选,方法中已经传递了一个object,如果需要调用的方法与本地方法不在同一个文件夹则需要创建新的object(jobject(AllocObject)(JNIEvn,jclass);),如果需要反射调用的java方法与本地方法不在同一个类中,需要创建该方法,但是如果是这样,并且需要更新UI操作,这个时候就会报空指针异常,因为这个时候调用的方法只是一个方法,没有Activity声明周期)。

(4)、通过对象调用方法,可以调用空参数方法,也可以调用有参数的方法,并将参数通过调用的方法传入(void(CallVoidMethod)(JNIEvn,jobject,jmethodID,...))。


Fragment的懒加载实现

重要

Fragment可见状态改变时会被调用setUserVisibleHint()方法,可以通过复写该方法实现Fragment的懒加载,但需要注意该方法可能在onVIewCreated之前调用,需要确保界面已经初始化完成的情况下再去加载数据,避免空指针。


requestLayout,invalidate,postInvalidate区别与联系

重要

相同点:三个方法都有刷新界面的效果。

不同点:invalidate和postInvalidate只会调用onDraw()方法;requestLayout则会重新调用onMeasure、onLayout、onDraw。

调用了invalidate方法后,会为该View添加一个标记位,同时不断向父容器请求刷新,父容器通过计算得出自身需要重绘的区域,直到传递到ViewRootImpl中,最终触发performTraversals方法,进行开始View树重绘流程(只绘制需要重绘的视图)。

调用requestLayout方法,会标记当前View及父容器,同时逐层向上提交,直到ViewRootImpl处理该事件,ViewRootImpl会调用三大流程,从measure开始,对于每一个含有标记位的view及其子View都会进行测量onMeasure、布局onLayout、绘制onDraw。


getWidth()方法和getMeasureWidth()区别呢?

重要

首先getMeasureWidth()方法在measure()过程结束后就可以获取到了,而getWidth()方法要在layout()过程结束后才能获取到。另外,getMeasureWidth()方法中的值是通过setMeasuredDimension()方法来进行设置的,而getWidth()方法中的值则是通过视图右边的坐标减去左边的坐标计算出来的。


什么是MeasureSpec

重要

MeasureSpec代表一个32位int值,高两位代表SpecMode(测量模式),低30位代表SpecSize(具体大小)。

SpecMode有三类:

UNSPECIFIED 表示父容器不对View有任何限制,一般用于系统内部,表示一种测量状态;

EXACTLY 父容器已经检测出view所需的精确大小,这时候view的最终大小SpecSize所指定的值,相当于match_parent或指定具体数值。

AT_MOST 父容器指定一个可用大小即SpecSize,view的大小不能大于这个值,具体多大要看view的具体实现,相当于wrap_content。


View的绘制原理

重要

View的绘制从ActivityThread类中Handler的处理RESUME_ACTIVITY事件开始,在执行performResumeActivity之后,创建Window以及DecorView并调用WindowManager的addView方法添加到屏幕上,addView又调用ViewRootImpl的setView方法,最终执行performTraversals方法,依次执行performMeasure,performLayout,performDraw。也就是view绘制的三大过程。

measure过程测量view的视图大小,最终需要调用setMeasuredDimension方法设置测量的结果,如果是ViewGroup需要调用measureChildren或者measureChild方法进而计算自己的大小。

layout过程是摆放view的过程,View不需要实现,通常由ViewGroup实现,在实现onLayout时可以通过getMeasuredWidth等方法获取measure过程测量的结果进行摆放。

draw过程先是绘制背景,其次调用onDraw()方法绘制view的内容,再然后调用dispatchDraw()调用子view的draw方法,最后绘制滚动条。ViewGroup默认不会执行onDraw方法,如果复写了onDraw(Canvas)方法,需要调用 setWillNotDraw(false);清楚不需要绘制的标记。


IdleHandler (闲时机制)

重要

IdleHandler是一个回调接口,可以通过MessageQueue的addIdleHandler添加实现类。当MessageQueue中的任务暂时处理完了(没有新任务或者下一个任务延时在之后),这个时候会回调这个接口,返回false,那么就会移除它,返回true就会在下次message处理完了的时候继续回调。


Looper.loop()为什么不会阻塞主线程

重要

Android是基于事件驱动的,即所有Activity的生命周期都是通过Handler事件驱动的。loop方法中会调用MessageQueue的next方法获取下一个message,当没有消息时,基于Linux pipe/epoll机制会阻塞在loop的queue.next()中的nativePollOnce()方法里,并不会消耗CPU。


Android类加载器

重要

Android平台上虚拟机运行的是Dex字节码,一种对class文件优化的产物,传统Class文件是一个Java源码文件会生成一个.class文件,而Android是把所有Class文件进行合并,优化,然后生成一个最终的class.dex,目的是把不同class文件重复的东西只需保留一份,如果我们的Android应用不进行分dex处理,最后一个应用的apk只会有一个dex文件。

Android中常用的有两种类加载器,DexClassLoader和PathClassLoader,它们都继承于BaseDexClassLoader。区别在于调用父类构造器时,DexClassLoader多传了一个optimizedDirectory参数,这个目录必须是内部存储路径,用来缓存系统创建的Dex文件。而PathClassLoader该参数为null,只能加载内部存储目录的Dex文件。所以我们可以用DexClassLoader去加载外部的apk。


三级缓存

一般

网络加载,不优先加载,速度慢,浪费流量

本地缓存,次优先加载,速度快

内存缓存,优先加载,速度最快

首次加载Android App时,肯定要通过网络交互来获取图片,之后我们可以将图片保存至本地SD卡和内存中,之后运行APP时,优先访问内存中的图片缓存,若内存中没有,则加载本地SD卡中图片,最后选择访问网络


如何优化ListView

一般

①Item布局,层级越少越好,使用hierarchyview工具查看优化。

②复用convertView

③使用ViewHolder

④item中有图片时,异步加载

⑤快速滑动时,不加载图片

⑥item中有图片时,应对图片进行适当压缩

⑦实现数据的分页加载


Touch事件传递机制

非常重要

在我们点击屏幕时,会有下列事件发生:

Activity调用dispathTouchEvent()方法,把事件传递给Window;

Window再将事件交给DecorView(DecorView是View的根布局);

DecorView再传递给ViewGroup;

Activity ——> Window ——> DecorView ——> ViewGroup——> View

事件分发的主要有三个关键方法

dispatchTouchEvent() 分发

onInterceptTouchEvent() 拦截 ,只有ViewGroup独有此方法

onTouchEvent() 处理触摸事件

Activity首先调用dispathTouchEvent()进行分发,接着调用super向下传递

ViewGroup首先调用dispathTouchEvent()进行分发,接着会调用onInterceptTouchEvent()(拦截事件)。若拦截事件返回为true,表示拦截,事件不会向下层的ViewGroup或者View传递;false,表示不拦截,继续分发事件。默认是false,需要提醒一下,View是没有onInterceptTouchEvent()方法的

事件在ViewGroup和ViewGroup、ViewGroup和View之间进行传递,最终到达View;

View调用dispathTouchEvent()方法,然后在OnTouchEvent()进行处理事件;OnTouchEvent() 返回true,表示消耗此事件,不再向下传递;返回false,表示不消耗事件,交回上层处理。


Service和线程的区别,我们为什么不用service代替线程,相应在什么情况下使用?

重要

Service:service是android的一种机制,对应的service是运行在主线程上的,它是由系统进程托管。他们之间的通信类似于client和server,是一种轻量级的ipc通信,这种通信的载体是binder,它是在linux层交换信息的一种ipc。

线程:线程是程序执行的最小单元,它是分配CPU的基本单位。可以用线程来执行一些异步的操作。

区别:Thread 的运行是独立于 Activity 的,也就是说当一个 Activity 被 finish 之后,如果你没有主动停止 Thread 或者 Thread 里的 run 方法没有执行完毕的话,Thread 也会一直执行。因此这里会出现一个问题:当 Activity 被 finish 之后,你不再持有该 Thread 的引用。另一方面,你没有办法在不同的 Activity 中对同一 Thread 进行控制;而Service则可以被多个activity共用(当然你也可以说我可以在服务里面新起线程这样不就可以被多个activity共用了,其实这样的本质还是共用的服务而不是线程)。

我们不用服务替代线程是因为:服务(子类IntentService则是在内部添加了子线程)也是运行在主线程上面,而不是子线程,相当于你还是需要新起线程来完成相应的操作,这又是何苦啦;并且一个类里面需要多线程操作的情况,服务是不是显得很无力。各有各的优点,下面来看使用情况。

使用情况:

1.在应用中,如果是长时间的在后台运行,而且不需要交互的情况下,使用服务。

同样是在后台运行,不需要交互的情况下,如果只是完成某个任务,之后就不需要运行,而且可能是多个任务,不需要长时间运行的情况下使用线程。

2.如果任务占用CPU时间多,资源大的情况下,要使用线程。

3.一般我们做下载任务都是在服务里面新起线程做异步任务来操作,或者直接使用IntentService。


IntentService和Service有什么区别?

重要

Service不是独立的进程,也不是独立的线程,它是依赖于应用程序的主线程(比喻成没有界面的activity),也就是说,在更多时候不建议在Service中编写耗时的逻辑和操作,否则会引起ANR。 

IntentService是Service的子类,IntentService在执行onCreate操作的时候,内部开了一个线程,去你执行你的耗时操作。通过Handler looper message的方式实现了一个多线程的操作,同时耗时操作也可以被这个线程管理和执行,同时不会产生ANR的情况。


JVM和DVM有什么区别,以及ART垃圾回收机制?

重要

1.基于不同位置

Dalvik:基于寄存器,编译和运行都会快一些 

JVM: 基于栈, 编译和运行都会慢些

2.字节码的区别

Dalvik: 执行.dex格式的字节码,是对.class文件进行压缩后产生的,文件变小

JVM: 执行.class格式的字节码

3.运行环境的区别   

Dalvik : 一个应用启动都运行一个单独的虚拟机运行在一个单独的进程中

JVM: 只能运行一个实例, 也就是所有应用都运行在同一个JVM中

但是在Android4.4引入了ART,也是 Android 5.0 及更高版本的默认 Android 运行时。目前google已经不再维护和发布dalvik(DVM)。

1.ART GC 与 Dalvik 的主要区别在于 ART GC 引入了移动垃圾回收器。使用移动 GC 的目的在于通过堆压缩来减少后台应用使用的内存。目前,触发堆压缩的事件是 ActivityManager 进程状态的改变。当应用转到后台运行时,它会通知 ART 已进入不再“感知”卡顿的进程状态。此时 ART 会进行一些操作(例如,压缩和监视器压缩),从而导致应用线程长时间暂停。目前正在使用的两个移动 GC 是同构空间压缩和半空间压缩。

2.与 Dalvik 相比,暂停次数从 2 次减少到 1 次。Dalvik 的第一次暂停主要是为了进行根标记,而这个动作在 ART中已经是让线程自己去标记,然后马上恢复运行,这样就减少了一次暂停。

3.与 Dalvik 类似,ART GC 在清除过程开始之前也会暂停 1 次。两者在这方面的主要差异在于:在此暂停期间,某些 Dalvik 环节在 ART 中并发进行。这些环节包括 java.lang.ref.Reference 处理、系统弱清除(例如,jni 弱全局等)、重新标记非线程根和卡片预清理。在 ART 暂停期间仍进行的阶段包括扫描脏卡片以及重新标记线程根,这些操作有助于缩短暂停时间。

4.相对于 Dalvik,ART GC 改进的最后一个方面是粘性 CMS 回收器增加了 GC 吞吐量。不同于普通的分代 GC,粘性 CMS 不移动。系统会将年轻对象保存在一个分配堆栈(基本上是 java.lang.Object 数组)中,而非为其设置一个专属区域。这样可以避免移动所需的对象以维持低暂停次数,但缺点是容易在堆栈中加入大量复杂对象图像而使堆栈变长

最后说一下回收机制:

1. 先回收与其他Activity 或Service/Intent Receiver 无关的进程(即优先回收独立的Activity)因此建议,我们的一些(耗时)后台操作,最好是作成Service的形式

2.不可见(处于Stopped状态的)Activity

3.Service进程(除非真的没有内存可用时会被销毁)

4.非活动的可见的(Paused状态的)Activity

5.当前正在运行(Active/Running状态的)Activity 


Lru算法的原理?

重要

LRU(Least Recently Used)最近最少使用,LRU使用的是LinkedHashMap,如果链表中存在一个数则将其置顶,如果没有则直接在顶部加入这个数并将其底部的数移除。大致可以这样理解,具体可以再搜索一下LRU算法。


横竖屏切换activity生命周期?

重要

1、AndroidManifest.xml不设置Activity的android:configChanges时,切屏会重新调用各个生命周期,

切横屏时会执行一次,切竖屏时会执行两次。生命周期如下:

onSaveInstanceState-onPause-onStop-onDestory-onCreate-onStart-onRestoreInstanceState-onResume

2、设置Activity的android:configChanges="orientation"时,切屏还是会重新调

用各个生命周期,切横、竖屏时只会执行一次。生命周期如下:

onSaveInstanceState-onPause-onStop-onDestory-onCreate-onStart-onRestoreInstanceState-onResume

3、设置Activity的android:configChanges="orientation|keyboardHidden"时,

切屏不会重新调用各个生命周期,只会执行onConfigurationChanged方法。


线程间通信有哪些方式?

一般

接口

Handler

观察者模式(EventBus)

Android使用RunonUiThread可以切换到主线程

AsyncTask

BroadCast

SharedPreferences


Android的数据存储方式有哪些?

一般

1.Sharedpreferences(适合轻量级数据存储,采用XML键值对的形式存储到本地,只能运用于一个App内) 

2.文件存储

3.SQLite数据库存储

4.ContentProvider

5.网络存储


Handler机制的原理与RXJava有什么区别?

一般

当时问到我的时候我还真不知道怎么回答,我只知道相同点:都是为了线程间通信。

区别:

1.RxJava线程切换更方便(直接可以切换子线程和UI线程),Handler需要在子线程去发送消息,在主线程去接受消息然后才能改变UI。

2.RxJava是观察者模式,Handler是消息队列用的是双向链表。


SharedPreference跨进程使用会怎么样?如何保证跨进程使用安全?

重要

在两个应用的manifest配置中好相同的shartdUserId属性,A应用正常保存数据,B应用

createPackageContext("com.netease.nim.demo", CONTEXT_IGNORE_SECURITY)

获取context然后获取应用数据,为保证数据安全,使用加密存储


recyclerView嵌套卡顿解决如何解决

重要

设置预加载的数量LinearLayoutManager.setInitialPrefetchItemCount(4),默认是预加载2个,

设置子项缓存,

设置自带滑动冲突解决属性rv.setHasFixedSize(true); rv.setNestedScrollingEnabled(false);

可以完美解决,不过Google不推荐RecyClerView嵌套使用,需要嵌套尽量找类似于ExpandableListView 第三方控件来解决


RecyclerView和ListView的区别

一般

RecyclerView可以完成ListView,GridView的效果,还可以完成瀑布流的效果。同时还可以设置列表的滚动方向(垂直或者水平);

RecyclerView中view的复用不需要开发者自己写代码,系统已经帮封装完成了。

RecyclerView可以进行局部刷新。

RecyclerView提供了API来实现item的动画效果。

在性能上:

如果需要频繁的刷新数据,需要添加动画,则RecyclerView有较大的优势。

如果只是作为列表展示,则两者区别并不是很大。


HybridApp WebView和JS交互

重要

Android与JS通过WebView互相调用方法,实际上是:

Android去调用JS的代码

1. 通过WebView的loadUrl(),使用该方法比较简洁,方便。但是效率比较低,获取返回值比较困难。

2. 通过WebView的evaluateJavascript(),该方法效率高,但是4.4以上的版本才支持,4.4以下版本不支持。所以建议两者混合使用。

JS去调用Android的代码

1. 通过WebView的addJavascriptInterface()进行对象映射 ,该方法使用简单,仅将Android对象和JS对象映射即可,但是存在比较大的漏洞。


漏洞产生原因是:当JS拿到Android这个对象后,就可以调用这个Android对象中所有的方法,包括系统类(java.lang.Runtime 类),从而进行任意代码执行。

解决方式:

(1)Google 在Android 4.2 版本中规定对被调用的函数以 @JavascriptInterface进行注解从而避免漏洞攻击。

(2)在Android 4.2版本之前采用拦截prompt()进行漏洞修复。


2. 通过 WebViewClient 的shouldOverrideUrlLoading ()方法回调拦截 url 。这种方式的优点:不存在方式1的漏洞;缺点:JS获取Android方法的返回值复杂。(ios主要用的是这个方式)


(1)Android通过 WebViewClient 的回调方法shouldOverrideUrlLoading ()拦截 url

(2)解析该 url 的协议

(3)如果检测到是预先约定好的协议,就调用相应方法


3. 通过 WebChromeClient 的onJsAlert()、onJsConfirm()、onJsPrompt()方法回调拦截JS对话框alert()、confirm()、prompt() 消息

这种方式的优点:不存在方式1的漏洞;缺点:JS获取Android方法的返回值复杂。


Handler的原理

非常重要

Handler 主要用于跨线程通信。涉及MessageQueue/Message/Looper/Handler 这 4 个类。

Message:消息,分为硬件产生的消息和软件生成的消息。

MessageQueue:消息队列,主要功能是向消息池投递信息 (MessageQueue.enqueueMessage) 和取走消息池的信息 (MessageQueue.next) 。

Handler:消息处理者,负责向消息池中发送消息 (Handler.enqueueMessage) 和处理消息 (Handler.handleMessage) 。

Looper:消息泵,不断循环执行 (Looper.loop) ,按分发机制将消息分发给目标处理者。

它们之间的类关系:

Looper 有一个 MessageQueue 消息队列;MessageQueue 有一组待处理的 Message;Message 中有一个用于处理消息的 Handler;Handler 中有 Looper 和 MessageQueue。


Android中跨进程通讯的几种方式

重要

Android 跨进程通信,像intent,contentProvider,广播,service都可以跨进程通信。

intent:这种跨进程方式并不是访问内存的形式,它需要传递一个uri,比如说打电话。

contentProvider:这种形式,是使用数据共享的形式进行数据共享。

service:远程服务,aidl

广播


Android中的几种动画

重要

帧动画:指通过指定每一帧的图片和播放时间,有序的进行播放而形成动画效果,比如想听的律动条。

补间动画:指通过指定View的初始状态、变化时间、方式,通过一系列的算法去进行图形变换,从而形成动画效果,主要有Alpha、Scale、Translate、Rotate四种效果。注意:只是在视图层实现了动画效果,并没有真正改变View的属性,比如滑动列表,改变标题栏的透明度。

属性动画:在Android3.0的时候才支持,通过不断的改变View的属性,不断的重绘而形成动画效果。相比于视图动画,View的属性是真正改变了。比如view的旋转,放大,缩小。


View,ViewGroup事件分发

非常重要

View的事件分发机制可以使用下图表示:

need-to-insert-img

如上图,图分为3层,从上往下依次是Activity、ViewGroup、View。

事件从左上角那个白色箭头开始,由Activity的dispatchTouchEvent做分发

箭头的上面字代表方法返回值,(return true、return false、return super.xxxxx(),super

的意思是调用父类实现。

dispatchTouchEvent和 onTouchEvent的框里有个【true---->消费】的字,表示的意思是如果方法返回true,那么代表事件就此消费,不会继续往别的地方传了,事件终止。

目前所有的图的事件是针对ACTION_DOWN的,对于ACTION_MOVE和ACTION_UP我们最后做分析。

之前图中的Activity 的dispatchTouchEvent 有误(图已修复),只有return

super.dispatchTouchEvent(ev) 才是往下走,返回true 或者 false 事件就被消费了(终止传递)。

ViewGroup事件分发

当一个点击事件产生后,它的传递过程将遵循如下顺序:

Activity -> Window -> View

事件总是会传递给Activity,之后Activity再传递给Window,最后Window再传递给顶级的View,顶级的View在接收到事件后就会按照事件分发机制去分发事件。如果一个View的onTouchEvent返回了FALSE,那么它的父容器的onTouchEvent将会被调用,依次类推,如果所有都不处理这个事件的话,那么Activity将会处理这个事件。

对于ViewGroup的事件分发过程,大概是这样的:如果顶级的ViewGroup拦截事件即onInterceptTouchEvent返回true的话,则事件会交给ViewGroup处理,如果ViewGroup的onTouchListener被设置的话,则onTouch将会被调用,否则的话onTouchEvent将会被调用,也就是说:两者都设置的话,onTouch将会屏蔽掉onTouchEvent,在onTouchEvent中,如果设置了onClickerListener的话,那么onClick将会被调用。如果顶级ViewGroup不拦截的话,那么事件将会被传递给它所在的点击事件的子view,这时候子view的dispatchTouchEvent将会被调用

View的事件分发

dispatchTouchEvent -> onTouch(setOnTouchListener) -> onTouchEvent -> onClick

onTouch和onTouchEvent的区别

两者都是在dispatchTouchEvent中调用的,onTouch优先于onTouchEvent,如果onTouch返回true,那么onTouchEvent则不执行,及onClick也不执行


四种LaunchMode及其使用场景

重要

此处延伸:栈(First In Last Out)与队列(First In First Out)的区别

栈与队列的区别:

1. 队列先进先出,栈先进后出

2. 对插入和删除操作的"限定"。 栈是限定只能在表的一端进行插入和删除操作的线性表。 队列是限定只能在表的一端进行插入和在另一端进行删除操作的线性表。

3. 遍历数据速度不同

standard 模式

这是默认模式,每次激活Activity时都会创建Activity实例,并放入任务栈中。使用场景:大多数Activity。

singleTop 模式

如果在任务的栈顶正好存在该Activity的实例,就重用该实例( 会调用实例的 onNewIntent() ),否则就会创建新的实例并放入栈顶,即使栈中已经存在该Activity的实例,只要不在栈顶,都会创建新的实例。使用场景如新闻类或者阅读类App的内容页面。

singleTask 模式

如果在栈中已经有该Activity的实例,就重用该实例(会调用实例的 onNewIntent() )。重用时,会让该实例回到栈顶,因此在它上面的实例将会被移出栈。如果栈中不存在该实例,将会创建新的实例放入栈中。使用场景如浏览器的主界面。不管从多少个应用启动浏览器,只会启动主界面一次,其余情况都会走onNewIntent,并且会清空主界面上面的其他页面。

singleInstance 模式

在一个新栈中创建该Activity的实例,并让多个应用共享该栈中的该Activity实例。一旦该模式的Activity实例已经存在于某个栈中,任何应用再激活该Activity时都会重用该栈中的实例( 会调用实例的 onNewIntent() )。其效果相当于多个应用共享一个应用,不管谁激活该 Activity 都会进入同一个应用中。使用场景如闹铃提醒,将闹铃提醒与闹铃设置分离。singleInstance不要用于中间页面,如果用于中间页面,跳转会有问题,比如:A -> B (singleInstance) -> C,完全退出后,在此启动,首先打开的是B。


讲解一下Context

非常重要

Context是一个抽象基类。

在翻译为上下文,也可以理解为环境,是提供一些程序的运行环境基础信息。

Context下有两个子类,ContextWrapper是上下文功能的封装类,而ContextImpl则是上下文功能的实现类。而ContextWrapper又有三个直接的子类, ContextThemeWrapper、Service和Application。

其中,ContextThemeWrapper是一个带主题的封装类,而它有一个直接子类就是Activity,所以Activity和Service以及Application的Context是不一样的,只有Activity需要主题,Service不需要主题。

Context一共有三种类型,分别是Application、Activity和Service。这三个类虽然分别各种承担着不同的作用,但它们都属于Context的一种,而它们具体Context的功能则是由ContextImpl类去实现的,因此在绝大多数场景下,Activity、Service和Application这三种类型的Context都是可以通用的。

不过有几种场景比较特殊,比如启动Activity,还有弹出Dialog。出于安全原因的考虑,Android是不允许Activity或Dialog凭空出现的,一个Activity的启动必须要建立在另一个Activity的基础之上,也就是以此形成的返回栈。而Dialog则必须在一个Activity上面弹出(除非是System Alert类型的Dialog),因此在这种场景下,我们只能使用Activity类型的Context,否则将会出错。

getApplicationContext()和getApplication()方法得到的对象都是同一个application对象,只是对象的类型不一样。

Context数量 = Activity数量 + Service数量 + 1 (1为Application)


Service生命周期

非常重要

Service 作为 Android四大组件之一,应用非常广泛。和Activity一样,Service 也有一系列的生命周期回调函数,具体如下图。

通常,启动Service有两种方式,startService和bindService方式。


startService生命周期

当我们通过调用了Context的startService方法后,我们便启动了Service,通过startService方法启动的Service会一直无限期地运行下去,只有在外部调用Context的stopService或Service内部调用Service的stopSelf方法时,该Service才会停止运行并销毁。

onCreate

onCreate: 执行startService方法时,如果Service没有运行的时候会创建该Service并执行Service的onCreate回调方法;如果Service已经处于运行中,那么执行startService方法不会执行Service的onCreate方法。也就是说如果多次执行了Context的startService方法启动Service,Service方法的onCreate方法只会在第一次创建Service的时候调用一次,以后均不会再次调用。我们可以在onCreate方法中完成一些Service初始化相关的操作。

onStartCommand

onStartCommand: 在执行了startService方法之后,有可能会调用Service的onCreate方法,在这之后一定会执行Service的onStartCommand回调方法。也就是说,如果多次执行了Context的startService方法,那么Service的onStartCommand方法也会相应的多次调用。onStartCommand方法很重要,我们在该方法中根据传入的Intent参数进行实际的操作,比如会在此处创建一个线程用于下载数据或播放音乐等。

public @StartResult int onStartCommand(Intent intent, @StartArgFlags int flags, int startId) {

}

当Android面临内存匮乏的时候,可能会销毁掉你当前运行的Service,然后待内存充足的时候可以重新创建Service,Service被Android系统强制销毁并再次重建的行为依赖于Service中onStartCommand方法的返回值。我们常用的返回值有三种值,START_NOT_STICKY、START_STICKY和START_REDELIVER_INTENT,这三个值都是Service中的静态常量。

START_NOT_STICKY

如果返回START_NOT_STICKY,表示当Service运行的进程被Android系统强制杀掉之后,不会重新创建该Service,当然如果在其被杀掉之后一段时间又调用了startService,那么该Service又将被实例化。那什么情境下返回该值比较恰当呢?如果我们某个Service执行的工作被中断几次无关紧要或者对Android内存紧张的情况下需要被杀掉且不会立即重新创建这种行为也可接受,那么我们便可将 onStartCommand的返回值设置为START_NOT_STICKY。举个例子,某个Service需要定时从服务器获取最新数据:通过一个定时器每隔指定的N分钟让定时器启动Service去获取服务端的最新数据。当执行到Service的onStartCommand时,在该方法内再规划一个N分钟后的定时器用于再次启动该Service并开辟一个新的线程去执行网络操作。假设Service在从服务器获取最新数据的过程中被Android系统强制杀掉,Service不会再重新创建,这也没关系,因为再过N分钟定时器就会再次启动该Service并重新获取数据。

START_STICKY

如果返回START_STICKY,表示Service运行的进程被Android系统强制杀掉之后,Android系统会将该Service依然设置为started状态(即运行状态),但是不再保存onStartCommand方法传入的intent对象,然后Android系统会尝试再次重新创建该Service,并执行onStartCommand回调方法,但是onStartCommand回调方法的Intent参数为null,也就是onStartCommand方法虽然会执行但是获取不到intent信息。如果你的Service可以在任意时刻运行或结束都没什么问题,而且不需要intent信息,那么就可以在onStartCommand方法中返回START_STICKY,比如一个用来播放背景音乐功能的Service就适合返回该值。

START_REDELIVER_INTENT

如果返回START_REDELIVER_INTENT,表示Service运行的进程被Android系统强制杀掉之后,与返回START_STICKY的情况类似,Android系统会将再次重新创建该Service,并执行onStartCommand回调方法,但是不同的是,Android系统会再次将Service在被杀掉之前最后一次传入onStartCommand方法中的Intent再次保留下来并再次传入到重新创建后的Service的onStartCommand方法中,这样我们就能读取到intent参数。只要返回START_REDELIVER_INTENT,那么onStartCommand重的intent一定不是null。如果我们的Service需要依赖具体的Intent才能运行(需要从Intent中读取相关数据信息等),并且在强制销毁后有必要重新创建运行,那么这样的Service就适合返回START_REDELIVER_INTENT。

onBind

Service中的onBind方法是抽象方法,所以Service类本身就是抽象类,也就是onBind方法是必须重写的,即使我们用不到。在通过startService使用Service时,我们在重写onBind方法时,只需要将其返回null即可。onBind方法主要是用于给bindService方法调用Service时才会使用到。

onDestroy

onDestroy: 通过startService方法启动的Service会无限期运行,只有当调用了Context的stopService或在Service内部调用stopSelf方法时,Service才会停止运行并销毁,在销毁的时候会执行Service回调函数。

bindService生命周期

bindService方式启动Service主要有以下几个生命周期函数:

onCreate():

首次创建服务时,系统将调用此方法。如果服务已在运行,则不会调用此方法,该方法只调用一次。

onStartCommand():

当另一个组件通过调用startService()请求启动服务时,系统将调用此方法。

onDestroy():

当服务不再使用且将被销毁时,系统将调用此方法。

onBind():

当另一个组件通过调用bindService()与服务绑定时,系统将调用此方法。

onUnbind():

当另一个组件通过调用unbindService()与服务解绑时,系统将调用此方法。

onRebind():

当旧的组件与服务解绑后,另一个新的组件与服务绑定,onUnbind()返回true时,系统将调用此方法。


Activity生命周期

非常重要

完整生命周期:完整生命周期始于onCreate方法回调,止于onDestroy方法回调

可见周期:可见周期始于onStart方法回调,止于onStop方法回调

前台周期:前台周期始于onResume方法回调,止于onPause方法回调

下面简单介绍一下各个生命周期方法:

onCreate 生命周期的第一个方法,表示Activity正在创建(启动)。特别说明:若您在该方法内调用finish方法,则会立即出发onDestroy回调,其他生命周期不会执行

onRestart 该方法触发的前提:onStop方法被调用。onStop方法被调用而导致的Activity不可见到Activity再次可见时被调用。该方法回调之后系统会相继触发onStart和onResume方法。

onStart Activity可见时调用(此时Activity尚未处于前台):在onCreate方法之后或由onStop方法被调用而导致的Activity不可见到Activity再次可见时被调用

onResume 该方法的回调标识Activity处于前台。官方文档指明,这里比较适合动画启动及排他性设备访问(如相机)等

onPause Activity即将进入后台时回调此方法。需要特别注意的是,若Activity A启动Activity B,则Activity A的onPause方法回调完成后,Activity B才会创建,因此不要在该回调方法中做耗时操作。

onStop Activity由可见到不可见时回调此方法

onDestroy 生命周期的最后一个方法,表示Activity即将被销毁。官方文档指明,在某些情况下,系统会简单粗暴的杀掉Activity的宿主进程(如下文示意图中的标注1),因此我们不应该依赖此方法做数据存储工作,可在此方法中做资源释放操作

下图为官方文档给出的示意图

此图很清晰严谨,但是官方文档对各个生命周期回调方法的描述并不是很详细,因此如果不认真研读文档,很难明白图中的生命周期方法的走向,并可能对某些方法产生误解。例如对于onStop方法,可能会有部分童鞋对其有误解,误认为Activity进入后台时回调该方法,其实进入后台时回调的是onPause方法,不可见时回调onStop方法;也有童鞋误以为onResume方法回调意味着可以进行交互,其实我们应该以public void onWindowFocusChanged (boolean hasFocus)回调中的hasFocus参数为依据来判定是否可以进行交互。

Activity可见与否与其是否处于前台是两回事,onStart 与 onStop 配对描述Activity是否可见;onPause 与

onResume 配对描述Activity是否处于前台。Activity可见并不意味着可以交互,同样的其处于前台也未必可以交互。直接的例子是,若Activity展示了Dialog,此时Activity仍处于前台,但我们却不能与Activity交互

另外,对于上述示意图中的标注2走向,我相信很多人并没有亲自验证过,因为通常你很难通过交互来复现这一场景。对于这种场景,我们可以通过代码控制来模拟:Activity A启动Activity B,在Activity B的onCreate方法中直接调用finish方法


最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,547评论 6 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,399评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,428评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,599评论 1 274
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,612评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,577评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,941评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,603评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,852评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,605评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,693评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,375评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,955评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,936评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,172评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,970评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,414评论 2 342