Android

61 阅读9分钟

文档宗旨

1、等时间久了,回头看还能看得懂

Dagger2

用法

添加依赖

告诉dagger,当需要这些类型的对象时,可以调用注解的函数进行获取

1、@Inject 在构造函数上

2、@Module @Provider

创建注入器

当需要进行依赖注入时,

@Component

pulibc interface xxxComponent {

}

属性有modules

依赖传递

当dagger要创建一个对象时,如果其构造函数中需要传入其他类型,这些其他类型如果也被dagger管理,则会自动创建这些类型

单例

默认情况下,当dagger需要一个对象时,会重新创建;而很多时候只需要复用一个实例就可以了。这时候如下操作:

①    构造的方法添加作用域注解

②    使用他的component也要添加一样的作用域注解

此时dagger产生的实例生命周期将与component一致,且必须保持一致。

Hilt

Hilt是专门用于android的dagger2。即针对Android环境下的dagger2使用进行优化。

即Hilt针对Anroid环境,简化了Dagger2使用时编写的重复代码。

Activity

Android将Activity分成几个运行时间点,当Activity处理某一运行时间点时,系统会调用相应的函数。因此开发者通常Override 这些函数来实现其App的功能。

常用回调

onCreate()

       void onCreate(@Nullable Bundle savedInstanceState)

       当Activity被创建时,被调用。

       在onCreate()执行finish(),onDestory()就会立即执行,而不会走其他生命周期回调。

       savedInstanceState一般为null,当Activity被异常重启后,savedInstanceState由onSaveInstanceState()创建。

onResume()

       当Activity前台可见时,被调用

onPause()

       当焦点从当前Activity转移到其他地方时,被调用。

onDestroy()

       当Activity被销毁时,被调用

onSaveInstanceState()

void onSaveInstanceState(@NonNull Bundle outState)

       当Activity被异常杀死时,被调用。

       该回调在onStop()之后,onDestroy()前被调用。

       默认实现会遍历该Activity的View树,保存其中有id的view的状态。

       Override这个函数,给空实现,可以加快异常重启的速度。

onActivityResult()

void onActivityResult(int requestCode, int resultCode, Intent data)

       如果在当前Activity启动了其他Activity,那么那个被启动的Activity可以通过setResult()将启动情况返回给启动发起者Activity。

       requestCode:用于区分是那一次启动activity。由发起者设定该值。

AndroidManifest

常用属性分析

process

用在四大组件下,用于表示目标组件所要运行的进程。

这使得同一应用application的不同组件可以运行在不同的进程。

process=”:abc”

此时生成的进程名为 包名:abc ,并且表示当进程不能跑其他应用组件

process=”.abc”

此时生成的进程名为 包名.abc

处在同一进程下,app的私有数据可以互相访问,如data目录下的数据

Handler-Looper

使用场景

1、  动作要在未来某一个条件触发

2、  想要在其他线程执行某一行为

这两个场景下如果要自己实现功能,会稍微麻烦。可以使用android写好的Looper-Handler架构

基本原理

每个线程可以创建一个MessageQueue,通过Handler可以往其中发送Message。

Looper死循环会不断从MessageQueue中取出Message,交予Handler进行处理。

在实现上,Handler与Looper交互,Looper再与MessageQueue交互。即开发者持有Handler,是间接与MesaageQueue交互

Message的派发流程

       Looper会阻塞等待MessageQueue中出现Message、

当通过Handler将Message传入到MessageQueue中后,Looper线程被唤醒,并获得Message。然后Looper将Message交给Handler.dispatchMessage()进行派发。

派发逻辑如下:

用法

启动Looper

该机制基于MessageQueue,所以使用前,需确保工作线程已经创建MessageQueue。而创建Looper就是创建MessageQueue(在Looper构造时,会new MessageQueue)。

所以每个线程最多有一个MessageQueue,也只能有一个对应的Looper(在Looper构造时会检查)。

另外,Looper每个线程都能使用同一个Api( prepare() )进行创建,其原理基于threadlocal。

 

创建方法如下:

1、 若是在UI线程中

UI线程默认已经初始化完成Looper,该步骤可省略

2、 若是在子线程中

需执行Looper.prepare()初始化Looper,

最后执行Looper.loop()使该线程进行循环操作Messagequeue

 

构造Handler实例

       使用Handler时,一般都要继承或者使用匿名类的方式。因为需要override 消息处理函数handleMessage()。

使用如下Api创建:

       Handler(@NonNull Looper looper)

       Handler(@NonNull Looper looper, @Nullable Callback callback)

创建Handler需要使用传入Looper的构造函数。目的:将该Handler对象与某线程的Looper(也是MessageQueue)绑定。也即Handler要往哪一个线程发消息。

实现消息处理函数

void handleMessage(@NonNull Message msg)

通过Handler发送Message时,Message会记录发送的Handler,因此Looper知道当该Message需要被处理时,应该由哪个Handler实例处理

发消息到MessageQueue

常用的API有以下:

boolean sendMessage(@NonNull Message msg)

boolean sendMessageDelayed(@NonNull Message msg, long delayMillis)

boolean sendMessageAtTime(@NonNull Message msg, long uptimeMillis)

 

boolean post(@NonNull Runnable r)

boolean postAtTime(@NonNull Runnable r, long uptimeMillis)

boolean postDelayed(@NonNull Runnable r, long delayMillis)

通过post相关接口,其实也是发送Message。不过封装了一层,将Message.callback进行了赋值。此时Message处理逻辑刚好与Message派发流程一致。

 

Message的获取建议通过Handler. obtainMessage()。使用这样的方式,Message从Message pool中获取。

 

Message可配置的属性有:

public int what
public int arg1;
public int arg2;
public Object obj;

Bundle data;

扩展

异步消息

Framework开发中源码会使用到。

通过MessageQueue.postSyncBarrier() 往消息队列中放置一个消息Barrier,当Looper处理到这个消息Barrier时,只会继续执行MessageQueue中的Async Message。

若此时没有Async Message,线程阻塞。或者通过

void removeSyncBarrier(int token)

异常消息Barrier。

HandlerThread

基于Handler可以实现专门用于处理耗时任务的线程,谷歌提供了HandlerThread方便开发。相比于自己创建,HandlerThread考虑了线程同步的问题。

 

 

Toast

作用

toast是一个view,用于短消息通知用户。

用法

       通过Toast类,我们可以创建一个toast并且show it。

       一个简单的创建toast的对象的方法,就是通过其静态方法:

              Toast makeText(Context context, CharSequence text, int duration)

       然后通过Toast.show() 就可以将toast展示出来。

      

       实际上,创建出toast后,我们可以通过setXX()方法,更该其显示的view,文字等等。但是有以下注意事项:

1、 对于setView()

在R版本之后,如果应用在后台show一个toast,只能出现系统默认的view。

2、 对于setText()

其重载了resID的方法,所以可以用resID作为参数,实现语言本地化

 

另外,

1、在toast显示期间,如果想立即关闭该toast,可以调用Toast.cancel()

2、可以通过Toast.addcallback()在toast显示或隐藏时进行回调

JAVA

注解

注解定义的方法与原理

       我们可以在一个方法上,使用注解 @Deprecated,如下:

****

****@Deprecated是JDK的一个内置实现的注解,其作用是在java源文件编译时进行检查,如果被注解的方法不是重载方法则编译报错。那么如何定义一个注解呢?

       一个注解的定义方式如下:

       上图中,需要使用@interface 表示在定义一个注解,该注解的名字为MyAnno。

       @interface的原理使用下图解释更为直观:

       即使用@interface的文件,编译时,变成一个继承于Annotation的接口。

 

       实际上,我们在看代码时,会看到注解还有属性,比如下图:

       带属性的注解的定义方式如下:

       从上图可以归纳出其定于属性时的语法:

              属性的类型 属性名字() 默认值

       另外,如果只有一个属性需要赋值,并且属性的名称为value,则赋值时value可以省略,可以直接定义值,如下图:

       注解的属性的获取方式,需要通过反射。

 

Android中的注解

@SystemApi

可用范围:

@Target({TYPE, FIELD, METHOD, CONSTRUCTOR, ANNOTATION_TYPE, PACKAGE})

属性的含义:

       Client client() default android.annotation.SystemApi.Client.PRIVILEGED_APPS

       PRIVILEGED_APPS: 只能被privileged应用使用

       SYSTEM_SERVER:只能在系统进程中使用

 

@UnsupportedAppUsage

       这个注解的对象的api不建议给普通app应用开发者使用。

 

Framework

ServiceManager分析

       一般这个类的方法都用了@UnsupportedAppUsage,不建议普通app应用开发使用相关api。

       但是原生的app经常会用到,其提供了几个常用的api:

1、根据service名获取其对象

       static IBinder getService(String name)

2、添加service到ServiceManager

       static void addService(String name, IBinder service)

3、 输出所有的service

static String[] listServices()

 

       这些api都是通过sServiceManager完成的,即ServiceManager也只是一个代理类。

       具体sServiceManager的方法,暂时不研究。

 

Media

MIME类型

作用:

       MIME (Multipurpose Internet Mail Extensions) 是描述消息内容类型的标准,用来表示文档、文件或字节流的性质和格式。

MIME 消息能包含文本、图像、音频、视频以及其他应用程序专用的数据

 

应用场景:

       如浏览器,通常使用 MIME 类型(而不是文件扩展名)来确定如何处理URL,因此 We b服务器在响应头中添加正确的 MIME 类型非常重要。如果配置不正确,浏览器可能会无法解析文件内容,网站将无法正常工作,并且下载的文件也会被错误处理

 

语法结构:

       MIME 类型通用结构:

type/subtype

       由类型与子类型两个字符串中间用 / 分隔而组成,不允许有空格。MIME类型对大小写不敏感,但是传统写法都是小写。

 

MediaCodec

应用方法

1、 初始化MediaCodec

// type 理想的输出数据MIME类型,比如video/avc

static MediaCodec createEncoderByType(@NonNull String type)

 

2、 配置编解码器

MediaFormat mFormat = MediaFormat.createVideoFormat("video/avc", 640 ,480);    

mFormat.setInteger(MediaFormat.KEY_BIT_RATE,600);       // 指定比特率

mFormat.setInteger(MediaFormat.KEY_FRAME_RATE,30);  // 指定帧率

mFormat.setInteger(MediaFormat.KEY_COLOR_FORMAT,mColorFormat);  // 指定编码器颜色格式,硬件相关

mFormat.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL,10); // 指定关键帧时间间隔

mFormat.setInteger(MediaFormat.KEY_BITRATE_MODE,MediaCodecInfo.EncoderCapabilities.BITRATE_MODE_VBR); // 编码模式

mVideoEncodec.configure(mFormat,null,null,MediaCodec.CONFIGURE_FLAG_ENCODE);

NDK

NDK的使用场景

1、 性能考量

对图形,视频,音频等计算密集型应用,将复杂模块计算封装在.so或者.a文件中处理

2、 提高数据安全性

封装so来实现。使用纯Java开发的app是有很多逆向工具可以破解的

3、  

数据类型的对应关系

常用的数据类型c++层已经定义好了:

 

Java类型      Native类型   描述

boolean jboolean     unsigned 8 bits

byte     jbyte        signed 8 bits

char     jchar         unsigned 16 bits

short    jshort        signed 16 bits

int       jint          signed 32 bits

long     jlong        signed 64 bits

float     jfloat        32 bits

double   jdouble       64 bits

void     void    

 

Class       jclass

String      jstring

Throwable  jthrowable

 

Object[]    jobjectArray

boolean[]   jbooleanArray

……

 

重载签名

当native层要调用java层的函数时,光知道函数名还不行,因为java支持重载。Jvm使用函数名+签名的形式进行定位。

       签名的定义如下:

Type Signature     Java Type

Z     boolean

B     byte

C     char

S     short

I      int

J      long

F     float

D     double

L fully-qualified-class ;       类的权限定描述符:如String -> Ljava.lang.String

[ type     type[] :属性描述

( arg-types ) ret-type  method type:方法描述

       Demo:

long f (int n, String s, int[] arr);

(I;Ljava/lang/String;[I)J

JavaVm/JNIEnv