Android初级知识整理--面试

ANR

一、什么是ANR
Application Not Responding
Activity-》5秒,Broadcast Receiver是10秒。在主线程中进行了耗时操作。
二、造成ANR的主要原因
主线程被IO操作阻塞
主线程中存在耗时的计算
Android中哪些操作是在主线程呢?(重要)
①Activity的所有生命周期回调都是执行在主线程中的
②Service默认是执行在主线程的
③BroadcastReceiver的onReceive回调是执行在主线程的
④没有使用子线程的looper的Handler的handleMessage,post(Runnable)是执行在主线程的
⑤AsyncTask的回调除了doInBackground,其他都是执行在主线程
三、如何解决ANR
①使用AsyncTask处理耗时IO操作
②使用Thread或者HandlerThread(Handler+Thread,内部实现了looper,实现了一个消息队列,③主要是onNewIntent方法)提高优先级
④使用handler来处理工作线程的耗时任务
⑤Activity的onCreate和onResume中不要进行耗时操作

UI卡顿

一、UI卡顿原理
每秒60fps;页面渲染复杂,操作过多。
overdraw:过度绘制,有大量重叠的部分。
比如activity有背景颜色,而layout也有,连子View中也有,就算只去掉其中的背景色,也可以减少overdraw的情况(使UI不那么卡顿)。
手机检测工具:
设置–>开发者选项–>调试GPU过度绘制–>显示GPU过度绘制
每个颜色说明如下:
原色:没有过度绘制
蓝色:1 次过度绘制
绿色:2 次过度绘制
粉色:3 次过度绘制
红色:4 次及以上过度绘制
总之一句话:在绘制的时候布局层级太多
二、UI卡顿原因分析
1.人为的在UI线程中做轻微耗时操作(轻量版ANR),导致UI线程卡顿。
2.布局Layout过于复杂,无法在16ms内完成系统渲染(背景一定不要重叠)。
3.同一时间动画执行次数过多,导致CPU或GPU负载过重
4.View过度绘制,导致某些像素在同一时间内被多次绘制
5.View绘制触发的measure、layout,导致measure、layout累计耗时过多及整个View频繁的重新渲染。
6.内存频繁触发GC过多,导致暂时阻塞渲染操作。
7.冗余资源及逻辑等导致加载和执行较慢。
8.ANR
三、UI卡顿总结
1.布局优化,include,merge,viewstub(尽量用gone代替invisible)
①merge:
使用场景:
如果Activity的布局文件根节点是FrameLayout,可以替换为merge标签,这样,执行setContentView之后,会减少一层FrameLayout节点。
自定义View如果继承LinearLayout,建议让自定义View的布局文件根节点设置成merge,这样能少一层结点。
注意问题:
只能作为XML布局的根标签使用。当Inflate以开头的布局文件时,必须指定一个父ViewGroup,并且必须设定attachToRoot为true。
②ViewStub
,在加载一个布局时,有一些在相应操作才显示的控件,如果一开始就初始化,无疑是浪费了没有必要的资源,而viewStub一个轻量级的view,不占用布局空间,占用资源很少。
ViewStub是一个轻量级的View,它一个看不见的,不占布局位置,占用资源非常小的控件。可以为ViewStub指定一个布局,在Inflate布局的时候,只有ViewStub会被初始化,然后当ViewStub被设置为可见的时候,或是调用了ViewStub.inflate()的时候,ViewStub所向的布局就会被Inflate和实例化,然后ViewStub的布局属性都会传给它所指向的布局
使用场景:
根据不同的逻辑时在某个页面显示不同的布局。(要显示二种不同的布局,一个是用TextView显示一段文字,另一个则是用ImageView显示一个图片。这两个是在onCreate()时决定是显示哪一个,这里就是应用ViewStub的最佳地点。)
使用

 <ViewStub android:id="@+id/stub"
               android:inflatedId="@+id/subTree"
               android:layout="@layout/mySubTree"
               android:layout_width="120dip"
               android:layout_height="40dip" />
ViewStub stub = (ViewStub) findViewById(R.id.stub);
View inflated = stub.inflate();

2.列表及adapter优化(监听滑动,滑动结束的时候再去加载图片,不要在滑动的时候加载)
3.背景和图片等内存分配优化
4.避免ANR


内存泄露

一、java内存泄露
该被释放的对象没有被释放,一直被某些对象所持有。(生命周期长的对象一直持有生命周期短的对象,不能及时释放)
1.java内存分配策略
①静态分配,对应静态存储区(也叫方法区),主要存放一些静态数据、全局变量;存放的变量在整个程序运行期间都存在。
②栈式分配,对应栈区,方法体内的局部变量会在栈上创建内存空间,并在方法结束后,被自动释放。
③堆式分配,对应堆区(又叫动态内存分配),new对象出来的内存
2.java如何管理内存
对象要new出来后分配空间,内存的释放由GC负责。
在这里插入图片描述
如上图,obj2在main函数中已经无法直接访问,就成为了可回收对象。这个对象,就是左边第二个o2 new出来的Object().
3.java中的内存泄露
内存泄露是指无用对象(不再使用的对象)持续占有内存或无用对象的内存得不到及时释放,从而造成的内存空间的浪费叫内存泄露。不断积累会造成OOM。
二、android内存泄露
1.单例
类中的Context对象用传过来的Context的context.getApplicationContext(),如果用Activity的Context,可能会导致Activity销毁后单例还持有activity对象,导致内存泄露。
2.匿名内部类
在java中非静态内部类默认持有外部类(Activity)的引用,而如果这个非静态内部类内部创建了一个静态变量的实例的话,该实例就跟应用一样长
所以我们用内部类的时候尽量用静态内部类。
3.Handler
如果是一般的:
private Handler handler=new Handler…
这种的话,handler是非静态内部类,持有当前activity的引用,handler的消息在Looper.loop()方法里阻塞处理,如果当前activity已经退出,而MessageQueue里还有未处理的消息,而消息队列中的message又持有handler的引用,handler又持有activity的引用,就会造成内存泄漏。
为了避免内存泄露,使用静态内部类和弱引用

private DataHandler zHandler=new DataHandler(this);
    /**
     * 防止内存泄露,静态内部类
     */
    private static class DataHandler extends Handler{
        //弱引用
        WeakReference<CtmPTRActivity> mActivity;
        public DataHandler(CtmPTRActivity activity){
            mActivity=new WeakReference<>(activity);
        }

        @Override
        public void handleMessage(Message msg) {
            CtmPTRActivity ctmPTRActivity=mActivity.get();
            switch (msg.what){
                case 1:
                    //数据加载完毕
                    ctmPTRActivity.addDatas();
                    ctmPTRActivity.adapter.notifyDataSetChanged();
                    ToastUtils.shortMsg("数据已更新!");
                    ctmPTRActivity.pullToRefreshView.dataCompleated();
                    break;
            }
            super.handleMessage(msg);
        }
    }

4.避免使用static变量
static 变量的生命周期和整个app的生命周期是一致的。如果架构的时候需要用static变量,一定要管理好这些生命周期。
5.资源未关闭造成的内存泄露
stream、socket、IO
6.AsyncTask造成的内存泄露
和handler差不多,都是非静态内部类持有外部对象的应用;可以在onDestroy中取消掉asyncTask任务(有个cancel方法)
7.Bitmap要记得recycle

内存管理

一、内存管理机制概述
1.分配机制
android会为每个进程分配一个较小的初始内存空间,在进程运行的时候再动态分配内存,并不是无限大小分配的。
2.回收机制
android进程分类:
前台进程(运行在前台的进程)
可见进程(没有运行在后台,用户仍能看到的进程)
服务进程(提供服务的进程,例如定位等)
后台进程(在后台进行计算的一些进程)
空进程(平衡整个系统的稳定性)
前三个,尽可能不杀死。
3.内存管理机制的特点
①更少的占用内存
②在合适的时候,合理的释放系统资源(频繁释放,会导致内存抖动,产生UI卡顿,ANR等)
③在系统内存紧张的情况下,能释放掉大部分不重要的资源,(Cursor、IO等),来为Android系统提供可用的内存。
④能够合理的在特殊生命周期中,保存或者还原重要数据,以至于系统能够正确的重新恢复该应用。
4.内存优化方法
①当Service完成任务后,尽量停止它(可以用IntentService来替代Service进行耗时任务,IntentService内部开启一个子线程,用完自动退出)
②在UI不可见的时候,释放掉一些只有UI使用的资源
③在系统内存紧张的时候,尽可能多的释放掉一些非重要资源
④避免滥用Bitmap导致的内存浪费,用完Bitmap调用其recycle方法
⑤使用针对内存优化过的数据容易(少用枚举!内存消耗是一般变量的两倍)
⑥避免使用依赖注入的框架
⑦使用ZIP对齐的APK
⑧使用多进程
5.内存溢出vs内存泄露
①内存溢出嘛,OOM,最多的还是Bitmap,bitmap太大了(给bitmap裁剪啊,及时recycle啦)
②内存泄露:本来应该被GC回收的内存,没有被回收掉,非静态内部类持有外部类引用,被GC的时候回收不了。

冷启动优化

一、什么是冷启动
1.冷启动定义
冷启动就是在启动应用前,系统中没有该应用的任何进程信息
2.冷启动/热启动的区别
热启动:用户使用返回键退出应用,然后马上又重新启动应用。(进程保留在后台)
两者区别:热启动不用初始化Application类,只需要初始化MainActivity
3.冷启动时间的计算
这个时间值是从应用启动(创建进程)开始计算,到完成视图的第一次绘制(即Activity内容对用户可见)为止。
二、冷启动流程
Zygote进程中fork出一个新的进程
创建和初始化Application类、创建MainActivity类
inflate布局、当onCreate/onStart/onResume方法都走完
contentView的measure/layout/draw显示在桌面上。
三、如何对冷启动的事件进行优化
1.减少onCreate()方法的工作量
2.不要让Application参与业务操作
3.不要再Application进行耗时操作
4.不要以静态变量的方式在Application中保存数据
5.布局(ViewStub–按需加载)/mainThread

其他优化

一、android不用静态变量存储数据
1.静态变量等数据由于进程已经被杀死而被初始化
2.使用其他数据传输方式:文件/SharePreference/Content Provider等
二、有关Sharepreference问题
1.不能跨进程同步
2.存储Sharepreference的文件过大问题
三、内存对象序列化
序列化:将对象的状态信息转换为可以存储或传输的形式的过程
1.Serializable:在序列化的时候会产生大量的临时变量,导致频繁的GC,最坏会发生OOM,而且使用IO读写存储在硬盘上,使用了反射,效率低。
2.Parcelable:直接在内存中读写,速度更快。Parcelable不能使用在要将数据存储在磁盘上的情况。
四、避免UI线程耗时操作。


SharePreference:存储配置信息(替代我们的KeyValueMgr)

package com.rye.catcher.common;

import android.content.Context;
import android.content.SharedPreferences;

import com.rye.catcher.utils.EchatAppUtil;

import java.util.concurrent.ConcurrentHashMap;

/**
 * Created at 2019/2/27.
 *
 * @author Zzg
 * @function: 使用SP存储配置信息,90K空间
 */
public class KeyValueMgr {
   private static final String SP_NAME="Z-RYE-CATCHER";
    //内存缓存
   private static ConcurrentHashMap concurrentHashMap=new ConcurrentHashMap();

    public static String getValue(String key){
        if (concurrentHashMap.contains(key)){
            return (String) concurrentHashMap.get(key);
        }
        SharedPreferences sp= EchatAppUtil.getAppContext()
                .getSharedPreferences(SP_NAME, Context.MODE_PRIVATE);
        return sp.getString(key,"");
    }
    public static void setValue(String key,Object value){
        if (key==null){
            throw new IllegalStateException("key can not be null!");
        }
        SharedPreferences sp=EchatAppUtil.getAppContext()
                .getSharedPreferences(SP_NAME,Context.MODE_PRIVATE);
        SharedPreferences.Editor editor=sp.edit();
        editor.putString(key,String.valueOf(value));
        editor.commit();
        saveMap(key,value);
    }
    private static  void saveMap(String key ,Object value){
        //保存到内存中
        if (concurrentHashMap.size()>200){//内存过大清空,可能导致问题,待后续完善
            concurrentHashMap.clear();
        }
        concurrentHashMap.put(key,String.valueOf(value));

    }
}


Android架构相关…

MVC

一、MVC定义
1.M:业务逻辑处理(比如操作数据库,网络访问等耗时任务)
2.V:处理数据显示的部分(对应于Android的布局XML文件)
3.C:Activity处理用户交互问题(Activity)
二、MVC特点
1.耦合性低
2.可扩展行好
3.生命周期成本低
缺点:Controller层任务繁重、既要负责视图处理又要处理业务逻辑,代码冗余度高

MVP

一、MVP定义
1.M:依然是业务逻辑和实体模型
2.V:对应于Activity,负责View的绘制以及与用户交互
3.P:负责完成View与Model间的交互

二、MVC和MVP的区别
在这里插入图片描述
最大的区别就是:MVC中,V层可直接跟Model层交互,而在MVP中,V层只能通过P层和Model层交互。
二、MVP特点:
1.View不直接与Model交互,而是通过与Presenter交互来与Model间接交互。
2.Presenter与View的交互是通过接口来进行的。
3. 通常View与Presenter是一对一的,但复杂的View可能绑定多个Presenter来处理逻辑。
一篇很好的MVP实例博客
MVP理论知识
在MVP 架构中跟MVC类似的是同样也分为三层。

Activity 和Fragment 视为View层,负责处理 UI。

Presenter 为业务处理层,既能调用UI逻辑,又能请求数据,该层为纯Java类,不涉及任何Android API。

Model 层中包含着具体的数据请求,数据源。

三层之间调用顺序为view->presenter->model,为了调用安全着想不可反向调用!不可跨级调用!
Model 层如何反馈给Presenter 层的呢?Presenter 又是如何操控View 层呢?看图!
在这里插入图片描述
从上图中可以看出低层的不会直接给上一层做反馈,而是通过View、Callback给上级做出反馈,这样就实现了请求数据与更新界面的异步操作。View、Callback都是以接口的形式存在的,其中View是经典MVP架构中定义的,Callback是自己加的。

插件化

一、插件化来由
当程序里的方法数超过65536/64K之后,就无法再增加方法。功能模块的解耦,职能分离是大势所趋。
二、插件化要解决的问题
1.动态加载apk

2.资源加载

3.代码加载
java类加载器的作用是把java字节码文件加载到java虚拟机中,对应android的类加载器(PathClassLoader、DexClassLoader)是将dex字节码文件加载到dalvik虚拟机中.
DexClassLoader是一个可以从包含classes.dex实体的.jar或.apk文件中加载classes的类加载器。可以用于实现dex的动态加载、代码热更新等等。这个类加载器必须要一个app的私有、可写目录来缓存经过优化的classes(odex文件)。
PathClassLoader提供一个简单的ClassLoader实现,可以操作在本地文件系统的文件列表或目录中的classes,但不可以从网络中加载classes
两者区别
主要的区别在于PathClassLoader的optimizedDirectory参数只能是null,optimizedDirectory是用来缓存我们需要加载的dex文件的,并创建一个DexFile对象,如果它为null,那么会直接使用dex文件原有的路径来创建DexFile 对象。
optimizedDirectory必须是一个内部存储路径,无论哪种动态加载,加载的可执行文件一定要存放在内部存储。DexClassLoader可以指定自己的optimizedDirectory,所以它可以加载外部的dex,因为这个dex会被复制到内部路径的optimizedDirectory;而PathClassLoader没有optimizedDirectory,所以它只能加载内部的dex,这些大都是存在系统中已经安装过的apk里面的
总而言之
DexClassLoader:能够加载未安装的jar/apk/dex (加载apk中的字节码)
PathClassLoader:只能加载系统中已经安装过的apk(只能加载文件目录下的apk)

Android热更新

一、热更新流程
1.线上检测到严重的crash
2.拉取bugfix分支并在分支上修复问题
3.jenkins构建和补丁生成
4.app通过推送或主动拉取补丁文件
5.将bugfix代码合到master上

二、热更新原理
1.Android类加载机制
①PathClassLoader:主要用于加载系统的类和文件
②DexClassLoader:加载一些dex、jar包、apk包。
2.热修复机制
①dexElements
②ClassLoader会遍历这个数组
主要通过在ClassLoader中创建一个dexElements的dex数组,然后根据线上的crash定位,打出补丁包,找到对应的类文件,然后把这个类文件打包成一个dex文件后,放到dexElements数组的最前方,这样classLoader在遍历这个数组的时候就不会有问题的dex文件,只会加载最前方改好的dex文件。

进程保活

一、android进程的优先级
1.Foreground process
前台进程,与用户交互的进程
2.Visible process
可见进程,没有可视界面,一般不会回收,除非前台进程资源不够了
3.Service processs
服务进程,后台操作一些数据,除非内存支持不了前台进程和可见进程的运行,才会回收Service process,其他情况下不会
4.Background process
后台进程
5.Empty process
就是用作缓存,用来减少下次启动的时间

二、android进程的回收策略
1.Low memory killer:通过一些比较复杂的评分机制,对进程进行打分,然后将分数高的进程判定为bad进程,杀死并释放内存。
2.OOM_ODJ:判别进程的优先级,值越小,级别越高,越不容易被杀死。
三、进程保活方案
1.利用系统广播拉活
开机、网络变化等
缺点:
①被第三方软件自启动管理介入的情况下,会接收不到广播。
②必须得发生这些事件的时候才能接收广播,不能保证死了立马活。
2.利用系统Service机制拉活
如果在Service中的onStartCommand方法中返回START_STICKY,那么如果app因为内存不足被杀死时,就会尝试重新唤醒Service。
有两种情况下拉不活:
①Service被连续杀死,第一次杀死五秒后,尝试,第二次,十秒,第三次,二十秒。
②root后,通过stopService,也无法拉活。
3.利用Native进程拉活
5.0后本地进程加强管理了,方法已经失效了。
4.利用JobScheduler机制拉活
5.利用账号同步机制拉活
失效


第二部分

设计模式-单例

一、单例概念
单例模式是一种对象创建模式,它用于产生一个对象的具体实例,它可以确保系统中一个类只产生一个实例
二、好处
1.对于频繁使用的对象,可以省略创建对象所花费的时间,这对于那些重量级对象而言,是非常可观的一笔系统开销
2.由于new操作的次数减少,因而对系统内存的使用频率也会降低,这将减轻GC压力,缩短GC停顿时间
三、单例的六种写法和各自特点
饿汉/懒汉/懒汉线程安全/DCL/静态内部类/枚举
1.饿汉模式

public class HungrySingleton {

    private static final HungrySingleton singleton = new HungrySingleton();

    private HungrySingleton() {
        System.out.println("HungrySingleton instance");
    }

    public static HungrySingleton getInstance() {
        return singleton;
    }
}

饿汉模式有三个特点:
①首先实例是static的
②有个private的构造函数
③获取单例的方法是static的
缺点:
无法对instance实例做延时加载,可能getInstance()永远不会执行到,但执行该类的其他静态方法或者加载了该类(class.forName),那么这个实例仍然初始化。
2.懒汉模式

/**
 * Created at 2019/2/28.
 *
 * @author Zzg
 * @function: 懒汉模式
 */
public class LazySingleton {
    private static  LazySingleton instance;
    private LazySingleton(){

    }
    public static LazySingleton getInstance(){
        if (instance==null){
            instance=new LazySingleton();
        }
        return  instance;
    }
    public  static void createString(){
        System.out.println("create String...");
    }
}

从上面可以看出,懒汉模式:
instance一开始没有初始化,而是在getInstance的时候,判断一下实例是否为空,为空再去实例化。
实际运行会发现:
在这里插入图片描述
在多线程中,可能一个线程中,正在创建一个singleton还没创建完,但线程二正好这个时候判断,就为空了。所以,缺点:
在多线程并发下这样的实现是无法保证实例唯一。
3.懒汉安全模式
就是为了解决上面的线程安全问题的,主要用synchronized关键字实现线程同步

package com.rye.catcher.project.singletons;

/**
 * Created at 2019/2/28.
 *
 * @author Zzg
 * @function:
 */
public class SafeLazySingleton {
    private static SafeLazySingleton instance;
    private SafeLazySingleton(){
    }

    public static void main(String[] args){

    }

    /**
     * 主要用synchronized同步方法
     * @return
     */
    public static synchronized  SafeLazySingleton getInstance(){
        if (instance==null){
            instance=new SafeLazySingleton();
        }
        return instance;
    }

    /**
     * 同步代码块
     * @return
     */
    public static SafeLazySingleton getInstance2(){
        synchronized (SafeLazySingleton.class){
            if (instance==null){
                instance=new SafeLazySingleton();
            }
        }
        return instance;
    }
}

缺陷:性能效率问题
优化版:DCL(双重锁机制)
4.DCL

package com.rye.catcher.project.singletons;

/**
 * Created at 2019/2/28.
 *
 * @author Zzg
 * @function: DCL双重锁机制(两次检查instance是否为空,提高效率)
 */
public class DCLSingleton {

    //private static DCLSingleton mInstance=null;


    //volatile关键字是可见性,保证我们本地没有mInstance的副本,禁止jvm指令重排序优化,
    // 下面三步就可以按照顺序进行,保证线程安全
    private static volatile DCLSingleton mInstance=null;
    private DCLSingleton(){

    }
    public void doSomething(){
        System.out.println("hello z!");
    }
    public static DCLSingleton getInstance(){
        if (mInstance==null){//节省效率
            synchronized (DCLSingleton.class){
                if (mInstance==null){//可能多个线程同时进入外部的检查,不锁上可能会创建多个实例
                    mInstance=new DCLSingleton();
                    //不是原子操作,jvm先给instance分配内存,第二步调用此类的构造方法
                    //来构造变量,第三步就将instance对象指向我们刚才分配的内存空间
                    //以上三步顺序不确定!!可能导致出错,解决方法给instance加上 volatile关键字
                }
            }
        }
        return mInstance;
    }
}

不足之处:
JVM的即时编译器存在指令重排序的优化,解决方法上面给了,用volatile关键字。
优化:静态内部类/枚举(关键点)
5.静态内部类单例模式

package com.rye.catcher.project.singletons;

/**
 * Created at 2019/2/28.
 *
 * @author Zzg
 * @function:静态内部类
 */
public class StaticInnerSingleton {

    public static StaticInnerSingleton getInstance(){
        return Singleton.INSTANCE;
    }

    /**
     * 不使用这个类的时候,jvm就不会加载这个类,
     * 完成了懒汉加载,也通过static关键字实现了线程安全
     * static保证了内存中的唯一性,synchronized十分影响性能
     */
    private static class Singleton{
        private static final StaticInnerSingleton INSTANCE=
                new StaticInnerSingleton();
    }
}

优点:JVM本身机制保证了线程安全/没有性能缺陷
原因:static final
6.枚举

package com.rye.catcher.project.singletons;

/**
 * Created at 2019/2/28.
 *
 * @author Zzg
 * @function:枚举类型单例
 */
public enum EnumSingleton {
    INSTANCE;
    public void doSomething(){
        System.out.println("ok..");
    }
}

写法可以说相当简便了。。
优点:
写法简单/线程安全(自己添加实体方法和变量要注意线程安全)

总结
饿汉:无法对instance实例进行延迟加载
懒汉:多线程并发情况下无法保证实例的唯一性
懒汉线程安全:使用synchoronized导致性能缺陷
DCL:JVM即时编译器的指令重排序
静态内部类/枚举:延迟加载/线程安全/性能优势
三、在Android中的单例
1.application
在这里插入图片描述

设计模式-builder

一、java的buider模式详解
1.概念
建造者模式是较为复杂的创建型模式,它将客户端与包含多个组成部分的对象的创建过程分离(Glide、Okhttp)
2.使用场景
当构造一个对象需要很多参数的时候,并且参数的个数或类型不确定的时候(比如okhttp中可以选择设置拦截器、缓存啊等)
3.UML结构图分析
在这里插入图片描述
①这里,Builder是抽象建造者既可以是抽象类,也可以是接口);
②buildPart方法是用来创建复杂对象的各个部件;
③getResult是用来返回已经创建完成的复杂对象;
④ConcreteBuilder为具体建造者,实现了bulider接口,实现了各个部件的具体方法(通过getResult获得产品,然后再实现的接口里实现产品属性)。
也可以提供一个方法返回创建好的复杂对象。(上图getResult)
⑤Director
导演,负责统筹创建过程、次序。客户端只需要跟Director交互即可,不用跟其他两个交互。
4.实际代码分析
*暂不贴
总结:
①Builder:它为创建一个产品Product对象的各个部件指定抽象接口
②ConcreateBuilder:它实现了Builder接口,实现各个部件的具体构造和装配方法
③Product:它是被构建的复杂独享,包含多个组成部分
④Director:指挥者又称为导演类,它负责安排复杂对象的构造次序,指挥者与抽象建造者之间存在关联关系。(实际使用中可能不用,直接用ConcreateBuilder了)

5.builder模式优点
①松散耦合:生成器模式可以用同一个构造算法构建出表现上完全不同的产品,实现产品构建和产品表现上的分离。
②可以很容易的改变产品的内部表示
③更好的复用性:生成器模式很好的实现构建算法和具体产品实现的分离

6.builder模式缺点
①会产生多余的Builder对象以及Director对象,消耗内存
②暴露了构造过程
二、builder模式在android中的实际运用
1.AlertDialog
2.Glide/Okhttp


AlertDialog部分源码解析:

public class AlertDialog extends Dialog implements DialogInterface {
    private AlertController mAlert;

    /**
     * Special theme constant for {@link #AlertDialog(Context, int)}: use
     * the traditional (pre-Holo) alert dialog theme.
     *
     * @deprecated Use {@link android.R.style#Theme_Material_Dialog_Alert}.
     */
    @Deprecated
    public static final int THEME_TRADITIONAL = 1;
******
******
    AlertDialog(Context context, @StyleRes int themeResId, boolean createContextThemeWrapper) {
        super(context, createContextThemeWrapper ? resolveDialogTheme(context, themeResId) : 0,
                createContextThemeWrapper);

        mWindow.alwaysReadCloseOnTouchAttr();
        mAlert = AlertController.create(getContext(), this, getWindow());
    }

AlertDialog只是个封装类,实际上具体的业务操作都在AlertController中进行,从其构造参数中可以看出来AlertDialog初始化的时候,也没做啥事。

 public static class Builder {
        private final AlertController.AlertParams P;

在alertdialog内部类builder中实际上也是用了AlertControlller里的参数给AlertDialog赋属性。

  public AlertDialog create() {
            // Context has already been wrapped with the appropriate theme.
            final AlertDialog dialog = new AlertDialog(P.mContext, 0, false);
            P.apply(dialog.mAlert);
            dialog.setCancelable(P.mCancelable);
            if (P.mCancelable) {
                dialog.setCanceledOnTouchOutside(true);
            }
            dialog.setOnCancelListener(P.mOnCancelListener);
            dialog.setOnDismissListener(P.mOnDismissListener);
            if (P.mOnKeyListener != null) {
                dialog.setOnKeyListener(P.mOnKeyListener);
            }
            return dialog;
        }

在alertdialog的create方法里,有句:P.apply(dialog.mAlert);这个就讲AlertController和AlertDialog联系起来了。

   public void apply(AlertController dialog) {
            if (mCustomTitleView != null) {
                dialog.setCustomTitle(mCustomTitleView);
            } else {
                if (mTitle != null) {
                    dialog.setTitle(mTitle);
                }
                if (mIcon != null) {
                    dialog.setIcon(mIcon);
                }
                if (mIconId != 0) {
                    dialog.setIcon(mIconId);
                }
                。。。。。。。。

。。。算了,编不下去了,贴个链接:
AlertDialog源码分析

设计模式-adapter(适配器模式)

一、adapter模式详解
1.适配器模式定义
将一个接口转换成客户希望的另一个接口,适配器模式使接口不兼容的哪些类可以一起工作,其别名为包装器(Wrapper)
2.类适配器
①类适配器定义
类的适配器模式把适配的类的API转换成目标类的API
②UML结构图解释
在这里插入图片描述
左上方,Target是我们的目标角色(通常是个接口,通过接口的方式让adapter去实现它)在这个接口中,我们声明了两个方法:sampleOperational1和sampleOperation2.
而右边的Adaptee并没有sampleOpertion2这个方法,现在如果我们需要这个方法,为了让我们的客户端用到sampleOpertion2这个方法同时又能使用adptee这个类,这里就提供了一个适配器adapter角色,adapter可以把Target和adptee连接起来,非常重要的一点Adapter和Adaptee是继承关系。Adapter会继承Adaptee这个类。
adapter怎么联系这两个类呢?
通过继承Adaptee,实现Target方法实现。
③code详解
对应项目里adapterpattern包下的代码
④总结
类适配器:
类适配器使用对象继承的方式,是静态的定义方式
对于类适配器,适配器可以重定义Adaptee的部分行为
对于类适配器,仅仅引入了一个对象,并不需要额外的引用来间接得到Adaptee
对于类适配器,由于适配器直接继承了Adaptee,使得适配器不能和Adaptee的子类一起工作

3.对象适配器
①与类的适配器模式一样,对象的适配器模式把被适配的类的API转换成目标类的API,与类的适配器模式不同的是,对象的适配器模式不是使用继承关系连接到Adaptee类,而是使用委派关系连接到Adaptee类。(在Adapter中声明Adaptee的实例)
②UML图
在这里插入图片描述

public class Adapter2  implements Target{
    //并没有像类适配器那样继承Apaptee
    private Adaptee mAdaptee;//持有Adaptee引用
    public Adapter2(Adaptee adaptee){//做为参数传进来
        mAdaptee=adaptee;
    }

    @Override
    public void sampleOpertion1() {
        mAdaptee.sampleOpertion1();//在这里引用
    }

    @Override
    public void sampleOpertion2() {

    }
}

第三部分

最近看到一个博主关于面试题的总结,觉得不错,特此将问题一篇文章总结一下,只说一下大概,提供个整体思路。
博主地址

一、Service

1.Service有几种启动模式,这几种启动模式的区别(可以从生命周期、交互这方面来说)
2.service的生命周期(不同的模式有不同的生命周期)
(这里提个醒,可以把Service当做一个没有界面的Activity来搞)
3.场景:
如果一个应用要从网络上下载MP3文件,并在Activity上展示进度条,这个Activity要求是可以转屏的。那么在转屏时Actvitiy会重启,如何保证下载的进度条能正确展示进度呢?
(这里就可以用Service,Activity会重启,Service不会,因为Activity和Service要交互,得用startService了,因为bindService的生命周期和Activity生命周期一致,但交互不是bindService才行的嘛。。,记一个问题,这种情况应该用哪种方式启动Service)

二、用广播来更新UI界面好吗

1.广播一般用两种:
Normal broadcasts无序广播,会异步的发送给所有的Receiver,接收到广播的顺序是不确定的,有可能是同时。
Ordered broadcasts有序广播,广播会先发送给优先级高(android:priority)的Receiver,而且这个Receiver有权决定是继续发送到下一个Receiver或者是直接终止广播。
其实,还有其他的,粘性广播sendStickyBroadcast。(实际上,现在基本上都在用EventBus,粘性广播,可以在@Subscribe里加上sticky=true)
2.实际上Receiver也是有生命周期的,而且很短,在onReceive结束后,其生命周期也就结束了。
如果不是频繁的刷新界面,使用广播来做也是可以的,onReceive是运行在主线程中,所以可以直接更新UI,也正是运行在主线程中,所以在onReceive中,不要做耗时操作!

三、怎么理解Activity的生命周期

1.实际上这方面哈,我个人觉得,知道完整的生命周期方法是最基础的,其次可能会问一下生命周期方法的区别,比如onStart和onResume的区别,onPause和onStop的区别,这些我不细说了,不知道的去百度
2.第二个可能就是在不同的操作下生命周期怎么走了,比如A跳到B,两者的生命周期是怎么个顺序,比如在一个界面通过home键退到后台这种
各种情况下Activity的生命周期

四、如何判断Activity是否在运行

参照AsyncTask的onPostExecute方法:

    @Override
    protected void onPostExecute(Void result) {
        final Activity activity = progressDialog.getOwnerActivity();

        if (activity == null || activity.isDestroyed() || activity.isFinishing()) {
            return;
        }

        if (progressDialog != null && progressDialog.isShowing()) {
            progressDialog.dismiss();
        }
    }

五、自定义View的状态是如何保存的

1.Activity类的onSaveInstanceState默认实现会恢复Activity的状态,默认实现会为布局中的每个View调用相应的 onSaveInstanceState方法,让每个View都能保存自身的信息。这里需要注意:
如果想保存view的状态,那么一定要加上唯一ID,不然是不会调用控件的onSaveIntanceState方法的。
2.关于onSaveInstanceState方法,我想说两点:
它和onPause的调用顺序是没有关联的!谁都可能在前或在后,唯一可以确定的是一定在onStop之前…
onSaveInstanceState中保存的状态信息,我们既可以在onRestoreInstanceState中恢复,也可以在onCreate中恢复(因为Activity销毁重建后就一定要走onCreate嘛)
六、略(这个不作为面试题,我想抽空看看,不懂的暂不说,想知道上面有链接)

七、Java的值传递和引用传递问题

“在Java里面参数传递都是按值传递”
这句话的意思是:按值传递是传递的值的拷贝,按引用传递其实传递的是引用的地址值,所以统称按值传递。

简单来说,基本类型是按值传递的,方法的实参实际上是一个原值的副本。类对象是按对象的引用地址(内存地址)传递地址的值,那么在方法内对这个对象进行修改是会直接反应在原对象上的(或者说这两个引用指向统一内存地址)。
2.注意String类型,String的对象是不可修改的(但引用可改)。

  public static void main(String[] args){
         String testStr=new String("SprrowZ");
         String testStr2="sss";
         change(testStr);
         change(testStr2);
         System.out.println(testStr);
         System.out.println(testStr2);
         testStr2="3333";
         System.out.println(testStr2);
     }
     public static void change(String str){
         str="zzg";
     }

输出结果:

SprrowZ
sss
3333

上面两个验证了String对象不可修改,第三个呢,实际上是testStr2这个对象指向了“3333”的内存空间,原值还是"sss",就相当于又创建了一个字符串"3333",然后将这个字符串的引用赋给testStr2。
如果想改变可以用StringBuilder。
3.说到StringBuilder,那么就得说说:String、StringBuilder、StringBuffer的区别:
在速度这方面:StringBuilder>StringBuffer>String
String是字符串常量,而另外两者均为字符串变量,前者创建后不可再更改,后面是可以改的。
在线程安全这面:StringBuilder是线程不安全的,而StringBuffer是线程安全的(多做事代价就越大,所以速度慢了)

八、Handler机制

说过太多次,这里不说了。(handler机制、内存泄露)
有几个小问题:
1.一个线程可以有几个Looper
只能有一个
2.一个线程可以有几个Handler
可以创建无数个Handler,但是他们使用的消息队列都是同一个,也就是同一个Looper
3.同一个Looper怎么区分不同的Handler
在handler中的enqueueMessage方法中,有个msg.target属性,用于区分不同的Handler

九、两个Activity之间如何传递参数

…intent 、bundle 、putExtra、parcelable、serializable
以及parcelable和serializable两者的区别:
前者使用内存缓存(android特有),后者使用硬盘缓存,反射,序列化和反序列化过程中需要大量I/O操作,会创建大量的临时对象,频繁的GC,慢。

十、如何理解Android中的Context,它有什么用?

在这里插入图片描述
Context是个抽象类,ContextImpl是这个抽象类的具体实现。
1.Context,在翻译为上下文,也可以理解为环境,是提供一些程序的运行环境基础信息。基本上在开发项目的时候,时刻都有接触到。Android程序不像Java程序,随便创建一个类,写个main()方法就能跑,而是要有一个完整的Android工程环境,在这个环境下,有像Activity、Service、BroadcastReceiver等系统组件,而这些组件并不是像一个普通的Java对象new一下就能创建实例的了,而是要有它们各自的上下文环境,也就是Context。可以说Context是维持Android程序中各组件能够正常工作的一个核心功能类。
总Context实例个数 = Service个数 + Activity个数 + 1(Application对应的Context实例)
2.Activity的Context和Application中的Context的区别:
一个是当前Activity的实例,一个是项目的Application的实例,这两者的生命周期是不同的,它们各自的使用场景不同,this.getApplicationContext()取的是这个应用程序的Context,它的生命周期伴随应用程序的存在而存在;而Activity.this取的是当前Activity的Context,它的生命周期则只能存活于当前Activity,这两者的生命周期是不同的。

Context 的创建都是写在在activityThread 里面,通过ams夸进程调用的。
3.在Application或者Service中启动Activity的话,Application(或者Service)需要给Intent设置Intent.FLAG_ACTIVITY_NEW_TASK这个flag。activity前台是在栈里创建的,后台service是不在所属activity的栈里,所以如果service要进行activity跳转,需要给跳转的activity前台新建一个存储activity前台的栈。即以new task方式。

十一、如何优化ListView的性能

1.重用convertView
2.使用Viewholder模式
3.使用异步线程加载图片(可以在listview停止滑动的时候再加载图片)
此外还有一些其他的优化方案
①在adapter的getView方法中尽可能的减少逻辑判断,特别是耗时的判断
②将ListView的scrollingCache和animateCache这两个属性设为false
③尽可能减少List item的layout层次
ViewHolder是如何优化的?
每次getView时,都还要对每个子View中包含的空间进行实例化(findViewById)操作,这是个耗时的操作,所以viewholder的优化主要是减少findViewById这个操作,通过getTag、setTag替换每个item的值来实现的。

十二、如何实现应用内多语言切换

(ORZ,没坐过,这个得去学一学–2019-11-26号,已经学了,回头链接贴过来)
实现方式是直接调用Android开放的接口:Resources.updateConfiguration.

   public static void changeSystemLanguage(Context context, String language) {
        if (context == null || TextUtils.isEmpty(language)) {
            return;
        }

        Resources resources = context.getResources();
        Configuration config = resources.getConfiguration();
        if (Locale.SIMPLIFIED_CHINESE.getLanguage().equals(language)) {
            config.locale = Locale.SIMPLIFIED_CHINESE;
        } else {
            config.locale = new Locale(language);
        }
        resources.updateConfiguration(config, null);
    }

十三、AsyncTask

1.asyncTask的四个方法,哪些是在主线程中,哪些是在子线程中
2.asyncTask导致的内存泄露怎么解决(跟handler解决方式一样,静态内部类,弱引用)
3.asyncTask实现原理,可以同时执行多少个Task(核心5,总大小128,还有个缓冲队列是10个,所以最多是138个,第139个开始的时候,就是崩溃的时候)
4.多个AsyncTask任务是串行还是并行(3.0后改成了串行)
5.这个用也鄙视,不用也鄙视,大家掂量着来吧

十四、SharedPreferences

1.一般用于存储一些配置信息,大小为90K,所以,悠着点来吧。
2.修改SharedPreferences后两种提交方式有何区别?
说实话,这个我还真不咋用。。两种提交方式,我一直以为就一个commit呢。。
实际上有两个,commit和apply,两者区别:
commit这种方式很常用,在比较早的SDK版本中就有了,这种提交修改的方式是同步的,会阻塞调用它的线程,并且这个方法会返回boolean值告知保存是否成功(如果不成功,可以做一些补救措施)。
而apply是异步的提交方式,目前Android Studio也会提示大家使用这种方式。
3.在一个进程中,SharedPreference其实建个单例就够了,一般不会出现并发冲突,如果对提交的结果不太关心的话,用apply,如果需要确保提交成功且有后续操作的话,还是用commit(看来还是commit靠谱,没白用) ,实际上SharedPreferences在多进程方面还是有问题的,这个我得抽时间好好研究研究,暂且几下,加个标识,方便我回头看的时候查到警告,警告,需要研究!!!

十五、有使用过ContentProvider吗?能说哈Android为什么要设计ContentProvider这个组件吗?(需要学习)

实际上,我并没有用过。。。我只用了AIDL,所以这俩底层都是Binder,不过我觉得区别应该还是很大的(四大组件之一啊,没点本事怎么坐到这个位置的??)

应用程序间的数据共享还有另外的一个重要话题,就是数据更新通知机制了。因为数据是在多个应用程序中共享的,当其中一个应用程序改变了这些共享数据的时候,它有责任通知其它应用程序,让它们知道共享数据被修改了,这样它们就可以作相应的处理。
ContentResolver接口的notifyChange函数来通知那些注册了监控特定URI的ContentObserver对象,使得它们可以相应地执行一些处理。ContentObserver可以通过registerContentObserver进行注册。

这上面提到了数据更新时通知其他程序,这点我还真没想到(不然也不会是初级知识整理了,,),用AIDL的时候没有注意到这个问题。。

android:exported属性非常重要。这个属性用于指示该服务是否能够被其他应用程序组件调用或跟它交互。如果设置为true,则能够被调用或交互,否则不能。设置为false时,只有同一个应用程序的组件或带有相同用户ID的应用程序才能启动或绑定该服务。

ContentProvider的方法(query/insert/update/delete等)没有执行完成前都会blocked调用者,所以操作要在子线程中来。

十六、如何处理线程同步的问题(需要学习)

1.Object的wait和notify/notifyAll如何实现线程同步?
在Object.java中,定义了wait(), notify()和notifyAll()等接口。wait()的作用是让当前线程进入等待状态,同时,wait()也会让当前线程释放它所持有的锁。而notify()和notifyAll()的作用,则是唤醒当前对象上的等待线程;notify()是唤醒单个线程,而notifyAll()是唤醒所有的线程。
2.wait和yield(或sleep)的区别?
wait()让线程由“运行状态”进入到“等待(阻塞)状态”,而yield()是让线程由“运行状态”进入到“就绪状态”,从而让其它具有相同优先级的等待线程获取执行权;但是,并不能保证在当前线程调用yield()之后,其它具有相同优先级的线程就一定能获得执行权
wait()会使线程释放它所持有对象的同步锁,而yield()方法不会释放锁
3.“对象的同步锁”以及CountDownLatch暂记,需要学习!!!
4.demo链接
wait()方法表示,放弃当前对资源的占有权,等啊等啊,一直等到有人通知我,我才会运行后面的代码。
notify()方法表示,当前的线程已经放弃对资源的占有, 通知等待的线程来获得对资源的占有权,但是只有一个线程能够从wait状态中恢复,
然后继续运行wait()后面的语句;

notifyAll()方法表示,当前的线程已经放弃对资源的占有, 通知所有的等待线程从wait()方法后的语句开始运行。 读出什么区别没有?
wait,notify,notifyAll方法必须运行在synchronized代码块内
十七、一个案例,解析json的
十八、SQLite(需要学习–SQLiteStatement,勿忘事务)

十九、Activity的启动模式有哪些,都有啥区别?

四种启动模式,区别见下面

二十、四种启动模式的具体区别

Intent有几个标签:
FLAG_ACTIVITY_NEW_TASK
FLAG_ACTIVITY_CLEAR_TOP
FLAG_ACTIVITY_SINGLE_TOP
问题1:
当前应用有两个Activity A和B,B的android:launchMode设置了singleTask模式,A是默认的standard,那么A startActivity启动B,B会新启一个Task吗?如果不会,那么startActivity的Intent加上FLAG_ACTIVITY_NEW_TASK这个参数会不会呢?
答案:
设置了"singleTask"启动模式的Activity,它在启动的时候,会先在系统中查找属性值affinity等于它的属性值taskAffinity的任务存在;如果存在这样的任务,它就会在这个任务中启动,否则就会在新任务中启动。
当Intent对象包含FLAG_ACTIVITY_NEW_TASK标记时,系统在查代时仍然按Activity的taskAffinity属性进行匹配,如果找到一个Task的taskAffinity与之相同,就将目标Activity压入此Task栈中,如果找不到则创建一个新的Task。
所以答案就很明确了,无论加不加,都要看有没有这个task,没有才会创建一个新的Task,有就不加了。
问题2:
设置为singleTask的启动模式,当Activity的实例已经存在时,再启动它,它的哪些回调函数会被执行?我们可以在哪个回调中处理新的Intent协带的参数?(通过startActivity(Intent)启动)
答案:
主要考察知不知道onNewIntent这个回调函数。

二十一、res目录

1.如果提供的图片资源有限,那么图片资源应该尽量放在高密度文件夹下,这样可以节省图片的内存开支。
2.drawable-nodpi文件夹
这个文件夹是一个密度无关的文件夹,放在这里的图片系统就不会对它进行自动缩放,原图片是多大就会实际展示多大。但是要注意一个加载的顺序,drawable-nodpi文件夹是在匹配密度文件夹和更高密度文件夹都找不到的情况下才会去这里查找图片的,因此放在drawable-nodpi文件夹里的图片通常情况下不建议再放到别的文件夹里面。
3.文件夹对应的尺寸(啊,,好吧,一直没注意到。。)
在这里插入图片描述
二十二、图片优化(OOM问题解决思路)
因为在前面博客中提过,这里暂不提了

二十三、当Android遇上JavaScript

1.webview这块
2.Java和JavaScript交互主要分为:
①Java调用WebView加载网页上的JavaScript
在java端可以通过WebView调用JavaScript,基本格式为:

webView.loadUrl(“javascript:methodName(parameterValues)”)

②JavaScript调用本地的Java对象方法
JavaScript调用Java需要在WebView通过JavaScriptInterface添加一个实例到WebView的一个Map对象中:(第一个参数)

public void addJavascriptInterface(Object object, String name)

混合开发,突然想到一点,前端现在既可以做桌面端,又可以做安卓、ios,感觉在未来的空间很很大啊,React Native必须得学!(2019-11-26日,我觉得RN要凉,现在flutter出来了,前景还不确定)
二十四、Fragment
主要讲了一些fragment基础,这里暂不累述
二十五、进程和线程
这块前面的文章也做了比较充分的讲解,暂不累述

二十六、如何解决ScrollView嵌套ListView的滑动冲突

滑动冲突,其实这个问题,我相信很多参加面试的同志都有被问到过,这考验的是我们对于Android事件分发机制的理解。
1.Android的的事件分发,基本都会遵从Activity–>ViewGroup–>View的顺序进行,这其中就有三个至关重要的方法,dispatchTouchEvent,onInterceptTouchEvent,onTouchEvent,相信大家对最后一个都不陌生,而前两个呢,第一个dispathcTouchEvent,从名字上我们可以看出,这是分发点击事件的意思,第二个onInterceptTouchEvent,是否拦截点击事件,这两个方法,都有一个布尔返回值;第一个,如果返回false,表明不分发点击事件,那么点击事件就在本层级的onTouchEvent解决;而返回true,表明分发事件,那么点击事件就会传入到下一级ViewGroup中;ViewGroup里的dispatchTouchEvent跟上面一样,决定是否分发事件,如果返回fasle,表明不分发,在自己的onTouchEvent中进行处理;(onInterceptTouchEvent是ViewGroup特有的,也就是Activity和View都没有onIntterceptTouchEvent这个方法);如果返回true,那么就会进入onInterceptTouchEvent,返回true,则表示拦截事件,那么事件就被ViewGroup的onTouchEvent处理掉;返回false,表示不拦截事件,事件进入下一级,如果下一级还是ViewGroup,那么处理流程跟上面一样;如果是Viwe,实际上在View中dispathTouchEvent是不执行分发逻辑的,因为在View中dispatchTouchEvent返回的是
onTouchEvent(event)的值,所以我们只需要在View中执行onTouchEvent就可以了。
2.趁此机会说一下自定义View
随便说一下哈,不全,也可能不对,我就给自己说说。。
三个方法:onMeasure,onDraw,onLayout
实际上,这三个方法我们在自定义View的时候可能都用不到;
onMeasure,什么时候用呢,当我们自定义View的时候如果想自适应宽高,也就是给自定义View设置WRAP_CONTENT的时候就需要重写此方法:

   @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        int count = getChildCount();

        for (int i = 0; i < count; i++) {
            View child = getChildAt(i);
            measureChild(child, widthMeasureSpec, heightMeasureSpec);
            mHeight += child.getMeasuredHeight();
            if (child.getMeasuredWidth() > mWidth) {
                mWidth = child.getMeasuredWidth();
            }
        }
        int finalWidth = resolveSize(mWidth, widthMeasureSpec);
        int finalHeight = resolveSize(mHeight, heightMeasureSpec);
        setMeasuredDimension(finalWidth, finalHeight);
    }

首先说一下widthMeasureSpec/heightMeasureSpec这个参数:
MeasureSpec封装了父布局传递给子布局的布局要求,每个MeasureSpec代表了一组宽度和高度的要求。而子view的onMeasure()方法拿到这个数值,则会根据这个数值对自身进行测量。
MeasureSpec有三个模式:(就是getMode方法,实际上,从上面的代码你没有看到getMode方法,原因下面说)

UNSPECIFIED
不限定,父View不限制子View的具体的大小,所以子View可以按自己需求设置宽高(前面说的ScrollView就给子View设置了这个模式,ListView就会自己确认自己高度)。
EXACTLY
父View决定子View的确切大小,子View被限定在给定的边界里,忽略本身想要的大小。
AT_MOST
最多的,子View最大可以达到的指定大小(当设置为wrap_content时,模式为AT_MOST, 表示子view的大小最多是多少。)

  @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        int heightMode = MeasureSpec.getMode(heightMeasureSpec);
        int widthSize = MeasureSpec.getSize(widthMeasureSpec);
        int heightSize = MeasureSpec.getSize(heightMeasureSpec);

一般都是上面这种,根据不同的Mode设置不同的size,而我们更上面的代码中,并没有这样写,是因为我们引用了View自带的resolveSize方法,走进去看一下:

   public static int resolveSize(int size, int measureSpec) {
        return resolveSizeAndState(size, measureSpec, 0) & MEASURED_SIZE_MASK;
    }

调用了resolveSizeAndState方法,再进一下:

    public static int resolveSizeAndState(int size, int measureSpec, int childMeasuredState) {
        final int specMode = MeasureSpec.getMode(measureSpec);
        final int specSize = MeasureSpec.getSize(measureSpec);
        final int result;
        switch (specMode) {
            case MeasureSpec.AT_MOST:
                if (specSize < size) {
                    result = specSize | MEASURED_STATE_TOO_SMALL;
                } else {
                    result = size;
                }
                break;
            case MeasureSpec.EXACTLY:
                result = specSize;
                break;
            case MeasureSpec.UNSPECIFIED:
            default:
                result = size;
        }
        return result | (childMeasuredState & MEASURED_STATE_MASK);
    }

就可以看到Mode和Size的具体逻辑了,这也是我们的代码中为什么没有自己写判断,有现成的API啊…
在onMeasure中,我们要做的就是,测量子控件(如果是自定义ViewGroup的话),测量父控件的宽高,
测量子控件也如前面写的的主要是通过getChildCount方法获取一共有几个子控件,然后用一个for循环,通过getChildAt方法获取到每个子控件,通过measureChild方法,测量子控件。然后测量子控件的同时,计算一下父控件的宽高,最后别忘了setMeasuredDimension即可。
实际上,在计算父布局的宽高时,应该加上子View的margin:

 for (int i=0;i<childCount;i++){
            View child=getChildAt(i);
            MarginLayoutParams lp= (MarginLayoutParams) child.getLayoutParams();
            width+=child.getMeasuredWidth()+lp.leftMargin+lp.rightMargin;
            childHeight=child.getMeasuredHeight()+lp.topMargin+lp.bottomMargin;
            if (height<childHeight){
                height=childHeight;
            }

onLayout(就是给子View设置位置,如果是自定义View不是ViewGroup的话,就不用了):

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
     int count=getChildCount();
     int leftMargin=0;
     for (int i=0;i<count;i++){
     View child=getChildAt(i);
     MarginLayoutParams lp= (MarginLayoutParams) child.getLayoutParams();
     //左上右下定义在for循环中
         int left=leftMargin+lp.leftMargin;
         int right=left+child.getMeasuredWidth()+lp.rightMargin;
         int top=lp.topMargin;
         int bottom=lp.topMargin+child.getMeasuredHeight();
        child.layout(left,top,right,bottom);
         leftMargin=right;
     }
    }

如上,计算出每个子View的上下左右的值,然后调用view的layout方法即可。
这里有个点需要注意,getMeasuredHeight(或getMeasuredWidth)和getHeight(或getWidth)的区别:(才发现自己原来学的是错误的。。)
getMeasuredHeight是用来判断布局信息的时候使用到的,onLayout中使用。而getHeight是在onDraw中使用。在onMeasure中测量完子控件后就可以获取到getMeasuredHeight的值了,所以:
我们是在measureChild完之后调用的getMeasuredHeight,如果在此之前,或者在自定义View的onMeasure中,是取不到这个值的!!!!
自定义View有太多东西要学,篇幅有限,暂不赘述。

二十七、ART&Dalvik(需学习)

Android5.0之后舍弃了DaLvik,ART和Dalvik不一样的地方,就是去掉了中间解释字节码的过程
这个需要后续了解,不太清楚。
二十八、
二十九、
内存泄漏,因为文章内容太过基础,此处暂且不提

三十、自定义View

1.invalidate和postInvalidate的区别
invalidate不能再子线程中使用,postInvalidate可以,其底层也是调用的invalidate方法
2.自定义View的流程
测量:onMeasure
布局:onLayout
绘制:onDraw
三十一、问题
这块主要是项目中遇到什么棘手的问题,怎么解决的,我面试过几个人,发现当问这个问题的时候,基本都说没有遇到过,实际上真的没有嘛,很大可能上是因为遇到问题的时候,没有去深入了解问题产生的原因,这点实际上不太好,希望大家遇到问题的时候多加思考吧
三十二、第三方框架
现在第三方框架有很多,特别著名的像Rxjava、Okhttp、EventBus等(2019-11-26,已经学了,用起来简直是得心应手),我觉得还是学一学的好,当然最好看一下源码,参考一下其实现思路。

三十三、MVP&MVVM

MVP
Android中MVC是什么,特点呢?
①Model:针对业务模型建立的数据结构和类(与View无关,只与业务相关)
②View:XML/JAVA或者JS+HTML进行页面的显示。Activity/Frgament也承担了View的功能。
③Controller:Android的控制层通常在Activity、Fragment之中。
④本质就是Controller操作Model层的数据,并且将数据返回给View层展示
MVC的缺点:
①Activity并不是MVC中标准的Controller,既有Controller的职责也有View的职责,导致Activity的代码过于臃肿。
②View层和Model层互相耦合,耦合过重,代码量过大,不易于开发和维护。
MVP
MVP(Model-View-Presenter)
①Model:主要提供数据的存储功能。Presenter需要通过Model存取数据。
②View: 负责处理点击事件和视图展示(Activity、Fragment或者某个View控件)
③Presenter: View和Model之间的桥梁,从Model检索数据后返回给View层。使得M/V之间不再有耦合关系。
特点
Presenter完全将Model和View解耦,主要逻辑处于Presenter中。
Presenter和具体View没有直接关联,通过定义好的接口进行交互。
View变更时,可以保持Presenter不变(符合面向对象编程的特点)
View只应该有简单的Set/Get方法、用户输入、界面展示的内容,此外没有更多内容。
低耦合:Model和View的解耦,决定了该特性。
MVC和MVP的区别:
①MVP中绝对不允许View直接访问Model
②本质是增加了一个接口降低一层耦合度
三十四、常去的android相关的站点
本人呢,主要是google、github、csdn、简书。。

三十四、Activity的启动流程

这篇说的很清楚
launcher
用户点击应用图标,会启动launcher应用程序(launcher继承自Activity),Launcher通过binder机制通知ActivityManagerService来启动activity,调用其内部的startActivity方法。
AMS:PMS的resolveIntent验证要启动的activity是否匹配
如果匹配,通过ApplicationThread发消息给Launcher所在的主线程,暂停当前Activity(Launcher).
暂停完,在该activity还不可见的时候,通过ActivityManagerService,根据要启动的Activity来配置相应的ActivityStack。
接着判断要启动的activity的进程是否存在

  • 如果存在,发送LAUNCH_ACTIVITY给ActivityThread,执行handleLaunchActivity
  • 如果不存在,通过socket向98求创建进程,进程启动后判断Application是否存在,如果不存在,则通过LoadApk.makeApplication创建一个application,并在ActivityThread通过thread.attach方法来关联ApplicationThread
    接着通过ActivityStackSupervisor来获取当前所需要展示的ActivityStack。
    获取到acitivity相关信息后,通过ApplicationThread来发送消息给ActivityThread的handler,来启动activity,即调用handleLaunchActivity。
    handleLaunchActivity:调用了performLaunchActivity,接着调用activity的生命周期。

三十五、什么是Looper

光知道用法可不行,还要理清概念:
Looper是android为线程间异步消息通信提供的一种机制,利用Looper机制可以方便我们实现多线程编程时线程间的相互沟通。当然,如果不用Looper而采用其它的线程间通信方式(像管道,信号量,共享内存,消息队列等)也是一样的。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值