Android知识整理(可用于面试)

一、Android网络相关

1.Http和Https的区别:
①https协议需要到CA申请证书,一般免费证书较少,因而需要一定费用。
②http是超文本传输协议,信息是明文传输,https则是具有安全性的SSL加密传输协议
可以理解为https=http+ssl
③http和https使用的连接方式不一样,使用的端口也不一样,http端口是80https端口是443
④http的连接是无状态的,而https是由SSL+HTTP协议构建的可进行加密传输、身份认证的网络协议,比http协议安全。
HTTPS的缺点:
HTTPS会影响缓存,增加数据开销和功耗;加密范围比较有限,在黑客攻击、拒绝服务攻击、服务器劫持等方面几乎起不到作用。
经济方面,SSL证书要钱,功能越强大的证书费用越高;连接服务器端资源占用高很多。HTTPS协议握手阶段比较费时,对网站的响应速度有负面影响,如非必要,没有理由牺牲用户体验。
2.HTTP请求与响应步骤(说一哈HTTP请求的过程、HTTP的一次会话流程)
①建立连接
先解析DNS,建立socket(通过三次握手建立TCP连接)
②发送请求命令
socket建立好之后,客户端向web服务器发送请求命令(GET/POST等)
③发送请求头、请求体(POST)
客户端先发送与自身相关的信息,再发送空行表示请求头发送完毕,如果是post会继续发送请求体
④回传状态行
服务器应答–第一步:发送协议版本和状态码(200、503、404等)
⑤回传应答头
服务器应答–第二步:先发送自身相关信息、Content-Type(必须)以及被请求的文档,接着发送空行表示请求内容发送完毕
⑥回传应答正文
服务器应答–第三步:根据应答头的Content-Type指定的格式发送应答正文(这也就是为什么响应头里必须得带Content-Type的原因了,不然不知道以什么格式将正文发给你呀)
⑦关闭连接
一次‘会话’完成,如果设置了Connection:keep-alive则TCP连接不关闭,否则关闭。
3.TCP/IP协议模型
在这里插入图片描述
TCP/IP协议模型不是只有TCP/IP协议,它是一个协议族,里面包含多个协议,如上图,TCP、IP协议只是其中的一部分。应用层服务有HTTP、FTP、DNS。
4.TCP和UDP的区别:
①TCP和UDP都是OSI模式中传输层的协议。
TCP面向连接(如拨打电话先拨号建立连接);UDP是无连接的,即发送数据之前不需要建立连接;TCP的逻辑通信信道是全双工的可靠通道,UDP则是不可靠信道。为了提供可靠的服务,TCP采用了超时重传、发送和接收端到端的确认分组等机制。
②TCP提供可靠的服务,通过TCP连接传送的数据,无差错,不丢失,不重复,按序到达;UDP尽最大努力交付,即不保证可靠交付
③TCP面向字节流,将数据看成一连串无结构的字节流;UDP是面向报文的。
④TCP连接只能是点到点的;UDP支持一对一,一对多,多对一和多对多的交互通信。
5.TCP的’三次握手’
在这里插入图片描述
①第一次握手
建立连接。客户端发送连接请求报文段(SYN),将SYN位置为1。并进入SYN_SENT状态,等待服务器确认。(SYN:同步序列编号–Synchronize Sequence Numbers)
②第二次握手
服务器收到SYN报文段,确认客户端的SYN,ACK+1,同时自己也要发送SYN请求信息,将SYN位置为1,Sequence Number(Seq)为y。服务器端将上述信息放到一个报文段(即SYN+ACK报文段)中,一并发送给客户端,此时服务器进入SYN_RECV状态。
③第三次握手
客户端收到服务器的SYN+ACK报文段,然后将ACK设置为y+1,向服务器发送ACK报文段,这个报文段发送完毕以后,客户端和服务器端都进入ESTABLISHED状态,完成TCP三次握手。
6.为什么TCP需要’三次握手’?
总之一句话:为了防止已失效的连接请求报文段突然又传送到了服务端,因而产生错误。(节省服务器端资源)
具体例子:客户端发送的第一个连接请求报文段没有丢失,只是因为网络较差长时间滞留了以至于连接释放以后的某个时间才到达服务器端。这本来是个已经失效的报文段,但server收到这个失效的报文段后,却误以为是客服端又再次发出了一个新的连接请求,然后服务器就会发出确认报文段,同意建立连接。如果不用’三次握手’,只要server发出确认,新的连接就建立。但由于客户端并没有发出建立连接的请求,就不会理睬服务器,也不会发送数据,但服务器却以为新的连接已经建立,并一直等待客户端发送出具,这样,服务器的很多资源就白白浪费了。而采用了’三次握手’,就可以防止上述现象发生。
7.TCP四次挥手

二、代码混淆

一篇不错的混淆讲解
1.需要在项目gradle中开启混淆

   shrinkResources true
   minifyEnabled true

2.混淆规则

-keep class cn.hadcn.test.*
-keep class cn.hadcn.test.**

一颗星表示只保持该包下的类名,而子包下的类名还会被混淆;
两颗星表示把本包和所含的子包下的类名都保持;
只用上面两个的话,会发现虽然类名没有被混淆,但里面的方法和变量命名变了;如果想保持里面的内容不被混淆,就需要用:

-keep class com.example.bean.** { *; }

哪些情况下不应该被混淆,应该keep住:
1.自定义控件
2.枚举(enum)
3.第三方库的类
4.运用了反射的类也不进行混淆
5.使用了 Gson 之类的工具要使 JavaBean 类即实体类不被混淆
6.在引用第三方库的时候,一般会标明库的混淆规则的,建议在使用的时候就把混淆规则添加上去,免得到最后才去找
7.有用到 WebView 的 JS 调用也需要保证写的接口方法不混淆
8.Parcelable 的子类和 Creator 静态成员变量不混淆,否则会产生 Android.os.BadParcelableException 异常

三、图片相关

1.图片加载三级缓存
内存缓存:优先加载,速度最快
本地缓存:次优先加载,速度快
网络缓存:最后加载,速度慢,耗费流量
①为什么进行三级缓存?
减少不必要的流量,增快加载速度。
②三级缓存原理
首次加载通过网络加载,获取图片,然后保存到内存和SD卡中
之后运行app时,有限访问内存中的图片缓存
如果内存没有,则加载本地SD卡中的图片,之后再存到内存中。
其中内存缓存采用的是LruCache,其内部通过LinkedHashMap来持有外界缓存对象的强引用
③LRU原理
首先说一下LinkedHashMap和HashMap的区别:
LinkdedHashMap保证了线程安全,保证了元素迭代的顺序,LinkedHashMap里的元素是有序的。
LruCache维护了一个LinkedHashMap,该LinkedHashMap是以访问顺序排序的。当调用put方法时,就会在集合中添加元素,并调用trimToSize()判断缓存是否已满,如果满了就调用LinkedHashMap的迭代器删除队首的元素,即近期最少访问的元素;当调用get方法时,则将该对象移到链表的尾端

    /**
     * 用户加载网络图片
     *
     * @param view
     * @param url
     */
    public void displayImage(ImageView view, String url) {
        Bitmap bitmap;
        bitmap = getBitmapFromCache(url);
        Log.i(TAG, "displayImage: bitmap:"+(bitmap==null));
        if (bitmap != null) {
            Log.i(TAG, "displayImage: getBitmap From Cache");
            view.setImageBitmap(bitmap);
            return;
        }
        bitmap=getBitmapFromLocal(url);
        if (bitmap!=null){
            Log.i(TAG, "displayImage: getBitmap From Local");
            view.setImageBitmap(bitmap);
            //从本地取得也要放到缓存中
            putBitmapToCache(url,bitmap);
        return ;
        }
        getBitmapFromNet(view,url);
    }

处理逻辑如上,如果需要具体代码的话,可以私信我,有点长就不贴了。
这里需要说一点:LruCache使用图片的Url作为key,存到本地的时候,文件名是将url通过MD5加密后的字符串,所以也是通过url取出图片。
2.图片压缩(防止OOM)
①计算一张图片的大小
像素所占字节数,像素字节数android一共有四种:
ALPHA_8 占1个字节
ARGB_4444占2个字节
ARGB_8888占4个字节
RGB_565占2个字节
压缩原理(采样率压缩[附带修改模式RGB_565])
压缩通过BitmapFactory的Options进行压缩,首先通过options里的inJustDecodeBounds参数为true,只加载图片的宽高,不加载内容,跟压缩尺寸计算出压缩比例inSampleSize,然后decode图片即可,还有一点,inPreferredConfig改变颜色模式为RGB_565,默认的的是ARGB_8888,这个质量最高,但一个像素点占用4个字节,ARGB_4444虽然和RGB_565都占用2个字节,但其画质太垃圾了。
还有其他的一些压缩方法:质量压缩(图片大小不变,对PNG无效)、缩放法压缩(matrix)、createScaledBitmap,这里不一一例举了(因为都没采样率好,hiahia…)

四、Activity相关

1.Activity的启动流程
Activity启动流程详解
在这里插入图片描述
startActivity最终都会调用startActivityForResult,通过ActivityManagerProxy调用system_server进程中ActivityManagerServicestartActvity方法,如果需要启动的Activity所在进程未启动,则调用Zygote孵化应用进程,进程创建后会调用应用的ActivityThread的main方法,main方法调用attach方法将应用进程绑定到ActivityManagerService(保存应用的ApplicationThread的代理对象)并开启loop循环接收消息。ActivityManagerService通过ApplicationThread的代理发送Message通知启动Activity,ActivityThread内部Handler处理handleLaunchActivity,依次调用performLaunchActivityhandleResumeActivity(即activity的onCreate,onStart,onResume)
2.四种启动模式
①标准模式:standard mode
系统默认的模式,每次启动一个activity都会重新创建一个新的实例,不管这实例是否存在。
②栈顶复用模式:singleTop
如果新的activity已经位于任务栈的栈顶,那么activity就不会重新创建,而是复用这个activity,同时回调它的onNewIntent方法。
③栈内复用模式:singleTask
只要activity在一个任务栈中存在,那么多次启动这个activity都不会创建实例,会回调onNewIntent方法。默认具有clearTop的效果,例如栈内Activity为A、B、C、D,这时我们启动C,那么任务栈就会清除A、B,调用栈内的C。
④单实例模式:singleIntance
singleTask模式的加强版,除了singleTask的一切属性外,它只能运行与一个单独的进程中。
3.Activity生命周期相关
①正常情况下的生命周期
onCreate->onStart->onResume->onPause->onStop->onDestroy
②异常情况下的生命周期:
------- 1>资源相关的系统配置发生改变(系统语言改变、屏幕旋转等)导致Activity被杀死并重新创建
在这里插入图片描述
注意:
onSaveInstanceState方法和onPause没有既定的时序关系,可能在其之前也可能在其之后,但一定在onStop之前
onRestoreInstanceState的调用时机在onStart之后

--------2> 资源不足导致优先级低的Activity被杀死
优先级排序:
前台Activity>可见但非前台Activity>后台Activity
当系统内存不足的时候,会按照上面的优先级杀死目标Activity所在的进程,并在后续通过onSaveInstanceState和onRestoreInstanceState来存储和恢复数据。
③onStart()和onResume(),onPause()和onStop()的区别?
onStart和onStop是从Activity是从Activity是否可见这个角度来回调的,onStart的时候,activity已经可见了,但是不能与用户交互了,onStop的时候activity已经不可见了。
而onResume和onPause是从Activity是否位于前台这个角度来回调的。onResume的时候已经可以跟用户交互了,onPause的时候activity还可见,但是已经不能交互了。
4.几种操作下activity的生命周期回调
①A跳转到B
A(onPause)—>B(onCreate)—>B(onStart)—>B(onResume)–>A(onStop)
②再跳转回A
B(onPause)–>A(onRestart)---->A(onResume)–>B(onStop)–>B(onDestroy)
③A调用系统键退到后台
A(onPause)–>A(onStop)
④再从后台唤起A…
A(onRestart)–>A(onResume)
见图:
在这里插入图片描述
5.Activity状态保存(防止activity重新创建)
①限定屏幕方向:
在清单文件中给activity添加标签:

android:screenOrientation="portrait"

②自己处理变更
在清单文件中添加标签:

android:configChanges="keyboardHidden|orientation|screenSize"

③系统处理变更
主要用到三个方法:
onSaveInstanceState–>保存被销毁前的状态
onRestoreInstantceState(或者onCreate)–>被销毁的状态保存在bundle中,从中恢复。

五、Service相关

service也是运行在UI线程中的,不能进行耗时操作,除非用thread或者asynctask等;
service需要在清单文件中注册;
对于同一个app来说默认情况下service和activity是运行在同一个线程中的,即UI线程。
1.service分类
service一般分为两类:本地service和远程service。
本地service,顾名思义,是当前应用在同一个进程中的service,彼此之间有共同的内存区域。
远程service,主要牵扯到不同进程之间的service访问—>AIDL.
2.service的两种启动模式
①startService
通过startService启动的Service的生命周期:
onCreate–onStartCommand,多次调用startService并不会重复创建,就会只走onStartCommand方法,而不会走onCreate。
特点:
1>一旦service开启就跟开启者没关系了。
2>开启者退出之后,服务还是可以在后台长期运行的,前提是没有调用stopServcie或者service内部的stopSelf。
3>开启者不能调用服务里面的方法。
②bindService
通过bindService启动的Service的生命周期:
onCreate–>onBind–>onUnbind–>onDestroy
特点:
1>bindService绑定的服务,调用者挂了,服务也会跟着挂掉。
2>绑定者可以调用服务里的方法,实现方法:
在Service中定义一个MyBinder继承Binder,在onBind中返回MyBinder;在调用者(比如activity)里的ServiceConnection(bindService时传入)里的onServiceConnected里有个IBinder对象,强转成我们的MyBinder即可。

  //与Service进行绑定
    private LocalService.MyBinder myBinder;
    //IBinder
    //ServiceConnection用于绑定客户端和service
    //进度监控
    private ServiceConnection connection = new ServiceConnection() {
        //服务绑定成功后调用,毕竟onBind方法只执行一次,绑定成功/失败调用下面的两个方法
        @Override
        public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
            //实例化service
            myBinder = (LocalService.MyBinder) iBinder;
            //开始调用Service中的方法
            myBinder.startDownload();
            Log.i(TAG, "onServiceConnected: -----------bindService---------");
        }

        @Override
        public void onServiceDisconnected(ComponentName componentName) {
            Log.i(TAG, "onServiceDisconnected: --------unbindService--------");
        }
    };

3.service怎么和Activity通信
方法一:
就是上面说的bindService的,添加一个Binder的内部类,在onBind时返回
在调用者的ServiceConnection,onServiceConnected时调用逻辑方法,绑定服务。
方法二:
广播
4.如何提高service的优先级
方法有很多,这里只举三个:
①在清单文件中对于intent-filter可以通过android:priority="1000"这个属性设置最高优先级,1000是最高值,数字越小则优先级越低,同时适用于广播。
②onStartCommand里面调用startForeground()方法把Service提升为前台进程级别,不要忘了再onDestroy里面调用stopForeground()方法。
③onStartCommand方法里,手动返回
START_STICKY
.
④在onDestroy方法里发广播重启service。
5.Service的onStartCommand有几种返回值,分别代表什么意思?
有四种返回值
①START_STICKY:如果service进程被kill掉,保留service的状态为开始状态,但不保留传送进来的intentintent对象,随后系统会尝试重新创建service,就会调用onStartCommand方法,intent为null。
②START_NOT_STICKY:如果在执行完onStartCommand后,服务被异常kill掉,系统不会自动重启该服务。
③START_REDELIVER_INTENT:重传intent。如果执行完onStartCommand后服务被异常kill掉,系统会自动重启该服务,并将intent的值传入。
④START_STICKY_COMPATIBILITY:START_STICKY的兼容版本,但不保证服务被kill掉一定能重启。
6.Service里面可以弹Toast吗?
把service看成activity就行。activity能做的事,它也能做。
7.bindService和startService的混合使用
如果一个service被启动又被绑定,那么会一直在后台运行(startService开启service可以长时间运行),只会调用一次onCreate方法。startService调用多少次,onStartCommand方法就会调用多少次。不管startService与bindService调用顺序如何,都要stopService和unbindService都用才能终止service。
startService和bindService两种模式是完全独立的,我们可以绑定一个已经通过startService方法启动的服务。这两个模式均用的场景:
比如一个后台播放音乐服务可以通过startService来播放音乐,可能用户在播放过程中需要执行一些操作,比如获取歌曲的一些信息,此时activity可以通过调用bindService方法与Service建立连接。
8.实际使用案例
下载文件,如何通知绑定者下载进度呢?
绑定者定义一个内部类,实现下载接口,将此接口传给service,在service里调用接口的方法;然后在绑定者的内部类中,实现方法里的逻辑即可。
9.service和IntentService的区别
①IntentService是继承并处理异步请求的一个类,在IntentService内有一个工作线程来处理耗时操作,启动IntentService的方式和启动传统的Service一样,同时,当任务执行完后,IntentService会自动停止,而不需要我们手动去控制或stopSelf()。另外,可以启动IntentService多次,而每一个耗时操作会以工作队列的方式在IntentService的onHandleIntent回调方法中执行,并且,每次只会执行一个工作线程,执行完第一个再执行第二个,以此类推。当service结束,则剩下的工作线程也终止执行了。
一般用startService来启动IntentService,用bindService的话
②IntentService源码解析

   @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);
    }

在oncreate方法中创建了一个HanderThread对象(实际上就是一个Thread),这个类中:

  @Override
    public void run() {
        mTid = Process.myTid();
        Looper.prepare();
        synchronized (this) {
            mLooper = Looper.myLooper();
            notifyAll();
        }
        Process.setThreadPriority(mPriority);
        onLooperPrepared();
        Looper.loop();
        mTid = -1;
    }

给我们实例化了Looper对象,调用了Looper的的prepare和loop方法,使得我们可以在IntentService中使用Handler;
ServiceHanlder里:

    private final class ServiceHandler extends Handler {
        public ServiceHandler(Looper looper) {
            super(looper);
        }

        @Override
        public void handleMessage(Message msg) {
            onHandleIntent((Intent)msg.obj);
            stopSelf(msg.arg1);
        }
    }

可以看出handleMessage里有个onHandleIntent方法,这也是我们继承IntentService必须要重写的方法。

    @Override
    public void onStart(@Nullable Intent intent, int startId) {
        Message msg = mServiceHandler.obtainMessage();
        msg.arg1 = startId;
        msg.obj = intent;
        mServiceHandler.sendMessage(msg);
    }

    /**
     * You should not override this method for your IntentService. Instead,
     * override {@link #onHandleIntent}, which the system calls when the IntentService
     * receives a start request.
     * @see android.app.Service#onStartCommand
     */
    @Override
    public int onStartCommand(@Nullable Intent intent, int flags, int startId) {
        onStart(intent, startId);
        return mRedelivery ? START_REDELIVER_INTENT : START_NOT_STICKY;
    }

在onstart方法中,我们看到mServiceHandler.sendMessage(msg),这就是我们平常使用的handler在子线程中的通信,因为ThreadHandler本质也是个Thread,从子线程中sendMessage,主线程中处理,这也就是为啥可以在intentService中进行耗时操作的原因。

六、IPC之AIDL

android IPC(跨进程通信)有三种:AIDL、ContentProvider、Socket
1.Davik进程、linux进程、线程之间的区别?
linux进程,它有独立的内核堆栈和独立的存储空间,它是操作系统中资源分配和调度的最小单位。
Davik进程:
Davik虚拟机运行于Linux操作系统之上。
Davik进程就是Linux操作系统中的一个进程,属于linux进程
每个android应用程序都有一个Davik虚拟机实例,这样做的好处是一个app崩了,不影响其他的…
线程:线程是进程的一个实体,是CPU调度和分派的基本单位,是比进程更小的能独立运行的基本单位;
线程自己基本不拥有系统资源,在运行时,只需要必不可少的一些资源。
线程与同属一个进程的其他的线程共享进程所拥有的所有资源。
三者之间的区别:
Davik进程就是Linux操作系统的一个进程。
线程就是进程的一个实体,线程是进程的最小单位,一个进程中可以有多个线程。


AIDL之使用
因为AIDL是不同APP之间的信息交互,所以我们这里分为客户端和服务器端讲解:
而我们交互的逻辑为:客户端输入两个数字,调用服务器端的add方法,计算完成后返回给客户端。
客户端:
1.新建aidl文件,添加方法。然后sync工程,生成接口的java文件(Binder类)
2.通过intent的隐式调用,传入服务端包名和接口的完整路径来绑定服务。
3.在ServiceConnection中返回一个IBinder对象,声明一个全局的aidl的java文件,然后实现接口java文件Stub代理类中的asInterface方法。
4.在onServiceDisConnected里解绑aidl(置为null),在onDestroy中解绑服务。
服务端:
1.新建aidl文件,添加方法,然后sync工程,***生成接口的java文件(Binder类)***,aidl文件的路径客户端和服务端一定要保持一致。直接拷过来即可(方便快捷!)
2.新建Service,因为AIDL是借助service通过binder实现的,在service新建binder实现aidl里的stub代理类。
3.在service里的onbind中返回binder
4.清单文件中注册一下service。


如果要传递的数据类型为自定义实体类:
首先要新建实体类,再新建aidl文件;有几点要注意!
1.实体类的包名路径要和aidl文件夹中对应的aidl文件路径名一致!(所以,在新建aidl文件的时候,尽量和项目路径一致,会省去不少麻烦)
2.aidl传递实体类,必须要实现Parcelable接口!
3.实体类的aidl文件要跟实体类名字一致,然后在此aidl文件中:
在这里插入图片描述
4.新建aidl文件,或者在上面我们用于计算的aidl里添加方法也可以。

List<PersonBean> add( in PersonBean  person);

这里需要注意,我们用List而不是ArrayList,实际上接口的地方还是用ArrayList接收的。同样的是Map。
这里不要忘了in!!
AIDL中的定向 tag 表示了在跨进程通信中数据的流向,其中 in 表示数据只能由客户端流向服务端, out 表示数据只能由服务端流向客户端,而 inout 则表示数据可在服务端与客户端之间双向流通。
从上面的in也可以看出,是客户端给服务器端发送的实体类信息。


AIDL的工作流程:
AIDL的跨进程通信,是通过Binder实现的,实际上ContentProvider也是通过Binder来实现的。
在我们定义好aidl文件后,sdk会自动帮我们生成的Binder类,也就是我们上面说的java文件。我们在使用AIDL时服务器端service里要用到的Stub类,就是sdk帮我们生成的binder类里的内部类。
这个binder类继承于android.os.IInterface接口,所有在Binder中传输的接口都必须实现IInterface接口。该文件中有个静态内部类Stub,其中有两个方法:
asInterface:该方法将服务端的Binder对象转换为客户端所需要的接口对象;(所以在客户端的ServiceConnect里会用到Stub().asInterface(service),这里的service就是服务器传过来的Binder对象)
onTransact:这个方法运行在服务端的Binder线程中,当客户端发起远程请求后,在底层封装后会交给此方法处理。通过code来区分客户端请求的方法。如果该方法返回false,客户端的请求就会失败,可以用此做权限控制。
其实,从上面的分析中可以看出,AIDL并不是必须的文件,我们可以手写出来这个binder类…

第二部分

一、 Android动画

一篇很好的讲解动画的博客
android动画一共两种:
View Animation、Property Animation,其中View Animation又分为FrameAnimation、TweenAnimation。接下来我们分别总结哈:
1.FrameAnimation(帧动画)
此动画实际上就是将一系列的图片一张张展示出来,GIF就是帧动画。两种实现方式,一是XML,二是代码。(最好xml–>方便管理)

<?xml version="1.0" encoding="utf-8"?>
<animation-list xmlns:android="http://schemas.android.com/apk/res/android"
    android:oneshot=["true" | "false"] >
    <item
        android:drawable="@[package:]drawable/drawable_resource_name"
        android:duration="integer" />
</animation-list>

主要就是在animation-list标签下,插入item,animation-list有个属性:android:oneshot=["true" | "false"] ,为true的时候播放一次,到item末尾结束;为false的时候会一直循环播放。item里面的duration是该item的执行时间,单位为ms。
使用方法:

iv.setBackgroundResource(R.drawable.frame_test);
AnimationDrawable animationDrawable=(AnimationDrawable)iv.getBackground();
animationDrawable.start();

注意OOM!
2.Tween Animation(补间动画)
四种:平移、旋转、缩放、透明度

<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
    //插值器
    android:interpolator="@[package:]anim/interpolator_resource"
    //动画结束后View是否停留在结束的位置
    android:fillAfter=["true" | "false"] 
    //重复的模式,默认为restart,即重头开始重新运行,reverse即从结束开始向前重新运行
   android:repeatMode="restart/reverse"
    //子元素是否共用此插值器
    android:shareInterpolator=["true" | "false"] >
    <alpha
        //开始透明度0.0(全透明)到1.0(完全不透明)
        android:fromAlpha="float"
        android:toAlpha="float"
        //动画执行时间
        android:duration="integer" />
    <scale 
        //其实X缩放大于0,1的时候表示不缩放,小于1缩小,大于1放大
        android:fromXScale="float"
        android:toXScale="float"
        android:fromYScale="float"
        android:toYScale="float"
       //缩放中心,也可以穿fraction值
        android:pivotX="float"
        android:pivotY="float"
        android:duration="integer" />
    <translate
        //表示 x 的起始值
        android:fromXDelta="float/fraction"
        android:toXDelta="float"
        android:fromYDelta="float"
        android:toYDelta="float" 
        android:duration="integer" />
    <rotate
        //起始的旋转角度
        android:fromDegrees="float"
        android:toDegrees="float"
        android:pivotX="float"
        android:pivotY="float"
        android:duration="integer"  />
    <set>
        ...
    </set>
</set>

需要注意的是平移的时候其实位置接受百分比数值:从-100到100的值,以“%”结尾,表示百分比相对于自身;从-100到100的值,以“%p”结尾,表示百分比相对于父容器。

例如给我们的Activity会加一些从左边进右边出的动画,fromXDelta值-100%p并将toXDelta为0%p,那么我们看到的效果就是从左边进入了。

插值器
在动画插值器起的作用主要是改变动画的执行速率,一般情况我们不需要自己实现插值器,因为在Android中已经给我们提供了9种插值器,应该够我们使用了,我们使用插值器后会让动画执行的效果更酷炫,当然想自定义插值器也不难,可以查看已经实现插值器源码做参考。

accelerate_decelerate_interpolator:先加速后减速
accelerate_interpolator:一直加速
anticipate_interpolator: 开始的时候先向后甩一点然后向前,就好比人扔东西会先向后甩一下,这样才能抛的远
anticipate_overshoot_interpolator:和上一种插值器相似,先向后抛然后向前,到达终点后会有回弹一下效果,好比我们将球抛到墙上,然后反弹回来
bounce_interpolator:动画结束的时候弹起,类似皮球落地,会弹几下才停止
cycle_interpolator:动画循环播放特定的次数回到原点,速率改变沿着正弦曲线
decelerate_interpolator:减速的插值器,刚开始速度快,然后越来越慢直到停止
linear_interpolator:线性的插值器。从开始到结束匀速运动
overshoot_interpolator:向前超过设定值一点然后返回
其实,记住两三个就好啦,我是只用过三种。。
使用方式,补间动画都一样啦~

 Animation animation= AnimationUtils.loadAnimation(this,R.anim.anim_in_left_top);
  imageView.startAnimation(animation);

可以给动画加上监听:setAnimationListener
在java中使用动画,操作如下:

                AlphaAnimation myAlpha=new AlphaAnimation(0.1f,1.0f);
                myAlpha.setDuration(3000);
                myAlpha.setRepeatCount(10);
                myAlpha.setRepeatMode(Animation.RESTART);
                iv.startAnimation(myAlpha);

动画的混合使用:
①.在xml文件set标签下添加动画
②.AnimationSet,可以顺次加载动画、同时播放动画;
3.Propterty Animation
View动画有个问题,虽然View移动了,但点击事件还是在原来的位置。属性动画则可以规避这个问题。

<set
//执行的顺序together同时执行,sequentially连续执行
  android:ordering=["together" | "sequentially"]>

    <objectAnimator
      //属性动画名字,例如alpha" 或者"backgroundColor"等
        android:propertyName="string"
        android:duration="int"
        //属性的开始值
        android:valueFrom="float | int | color"
        android:valueTo="float | int | color"
        //该动画开始的延迟时间
        android:startOffset="int"
        //动画重复次数,-1表示一直循环,1表示循环一次也就是播放两次,默认0,播放一次
        android:repeatCount="int"
        //repeatCount设置-1时才会有效果
        android:repeatMode=["repeat" | "reverse"]
        //如果值是颜色,则不用使用此属性
        android:valueType=["intType" | "floatType"]/>

    <animator
        android:duration="int"
        android:valueFrom="float | int | color"
        android:valueTo="float | int | color"
        android:startOffset="int"
        android:repeatCount="int"
        android:repeatMode=["repeat" | "reverse"]
        android:valueType=["intType" | "floatType"]/>

    <set>
        ...
    </set>
</set>

ex:

<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
    android:ordering="sequentially">
    <objectAnimator
        android:duration="500"
        android:propertyName="rotation"
        android:valueFrom="0"
        android:valueTo="180" />
    <objectAnimator
        android:duration="500"
        android:propertyName="alpha"
        android:valueFrom="1.0f"
        android:valueTo="0.5f" />
</set>
AnimatorSet set = (AnimatorSet) AnimatorInflater.loadAnimator(this,
                R.animator.property_animator);
set.setTarget(imageView);
set.start();

实际上,就我个人而言,我更喜欢用java代码写,感觉更方便些:

  ObjectAnimator ob=ObjectAnimator.ofInt(tv,"BackgroundColor", 0xffff00ff, 0xffffff00, 0xffff00ff,0xff0000ff);
    ob.setRepeatCount(ValueAnimator.INFINITE);
    ob.setRepeatMode(ValueAnimator.REVERSE);
    ob.setDuration(8000);
    ob.start();

对应View动画的AnimationSet,属性动画也有一个动画集合AnimatorSet;
其实还有个数值动画,暂且不提。(值得一提的是ObjectAnimator的爹就是ValueAnimator。。)

二、Android线程通信

线程间通信有四种方式:
Handler、runonUiThread、View.post、AsyncTask
1.Handler详解
大佬对Handler的详解
handler是不能直接在子线程中声明(使用在子线程中使用)的。如果想声明的话,需要在调用之前加Looper.prepare()方法,调用后加Looper.loop()。需要注意的是,Looper.loop()是无限循环的,loop之后的代码是不执行的,调用实例如下:

Looper.prepare();
mHandler = new Handler(){
    @Override
  public void handleMessage(Message msg) {
            Log.d(TAG," mHandler is coming");
  handler_main.sendEmptyMessage(1);
  }
};
mHandler.sendEmptyMessage(1);
Looper.loop();

接下来开始讲Handler的流程:

    public Handler(Callback callback, boolean async) {
        if (FIND_POTENTIAL_LEAKS) {
            final Class<? extends Handler> klass = getClass();
            if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) &&
                    (klass.getModifiers() & Modifier.STATIC) == 0) {
                Log.w(TAG, "The following Handler class should be static or leaks might occur: " +
                    klass.getCanonicalName());
            }
        }

        mLooper = Looper.myLooper();
        if (mLooper == null) {
            throw new RuntimeException(
                "Can't create handler inside thread that has not called Looper.prepare()");
        }
        mQueue = mLooper.mQueue;
        mCallback = callback;
        mAsynchronous = async;
    }

在handler的构造方法里,实例化Looper对象,也就是:mLooper = Looper.myLooper();进入到myLooper方法里:

 public static @Nullable Looper myLooper() {
        return sThreadLocal.get();
    }

可以看到这个Looper对象是从ThreadLocal中获取的,ThreadLocal,用来存储以当前线程为键的一对键值对(可以理解为HashMap),它的键默认是当前的线程对象,通过set()方法设置对应的值,可以是任意对象,在这里是指一个Looper对象。
在Looper的构造方法里:

private Looper(boolean quitAllowed) {
    mQueue = new MessageQueue(quitAllowed);
    mThread = Thread.currentThread();
}

可以看出looper创建了一个MessageQueue;也就是说在handler初始化的时候,创建了一个looper对象和其对应的消息队列,也就是MessageQueue,一个looper有且只有一个唯一的消息队列。
在ActivityThread类中的main方法,有调用Looper.loop();在activity的启动流程里是需要走ActivityThread里的main方法的。
Looper.loop()方法类似于Socket服务端的ServerSocket.accept()方法,一直阻塞,直到有消息为止。

    public static void loop() {
        final Looper me = myLooper();
        if (me == null) {
            throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
        }
        final MessageQueue queue = me.mQueue;

        // Make sure the identity of this thread is that of the local process,
        // and keep track of what that identity token actually is.
        Binder.clearCallingIdentity();
        final long ident = Binder.clearCallingIdentity();

        for (;;) {
            Message msg = queue.next(); // might block
            if (msg == null) {
                // No message indicates that the message queue is quitting.
                return;
            }

            // This must be in a local variable, in case a UI event sets the logger
            final Printer logging = me.mLogging;
            if (logging != null) {
                logging.println(">>>>> Dispatching to " + msg.target + " " +
                        msg.callback + ": " + msg.what);
            }

            final long slowDispatchThresholdMs = me.mSlowDispatchThresholdMs;

            final long traceTag = me.mTraceTag;
            if (traceTag != 0 && Trace.isTagEnabled(traceTag)) {
                Trace.traceBegin(traceTag, msg.target.getTraceName(msg));
            }
            final long start = (slowDispatchThresholdMs == 0) ? 0 : SystemClock.uptimeMillis();
            final long end;
            try {
                msg.target.dispatchMessage(msg);
                end = (slowDispatchThresholdMs == 0) ? 0 : SystemClock.uptimeMillis();
            } finally {
                if (traceTag != 0) {
                    Trace.traceEnd(traceTag);
                }
            }
            if (slowDispatchThresholdMs > 0) {
                final long time = end - start;
                if (time > slowDispatchThresholdMs) {
                    Slog.w(TAG, "Dispatch took " + time + "ms on "
                            + Thread.currentThread().getName() + ", h=" +
                            msg.target + " cb=" + msg.callback + " msg=" + msg.what);
                }
            }

            if (logging != null) {
                logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
            }

            // Make sure that during the course of dispatching the
            // identity of the thread wasn't corrupted.
            final long newIdent = Binder.clearCallingIdentity();
            if (ident != newIdent) {
                Log.wtf(TAG, "Thread identity changed from 0x"
                        + Long.toHexString(ident) + " to 0x"
                        + Long.toHexString(newIdent) + " while dispatching to "
                        + msg.target.getClass().getName() + " "
                        + msg.callback + " what=" + msg.what);
            }

            msg.recycleUnchecked();
        }
    }

 for (;;) {
            Message msg = queue.next(); // might block
            if (msg == null) {
                // No message indicates that the message queue is quitting.
                return;
            }

从这句可以看出来,为啥子说loop是阻塞式的了。next是有消息的时候,就将消息从消息队列中取出,没有消息的时候就return null;
在Message类中,我们可以看到:

   public static Message obtain() {
        synchronized (sPoolSync) {
            if (sPool != null) {
                Message m = sPool;
                sPool = m.next;
                m.next = null;
                m.flags = 0; // clear in-use flag
                sPoolSize--;
                return m;
            }
        }
        return new Message();
    }

可以看出,obtain方法里,从消息池中取消息,没有消息的时候再去new。所以尽量用handler.obtainMessage()方法。(唉,不愧是大佬的博客,都是知识点啊)。
前面我们说子线程中如果用handler则必须在调用前写上Looper.loop()方法,调用后开启Looper.loop()方法,而主线程中不用,是因为,在Activity启动的时候,在主线程中就已经启动了这两个方法(ActivityThread类中的main方法)。而子线程中并没有。
最后说一下handler.sendMessage方法,当然还有其他的sendMessageDelay啊等,最终都会调用sendMessageAtTime方法:

  public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
        MessageQueue queue = mQueue;
        if (queue == null) {
            RuntimeException e = new RuntimeException(
                    this + " sendMessageAtTime() called with no mQueue");
            Log.w("Looper", e.getMessage(), e);
            return false;
        }
        return enqueueMessage(queue, msg, uptimeMillis);
    }

在这里,调用enqueueMessage方法,此方法,就是将message添加到消息队列中,而消息是如何从消息队列中取出来的,就在我们之前说的Looper.loop()方法里;
消息的最后处理,是通过Callback返回给调用方,然后重写handleMessage方法即可。
子线程中可以创建Handler,前提是要调用Looper.prepare(),之后实例化handler后调用Looper.loop()方法。(主线程发消息,子线程处理handler)
Handler负责发送消息,Looper负责处理消息(将消息放到MessageQueue里),最后Looper将处理结果发给Handler,调用Handler的handleMessage方法。

2.AsyncTask
AsyncTask=Handler+线程池
①五个方法:
onPreExecute()、doInBackground()、onProgressUpdate()、onPostExecute()、
onCancelled()
除了doInBackground方法,其他四个方法都是运行在主线程中
AsyncTask必须在主线程中创建,一个AsyncTask对象只能调用一次excute()方法,且在主线程中调用
③AsyncTask注意事项:
AsyncTask跟Handler一样,都会导致内存泄漏,也就是activity销毁时仍然持有activity对象的引用,导致activity不能及时释放;解决方法就是:AsyncTask内部持有外部Activity的弱引用,将AsyncTask改为静态内部类(和Handler处理一样);在onDestroy方法里,调用AsyncTask.cancel();
有可能导致结果丢失,当屏幕旋转、Activity在内存紧张时被回收的情况下,activity被重新创建,还在运行的asynctask会持有之前activity的非法引用,导致onpostexecute没有任何作用。
3.android中使用子线程
有三种方式:
①继承Thread类
②实现Runnable接口
③实现Callable接口
Callable接口和Runnable接口的区别:
它们的主要区别是 Callable 的 call() 方法可以返回值和抛出异常,而 Runnable 的 run() 方法没有这些功能。Callable 可以返回装载有计算结果的 Future 对象。(鄙人没用过Callable,这个是用在线程池中的。。线程池还停留在只会用简单使用的地步。。)
——————
①Thread来的run()和start()方法区别:
start()方法用来启动线程,此时线程是处于就绪状态并没有运行,然后调动run()方法来完成其运行操作。run()方法结束,此线程终止
②Thread类的sleep()方法和对象的wait()方法都可以让线程暂停执行,它们有什么区别?
sleep()方法(休眠)是线程类(Thread)的静态方法,调用此方法会让当前线程暂停执行指定的时间,将执行机会(CPU)让给其他线程**,但是对象的锁依然保持**,因此休眠时间结束后会自动恢复。wait()是Object类的方法,调用对象的wait()方法导致当前线程放弃对象的锁(线程暂停执行),进入对象的等待池(wait pool),只有调用对象的notify()方法(或notifyAll()方法)时才能唤醒等待池中的线程进入等锁池(lock pool),如果线程重新获得对象的锁就可以进入就绪状态。
——————————————————
线程安全:
4.线程池
①为什么用线程池?:
1:在任务众多的情况下,系统要为每一个任务创建一个线程,而任务执行完毕后会销毁每一个线程,所以会造成线程频繁地创建与销毁。
2:多个线程频繁地创建会占用大量的资源,并且在资源竞争的时候就容易出现问题,同时这么多的线程缺乏一个统一的管理,容易造成界面的卡顿。
3:多个线程频繁地销毁,会频繁地调用GC机制,这会使性能降低,又非常耗时。
总而言之:频繁地为每一个任务创建一个线程,缺乏统一管理,降低性能,并且容易出现问题。
②线程池好处:
1:对多个线程进行统一地管理,避免资源竞争中出现的问题。
2:(重点):对线程进行复用,线程在执行完任务后不会立刻销毁,而会等待另外的任务,这样就不会频繁地创建、销毁线程和调用GC
3:JAVA提供了一套完整的ExecutorService线程池创建的api,可创建多种功能不一的线程池,使用起来很方便。
③常见线程池
1.newSingleThreadExecutor
单个线程的线程池,即线程池中每次只有一个线程工作,单线程串行执行任务
2.newFixedThreadExecutor(n)
固定数量的线程池,只有核心线程,每提交一个任务就是一个线程,直到达到线程池的最大数量,然后后面进入等待队列直到前面的任务完成才继续执行;
当线程处于空闲的时候,并不会被回收,除非线程池被关闭,由于FixedThreadPool中只有核心线程并且这些核心线程不会被回收,这意味着它能够更加快速地响应外界的请求.
3.newCacheThreadExecutor(推荐使用)
可缓存线程池,当线程池大小超过了处理任务所需的线程,那么就会回收部分空闲(一般是60秒无执行)的线程,当有任务来时,又智能的添加新线程来执行。它只有非核心线程
(1)比较适合执行大量的耗时较少的任务.
(2)当整个线程都处于闲置状态时,线程池中的线程都会超时而被停止,这时候的CacheThreadPool几乎不占任何系统资源的.

4.newScheduleThreadExecutor
它的核心线程数量是固定的,而非核心线程数是没有限制的,并且当非核心线程闲置时会被立即回收;主要用于执行定时任务和具有固定周期的重复任务.
ex:

    private ExecutorService cachedThreadPool=Executors.newCachedThreadPool();
    /**
     * 创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。
     * @param callback
     */
    public void getCachedThreadPool(Callback callback){
      
        Runnable runnable= () -> {
         callback.start();
            Log.i(TAG, "activeThread-cached: "+Thread.activeCount());
        };
        cachedThreadPool.submit(runnable);
    }

Looper.loop()为什么不会阻塞主进程(ANR)?

因为Android应用是由事件驱动的。looper.loop()不断的轮询接收事件,handler不断的处理事件。(Looper.loop方法用于维护应用的状态,不该进去怎么保证运行呢?)每一个点击触摸事件,或者Activity的生命周期的创建,都是运行在looper.loop()控制之下,简单的来说就是,这些都是属于一个事件消息,然后由looper.loop()轮询到,接着做相应的操作

Looper.loop()当没有轮询到事件的时候,整个应用程序就退出了,而我们的消息事件都是在Looper.loop()循环内,如果循环内没有及时的处理事件,就会造成了ANR.

强引用&弱引用

1.强引用:就是通过new得到的对象引用
强引用是使用最普遍的引用。如果一个对象具有强引用,在GC ROOTS中的话GC就不会回收他,当内存空间不足时,即使抛出OOM也不会回收
ex:
图片这种如果使用通常的强引用,强引用本身会使得图像一直存留在内存中,必须自己决定什么时候移除缓存中的引用,这样对象才能被垃圾回收机制回收。
2.弱引用
只具有弱引用的对象拥有更短暂的生命周期,在垃圾回收器扫描它所管辖的内存区域的过程中,一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存
当我们想引用一个对象,但是这个对象有自己的生命周期,我们不想介入这个对象的生命周期,这时候就要用弱引用了。
在android中,比如:handler、asyncTask这些


第三部分

部分转载地址

Android类加载器

Android平台上虚拟机(Dalvik)上运行的是Dex字节码,Java虚拟机上一个Java源码文件生成一个对应的.class文件,Android把所有Class文件进行合并、优化,生成一个class.dex文件,目的在于去除不同class文件中重复的东西。若apk不进行分包处理,那么一个apk对应一个dex文件(方法数超过65535分包)。
Android常用的两种类加载器:
DexClassLoader、PathClassLoader
这两个均继承于BaseDexClassLoader,区别在于调用父类构造器时,DexClassLoader多传了一个optimizedDirectory参数,这个参数必须是内部存储目录,用来缓存系统创建的Dex文件。两者区别:
DexClassLoader:可加载jar、apk和dex,可以从SD卡中加载
PathClassLoader:只能加载已安装到系统中(即/data/app目录下)的apk文件。
所以我们可以用DexClassLoader去加载外部的apk。(热修复)

JVM、Dalvik和ART的区别

链接
JVM:Java虚拟机,并不专为Java所实现运行,只要其他编程语言能生成Java字节码,那这个语言也能在JVM上运行。
Dalvik:是Android 平台的虚拟机,依赖于Linux内核。
JVM和Dalvik的区别:

Java虚拟机Dalvik虚拟机
java虚拟机基于栈,基于栈的机器必须使用指令来载入和操作栈上数据Dalvik基于寄存器
java虚拟机上运行的是java字节码(java类会被编译成一个或多个字节码.class文件,打包到.jar文件中,jvm从相应的.class文件和.jar获取相应的字节码)Dalvik运行的是自己专属的.dex字节码格式
-一个应用对应于一个Dalvik虚拟机实例
JVM在运行时为每一个类装载字节码(很多class文件,jar文件)Dalvik只包含一个.dex文件,这个文件包含了程序中所有的类

实际上,在14年的时候,谷歌就已经移除Dalvik,转而用ART虚拟机了,但是,国内的运营商会不会revert,鬼才知道嘞,所以,我们需要判断一下,当前手机是Dalvik还是art,不过我想这都五年了,不可能还会用dalvik了吧,获取方式:


private boolean getIsArtInUse() {
    final String vmVersion = System.getProperty("java.vm.version");
    return vmVersion != null && vmVersion.startsWith("2");
}

如果是ART虚拟机,那么属性值会大于2.0.0

好的,接着介绍ART虚拟机:
即Android Runtime,Android 4.4发布了一个ART运行时,准备用来替换掉之前一直使用的Dalvik虚拟机。
ART 的机制与 Dalvik 不同。在Dalvik下,应用每次运行的时候,字节码都需要通过即时编译器(just in time ,JIT)转换为机器码,这会拖慢应用的运行效率,而在ART 环境中,应用在第一次安装的时候,字节码就会预先编译成机器码,使其成为真正的本地应用。这个过程叫做预编译(AOT,Ahead-Of-Time)。这样的话,应用的启动(首次)和执行都会变得更加快速。
Dalvik与Art的区别
Dalvik每次都要编译再运行,Art只会首次启动编译
Art占用空间比Dalvik大(原生代码占用的存储空间更大),就是用“空间换时间”
Art减少编译,减少了CPU使用频率,使用明显改善电池续航
Art应用启动更快、运行更快、体验更流畅、触感反馈更及时

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值