Android 知识整理(二)-Android篇

2018-03-05  本文已影响0人  胡二囧

1、如何导入外部数据库?

2、本地广播和全局广播有什么差别?

因广播数据在本应用范围内传播,不用担心隐私数据泄露的问题;不用担心别的应用伪造广播,造成安全隐患;相比在系统内发送全局广播,它更高效。

3、IntentService作用是什么,AIDL解决了什么问题-小米

4、Ubuntu编译安卓系统-百度

5、LaunchMode应用场景-百度-小米-乐视

6、Touch事件传递流程-小米

7、View绘制流程-百度

view_draw_method_chain.png

从整体上来看 Measure 和 Layout 两个步骤的执行:


measure_layout.png

8、多线程-360

原文链接

  1. AsyncTask类必须在主线程中加载。因为AsyncTask里面有个一静态的Handler对象,为了能够将执行环境切换到主线程,这就要求Handler必须在主线程中创建,所以AsyncTask必须在主线程中进行类加载。
  2. AsyncTask的对象必须在主线程中创建。
  3. execute 方法必须在UI线程调用。
  4. 一个AsyncTask对象只能执行一次,即只能调用一次execute方法,否则报运行时异常。
  5. 在Android1.6之前,AsyncTask是串行执行任务的,Android1.6的时候AsyncTask开始使用线程池处理并行任务,但是从Android3.0开始,为了避免AsyncTask所带来的并发错误,AsyncTask又采用一个线程串行执行任务。尽管如此,在Android3.0以及后续的版本中,我们仍然可以通过AsyncTask的executeOnExecutor方法来并行地执行任务。
@Override
public void run() {  
    mTid = Process.myTid();  
    Looper.prepare();    
    synchronized (this) {     
        mLooper = Looper.myLooper();  
        notifyAll(); 
    }  
    Process.setThreadPriority(mPriority);   
    onLooperPrepared();   
    Looper.loop();   
    mTid = -1;
}

HandlerThread因为在内部创建了一个消息队列,所以需要在外部通过Handler发送消息通知HandlerThread执行一个具体的任务。

@Override
public void onCreate() {   
    // TODO: It would be nice to have an option to hold a partial wakelock   
    // during processing, and to have a static startService(Context, Intent)   
    // method that would launch the service & hand off a wakelock.  
    super.onCreate();    
    HandlerThread thread = new HandlerThread("IntentService[" + mName + "]");   
    thread.start();  
    mServiceLooper = thread.getLooper();   
    mServiceHandler = new ServiceHandler(mServiceLooper);
}

IntentService是在onStartCommand中处理每个后台任务的Intent。在该方法中发送消息给ServiceHandler去处理,具体处理方法为:

@WorkerThreadprotected 
abstract void onHandleIntent(Intent intent);
  • 重用线程池中的线程,避免因为线程的创建和销毁所带来的性能开销。
  • 能有效控制线程池的最大并发数,避免大量的线程之间因相互抢占系统资源而导致的阻塞现象。
  • 能够对线程进行简单的管理,并提供定时执行以及指定间隔循环执行等功能。

Android中线程池的概念来源于Java中的Executor,Executor是一个接口,真正的线程池实现为ThreadPoolExecutor。ThreadPoolExecutor提供了一系列参数来配置线程池,主要为4类,在介绍这四类线程池之前先了解一下ThreadPoolExecutor。
ThreadPoolExecutor常用的构造方法:

public ThreadPoolExecutor(
    int corePoolSize,
    int maximumPoolSize,
    long keepAliveTime,
    TimeUnit unit,  
    BlockingQueue<Runnable> workQueue,
    ThreadFactory threadFactory) { 
       this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,threadFactory, defaultHandler);
}

9、Handler,Thread和HandlerThread的差别-小米

原文链接

还是先来看看HandlerThread的使用方法: 首先新建HandlerThread并且执行start()

private HandlerThread mHandlerThread;
......
mHandlerThread = new HandlerThread("HandlerThread");
handlerThread.start();

创建Handler,使用mHandlerThread.getLooper()生成Looper:

final Handler handler = new Handler(mHandlerThread.getLooper()){
    @Override
    public void handleMessage(Message msg) {
        System.out.println("收到消息");
    }
};

然后再新建一个子线程来发送消息:

new Thread(new Runnable() {
    @Override
    public void run() {
        try {
            Thread.sleep(1000);//模拟耗时操作
            handler.sendEmptyMessage(0);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}).start();

最后一定不要忘了在onDestroy释放,避免内存泄漏:

@Override
protected void onDestroy() {
    super.onDestroy();
    mHandlerThread.quit();
}

执行结果很简单,就是在控制台打印字符串:收到消息

HandlerThread的使用方法还是比较简单的,但是我们要明白一点的是:如果一个线程要处理消息,那么它必须拥有自己的Looper,并不是Handler在哪里创建,就可以在哪里处理消息的。

如果不用HandlerThread的话,需要手动去调用Looper.prepare()和Looper.loop()这些方法。

10、线程同步-百度

11、什么情况导致内存泄漏-美团

  1. 资源对象没关闭造成的内存泄漏。 资源性对象比如(Cursor,File文件等)往往都用了一些缓冲,我们在不使用的时候,应该及时关闭它们,以便它们的缓冲及时回收内存。它们的缓冲不仅存在于 java虚拟机内,还存在于java虚拟机外。如果我们仅仅是把它的引用设置为null,而不关闭它们,往往会造成内存泄漏。因为有些资源性对象,比如 SQLiteCursor(在析构函数finalize(),如果我们没有关闭它,它自己会调close()关闭),如果我们没有关闭它,系统在回收它时也会关闭它,但是这样的效率太低了。因此对于资源性对象在不使用的时候,应该调用它的close()函数,将其关闭掉,然后才置为null.在我们的程序退出时一定要确保我们的资源性对象已经关闭。
    程序中经常会进行查询数据库的操作,但是经常会有使用完毕Cursor后没有关闭的情况。如果我们的查询结果集比较小,对内存的消耗不容易被发现,只有在常时间大量操作的情况下才会复现内存问题,这样就会给以后的测试和问题排查带来困难和风险。
  2. 构造Adapter时,没有使用缓存的convertView。 以构造ListView的BaseAdapter为例,在BaseAdapter中提供了方法:
    public View getView(int position, ViewconvertView, ViewGroup parent)
    来向ListView提供每一个item所需要的view对象。初始时ListView会从BaseAdapter中根据当前的屏幕布局实例化一定数量的 view对象,同时ListView会将这些view对象缓存起来。当向上滚动ListView时,原先位于最上面的list item的view对象会被回收,然后被用来构造新出现的最下面的list item。这个构造过程就是由getView()方法完成的,getView()的第二个形参View convertView就是被缓存起来的list item的view对象(初始化时缓存中没有view对象则convertView是null)。由此可以看出,如果我们不去使用 convertView,而是每次都在getView()中重新实例化一个View对象的话,即浪费资源也浪费时间,也会使得内存占用越来越大。
  3. Bitmap对象不在使用时调用recycle()释放内存。 有时我们会手工的操作Bitmap对象,如果一个Bitmap对象比较占内存,当它不在被使用的时候,可以调用Bitmap.recycle()方法回收此对象的像素所占用的内存,但这不是必须的,视情况而定。可以看一下代码中的注释:
  4. 试着使用关于application的context来替代和activity相关的context。 这是一个很隐晦的内存泄漏的情况。有一种简单的方法来避免context相关的内存泄漏。最显著地一个是避免context逃出他自己的范围之外。使用Application context。这个context的生存周期和你的应用的生存周期一样长,而不是取决于activity的生存周期。如果你想保持一个长期生存的对象,并且这个对象需要一个context,记得使用application对象。你可以通过调用 Context.getApplicationContext() or Activity.getApplication()来获得。
  5. 注册没取消造成的内存泄漏。 一些Android程序可能引用我们的Anroid程序的对象(比如注册机制)。即使我们的Android程序已经结束了,但是别的引用程序仍然还有对我们的Android程序的某个对象的引用,泄漏的内存依然不能被垃圾回收。调用registerReceiver后未调用unregisterReceiver。
    比如:假设我们希望在锁屏界面(LockScreen)中,监听系统中的电话服务以获取一些信息(如信号强度等),则可以在LockScreen中定义一个 PhoneStateListener的对象,同时将它注册到TelephonyManager服务中。对于LockScreen对象,当需要显示锁屏界面的时候就会创建一个LockScreen对象,而当锁屏界面消失的时候LockScreen对象就会被释放掉。
    但是如果在释放 LockScreen对象的时候忘记取消我们之前注册的PhoneStateListener对象,则会导致LockScreen无法被垃圾回收。如果不断的使锁屏界面显示和消失,则最终会由于大量的LockScreen对象没有办法被回收而引起OutOfMemory,使得system_process 进程挂掉。
    虽然有些系统程序,它本身好像是可以自动取消注册的(当然不及时),但是我们还是应该在我们的程序中明确的取消注册,程序结束时应该把所有的注册都取消掉。
  6. 集合中对象没清理造成的内存泄漏。 我们通常把一些对象的引用加入到了集合中,当我们不需要该对象时,并没有把它的引用从集合中清理掉,这样这个集合就会越来越大。如果这个集合是static的话,那情况就更严重了。

12、ANR定位和修正

如果开发机器上出现问题,我们可以通过查看/data/anr/traces.txt即可,最新的ANR信息在最开始部分。

Android系统会监控程序的响应状况,一旦出现下面两种情况,则弹出ANR对话框

13、什么情况导致oom-乐视-美团

14、Service与Activity之间通信的几种方式

通过Binder对象
通过broadcast(广播)的形式

15、如何保证Service在后台不被Kill

16、RequestLayout, onLayout, onDraw, DrawChild区别与联系-猎豹

17、Android动画框架实现原理

Animation框架定义了透明度,旋转,缩放和位移几种常见的动画,而且控制的是整个View,实现原理是每次绘制视图时View所在的ViewGroup中的drawChild函数获取该View的Animation的Transformation值,然后调用canvas.concat(transformToApply.getMatrix()),通过矩阵运算完成动画帧,如果动画没有完成,继续调用invalidate()函数,启动下次绘制来驱动动画,动画过程中的帧之间间隙时间是绘制函数所消耗的时间,可能会导致动画消耗比较多的CPU资源,最重要的是,动画改变的只是显示,并不能相应事件。

18、Android为每个应用程序分配的内存大小是多少-美团

android程序内存一般限制在16M,也有的是24M。近几年手机发展较快,一般都会分配两百兆左右,和具体机型有关。

19、优化自定义View百度-乐视-小米

为了加速你的view,对于频繁调用的方法,需要尽量减少不必要的代码。先从onDraw开始,需要特别注意不应该在这里做内存分配的事情,因为它会导致GC,从而导致卡顿。在初始化或者动画间隙期间做分配内存的动作。不要在动画正在执行的时候做内存分配的事情。

你还需要尽可能的减少onDraw被调用的次数,大多数时候导致onDraw都是因为调用了invalidate().因此请尽量减少调用invaildate()的次数。如果可能的话,尽量调用含有4个参数的invalidate()方法而不是没有参数的invalidate()。没有参数的invalidate会强制重绘整个view。

另外一个非常耗时的操作是请求layout。任何时候执行requestLayout(),会使得Android UI系统去遍历整个View的层级来计算出每一个view的大小。如果找到有冲突的值,它会需要重新计算好几次。另外需要尽量保持View的层级是扁平化的,这样对提高效率很有帮助。

如果你有一个复杂的UI,你应该考虑写一个自定义的ViewGroup来执行他的layout操作。与内置的view不同,自定义的view可以使得程序仅仅测量这一部分,这避免了遍历整个view的层级结构来计算大小。这个PieChart 例子展示了如何继承ViewGroup作为自定义view的一部分。PieChart 有子views,但是它从来不测量它们。而是根据他自身的layout法则,直接设置它们的大小。

20、Volley-美团-乐视

21、Glide源码解析

22、Android设计模式

23、Android属性动画特性-乐视-小米

如果你的需求中只需要对View进行移动、缩放、旋转和淡入淡出操作,那么补间动画确实已经足够健全了。但是很显然,这些功能是不足以覆盖所有的场景的,一旦我们的需求超出了移动、缩放、旋转和淡入淡出这四种对View的操作,那么补间动画就不能再帮我们忙了,也就是说它在功能和可扩展方面都有相当大的局限性,那么下面我们就来看看补间动画所不能胜任的场景。

注意上面我在介绍补间动画的时候都有使用“对View进行操作”这样的描述,没错,补间动画是只能够作用在View上的。也就是说,我们可以对一个Button、TextView、甚至是LinearLayout、或者其它任何继承自View的组件进行动画操作,但是如果我们想要对一个非View的对象进行动画操作,抱歉,补间动画就帮不上忙了。可能有的朋友会感到不能理解,我怎么会需要对一个非View的对象进行动画操作呢?这里我举一个简单的例子,比如说我们有一个自定义的View,在这个View当中有一个Point对象用于管理坐标,然后在onDraw()方法当中就是根据这个Point对象的坐标值来进行绘制的。也就是说,如果我们可以对Point对象进行动画操作,那么整个自定义View的动画效果就有了。显然,补间动画是不具备这个功能的,这是它的第一个缺陷。

然后补间动画还有一个缺陷,就是它只能够实现移动、缩放、旋转和淡入淡出这四种动画操作,那如果我们希望可以对View的背景色进行动态地改变呢?很遗憾,我们只能靠自己去实现了。说白了,之前的补间动画机制就是使用硬编码的方式来完成的,功能限定死就是这些,基本上没有任何扩展性可言。

最后,补间动画还有一个致命的缺陷,就是它只是改变了View的显示效果而已,而不会真正去改变View的属性。什么意思呢?比如说,现在屏幕的左上角有一个按钮,然后我们通过补间动画将它移动到了屏幕的右下角,现在你可以去尝试点击一下这个按钮,点击事件是绝对不会触发的,因为实际上这个按钮还是停留在屏幕的左上角,只不过补间动画将这个按钮绘制到了屏幕的右下角而已。

24、Activity Window View三者的差别,fragment的特点-360

Activity像一个工匠(控制单元),Window像窗户(承载模型),View像窗花(显示视图)
LayoutInflater像剪刀,Xml配置像窗花图纸。

  1. 在Activity中调用attach,创建了一个Window
  2. 创建的window是其子类PhoneWindow,在attach中创建PhoneWindow
  3. 在Activity中调用setContentView(R.layout.xxx)
  4. 其中实际上是调用的getWindow().setContentView()
  5. 调用PhoneWindow中的setContentView方法
  6. 创建ParentView:作为ViewGroup的子类,实际是创建的DecorView(作为FramLayout的子类)
  7. 将指定的R.layout.xxx进行填充,通过布局填充器进行填充【其中的parent指的就是DecorView】
  8. 调用到ViewGroup
  9. 调用ViewGroup的removeAllView(),先将所有的view移除掉
  10. 添加新的view:addView()

Fragment 特点

  1. Fragment可以作为Activity界面的一部分组成出现;
  2. 可以在一个Activity中同时出现多个Fragment,并且一个Fragment也可以在多个Activity中使用;
  3. 在Activity运行过程中,可以添加、移除或者替换Fragment;
  4. Fragment可以响应自己的输入事件,并且有自己的生命周期,它们的生命周期会受宿主Activity的生命周期影响。

25、invalidate和postInvalidate的区别及使用-百度

26、LinearLayout和RelativeLayout性能对比-百度

为什么Google给开发者默认新建了个RelativeLayout,而自己却在DecorView中用了个LinearLayout。因为DecorView的层级深度是已知而且固定的,上面一个标题栏,下面一个内容栏。采用RelativeLayout并不会降低层级深度,所以此时在根节点上用LinearLayout是效率最高的。而之所以给开发者默认新建了个RelativeLayout是希望开发者能采用尽量少的View层级来表达布局以实现性能最优,因为复杂的View嵌套对性能的影响会更大一些。

27、View刷新机制-百度-美团

由ViewRoot对象的performTraversals()方法调用draw()方法发起绘制该View树,值得注意的是每次发起绘图时,并不会重新绘制每个View树的视图,而只会重新绘制那些“需要重绘”的视图,View类内部变量包含了一个标志位DRAWN,当该视图需要重绘时,就会为该View添加该标志位。

mView.draw()开始绘制,draw()方法实现的功能如下:

  • 绘制该View的背景
  • 为显示渐变框做一些准备操作(见5,大多数情况下,不需要改渐变框)
  • 调用onDraw()方法绘制视图本身 (每个View都需要重载该方法,ViewGroup不需要实现该方法)
  • 调用dispatchDraw ()方法绘制子视图(如果该View类型不为ViewGroup,即不包含子视图,不需要重载该方法)值得说明的是,ViewGroup类已经为我们重写了dispatchDraw ()的功能实现,应用程序一般不需要重写该方法,但可以重载父类函数实现具体的功能。

28、从网络加载一个10M的图片,说下注意事项-腾讯

图片缓存、异常恢复、质量压缩

29、进程间通信方式

30、什么是协程

31、Android自定义控件原理

32、Service生命周期

官方示意图 生命周期方法具体介绍 常见的生命周期使用

33、Android系统启动流程是什么

系统启动流程.png
上一篇下一篇

猜你喜欢

热点阅读