Android 学习笔记

283 阅读43分钟

@[TOC](Android 学习笔记)

最全 Android 常用开源库总结!

最全 Android 常用开源库总结!

Android studio 最全最好用的插件

Android studio 最全最好用的插件

Android shape属性大全

Android shape属性大全

UML

在这里插入图片描述

UML学习链接

Android改主题字体和字体大小

在这里插入图片描述

Android Live Template(实时模板)的使用

大幅提高Android开发效率之Android项目模板化

idea中live template的详细使用教程

Android studio Debug

Android Studio Debug 的 9 个小技巧

设计模式

设计模式

Gradle

这一次,彻底了解 Gradle 吧! Gradle 与 Android 构建入门

AppTheme属性设置集合

AppTheme属性设置集合

Android Fragment 非常详细的一篇

Android Fragment 非常详细的一篇

FragmentTwo

public class FragmentTwo extends Fragment implements View.OnClickListener {
    private String mParam;
    private static String ARG_PARAM = "param_key";

    private Activity mActivity;
    @Override
    public void onAttach(Context context) {
        Log.e("tony", "onAttach");
        super.onAttach(context);
//        如果要获取Activity对象,不建议调用getActivity(),而是在onAttach()中将Context对象强转为Activity对象。 参考链接https://www.jianshu.com/p/11c8ced79193
        mActivity = (Activity) context;
        mParam = getArguments().getString(ARG_PARAM);  //获取参数
    }

    public interface FOneBtnClickListener {
        void onFOneBtnClick();
    }


    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.fragmentlayout_two, container, false);
        TextView textView = view.findViewById(R.id.textView);
        textView.setText(mParam);
        Button button = view.findViewById(R.id.button);
        button.setOnClickListener(this);

        return view;
    }

    @Override
    public void onClick(View view) {
        if(mActivity!=null) {
            ((FOneBtnClickListener) mActivity).onFOneBtnClick();
        }
    }

    public static FragmentTwo newInstance(String str) {
        FragmentTwo fragment = new FragmentTwo();
        Bundle bundle = new Bundle();
        bundle.putString(ARG_PARAM, str);
        fragment.setArguments(bundle);   //设置参数
        return fragment;
    }
}

MainActivity

public class MainActivity extends AppCompatActivity implements FragmentTwo.FOneBtnClickListener {
    private FragmentTwo fragmentTwo;
    @Override
    protected void onCreate(Bundle savedInstanceState)  {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Log.e("tony", "onCreate");
//        // 添加Fragment前检查是否有保存的Activity状态。如果没有状态保存,说明Acitvity是第1次被创建,我们添加Fragment。如果有状态保存,说明Activity刚刚出现过异常被销毁过,现在正在恢复,我们不再添加Fragment。
//        //作者:梦想编织者灬小楠
//        //链接:https://www.jianshu.com/p/5761ee2d3ea1
//        if(savedInstanceState == null){
//            getSupportFragmentManager().beginTransaction()
//                    .add(R.id.container, FragmentOne.newInstance("hello world"), "f1")        //.addToBackStack("fname")
//                    .commit();
//        }


               FragmentManager fm = getSupportFragmentManager();
               fragmentTwo = (FragmentTwo) fm.findFragmentById(R.id.container);
               if(fragmentTwo == null){
//                   传递参数
                   fragmentTwo = FragmentTwo.newInstance("MainActivity传递到FragmentTwo的参数");
                   fm.beginTransaction().add(R.id.container,fragmentTwo).commit();
               }

    }

//    FragmentTwo的button的点击事件
    @Override
    public void onFOneBtnClick() {
        Toast.makeText(MainActivity.this,"onFOneBtnClick",Toast.LENGTH_LONG).show();
    }
}

Fragment 与 Activity 通信

  • a、如果 Activity 中包含自己管理的 Fragment 的引用,可以通过引用直接访问所有的 Fragment 的 public 方法
  • b、如果 Activity 中未保存任何 Fragment 的引用,可以通过每个 Fragment 都有一个唯一的 TAG或者 ID使用 getFragmentManager.findFragmentByTag()或者 findFragmentById()获得任何 Fragment 实例,然后进行操作。
  • c、在 Fragment 中可以通过 getActivity 得到当前绑定的 Activity 的实例,然后进行操作
  • d、如果在Fragment中需要Context,可以通过调用getActivity(),如果该Context需要在Activity被销毁后还存在,则使用 getActivity(0.getApplicationContext().

Fragment 返回数据给 Activity 在Fragment中定义接口,并让Activity实现该接口

Activity传递参数给Fragment 使用setArguments方法,并在onAttach()中调用getArguments()获取参数

Fragment的replace、add、remove方法

Fragment的replace、add、remove方法 Fragment涉及的add、remove和replace方法和回退栈的关系详解

  • add是一层一层往上放
  • remove就是从顶上一层一层的往下拿
  • replace相当于执行了remove和add,会remove相同id的所有Fragment,然后再把这个Fragment放进去
  • add、remove和replace方法和回退栈基本没有关系。这里有一个点非常容易混淆,就是因为add也是一层一层的往FrameLayout里添加Fragment,那么在按回退按钮的时候是不是一层一层的再拿出来呢?笔者这里告诉大家,根本不会,除非加入了回退栈。

FragmentPagerAdapter与FragmentStatePagerAdapter区别

  • FragmentStatePagerAdaper在每次切换viewpager的时候会回收内存, 适合页面比较多的情况。

  • FragmentStatePagerAdapter:会销毁不再需要的 fragment,当当前事务提 交以后,会彻底的将 fragmeng 从当前 Activity 的 FragmentManager 中移除, state 标明,销毁时,会将其 onSaveInstanceState(Bundle outState)中的 bundle 信息保存下来,当用户切换回来,可以通过该 bundle 恢复生成新的 fragment,也就是说,你可以在 onSaveInstanceState(Bundle outState)方法 中保存一些数据,在 onCreate 中进行恢复创建。 如上所说,使用 FragmentStatePagerAdapter 当然更省内存,但是销毁新建 也是需要时间的。一般情况下,如果你是制作主页面,就 3、4 个 Tab,那么可 以选择使用 FragmentPagerAdapter,如果你是用于 ViewPager 展示数量特别 多的条目时,那么建议使用 FragmentStatePagerAdapter。

  • FragmentPagerAdapter只是做了页面分离。

  • FragmentPagerAdapter:对于不再需要的 fragment,选择调用 detach 方 法,仅销毁视图,并不会销毁 fragment 实例。

Fragment生命周期与Activity生命周期之间的关系

在这里插入图片描述

  • onAttach():Fragment和Activity相关联时调用。可以通过该方法获取Activity引用,还可以通过getArguments()获取参数。
  • onCreate():Fragment被创建时调用。
  • onCreateView():创建Fragment的布局。
  • onActivityCreated():当Activity完成onCreate()时调用。
  • onStart():当Fragment可见时调用。
  • onResume():当Fragment可见且可交互时调用。
  • onPause():当Fragment不可交互但可见时调用。
  • onStop():当Fragment不可见时调用。
  • onDestroyView():当Fragment的UI从视图结构中移除时调用。
  • onDestroy():销毁Fragment时调用。
  • onDetach():当Fragment和Activity解除关联时调用。

FragmentTransaction有一些基本方法,下面给出调用这些方法时,Fragment生命周期的变化:

  • add(): onAttach()->…->onResume()。 remove(): onPause()->…->onDetach()。
  • replace(): 相当于旧Fragment调用remove(),新Fragment调用add()。
  • show(): 不调用任何生命周期方法,调用该方法的前提是要显示的 Fragment已经被添加到容器,只是纯粹把Fragment UI的setVisibility为true。
  • hide(): 不调用任何生命周期方法,调用该方法的前提是要显示的Fragment已经被添加到容器,只是纯粹把Fragment UI的setVisibility为false。
  • detach(): onPause()->onStop()->onDestroyView()。UI从布局中移除,但是仍然被FragmentManager管理。
  • attach(): onCreateView()->onStart()->onResume()。
  • 切换到该Fragment,分别执行onAttach()、onCreate()、onCreateView()、onActivityCreated()、onstart()、onResume()方法。
  • 锁屏,分别执行onPause()、onStop()方法。
  • 亮屏,分别执行onstart()、onResume()方法。
  • 覆盖,切换到其他Fragment,分别执行onPause()、onStop()、onDestroyView()方法。
  • 从其他Fragment回到之前Fragment,分别执行onCreateView()、onActivityCreated()、onstart()、onResume()方法。

Android 设计模式

关于Android架构,你是否还在生搬硬套

关于Android架构,你是否还在生搬硬套

Android 设计模式—MVC

在这里插入图片描述 MVC即Model、View、Controller即模型、视图、控制器

  • Model:针对业务模型建立的数据结构和类(与View无关,只与业务相关)
  • View:XML/JAVA显示。Activity/Frgament也承担了View的功能。
  • Controller:Android的控制层通常在ActivityFragment之中。 本质就是Controller操作Model层的数据,并且将数据返回给View层展示。

MVC 的缺点

  • Activity并不是MVC中标准的Controller,既有Controller的职责也有View的职责,导致Activity的代码过于臃肿。
  • View层和Model层互相耦合,耦合过重,代码量过大,不易于开发和维护。
Android 设计模式—MVP

在这里插入图片描述 Android MVP 架构 参考链接2

  • Model层:Model层也是数据层。它区别于MVC架构中的Model,在这里Model它负责对数据的存取操作,例如对数据库的读写,网络的数据的请求等。

  • View层:View层也是视图层,只负责对数据的展示,提供友好的界面与用户进行交互。开发中通常将Activity或者Fragment作为View层。

  • Presenter层:是连接View层与Model层的桥梁并对业务逻辑进行处理。在MVP架构中Model与View无法直接进行交互。所以在Presenter层它会从Model层获得所需要的数据,进行一些适当的处理后交由View层进行显示。这样通过Presenter将View与Model进行隔离,使得View和Model之间不存在耦合,同时也将业务逻辑从View中抽离。

MVP在实现代码简洁的同时,额外增加了大量的接口、类,不方便进行管理。通过Contract,将Model、View、Presenter 进行约束管理,方便后期类的查找、维护。

  • MVP和MVC的区别?
  1. MVP中绝对不允许View直接访问Model
  2. 本质是增加了一个接口降低一层耦合度 3.MVP Presenter完全将Model和View解耦,主要逻辑处于Presenter中。
Android 设计模式—MVVM

在这里插入图片描述

  1. Model-View-ViewModel,将Presenter替换为ViewModel。
  2. ViewModel和Model/View进行了双向绑定。
  3. View发生改变时,ViewModel会通知Model进行更新数据
  4. Model数据更新后,ViewModel会通知View更新显示
  5. 谷歌发布了MVVM支持库Data Binding:能将数据绑定到xml中
  6. 现在谷歌又推出了ViewModel和LiveData组件用于更方便的实现MVVM

Android几种进程

  • 前台进程:即与用户正在交互的Activity或者Activity用到的Service等,如果系统内存不足时前台进程是最后被杀死的
  • 可见进程:可以是处于暂停状态(onPause)的Activity或者绑定在其上的Service,即被用户可见,但由于失去了焦点而不能与用户交互
  • 服务进程:其中运行着使用startService方法启动的Service,虽然不被用户可见,但是却是用户关心的,例如用户正在非音乐界面听的音乐或者正在非下载页面自己下载的文件等;当系统要空间运行前两者进程时才会被终止
  • 后台进程:其中运行着执行onStop方法而停止的程序,但是却不是用户当前关心的,例如后台挂着的QQ,这样的进程系统一旦没了有内存就首先被杀死 空进程:不包含任何应用程序的程序组件的进程,这样的进程系统是一般不会让他存在的

如何避免后台进程被杀死: 调用startForegound,让你的Service所在的线程成为前台进程 Service的onStartCommond返回START_STICKY或START_REDELIVER_INTENT Service的onDestroy里面重新启动自己

Android开发基础之服务Service

Android开发基础之服务Service Service(服务)是一个一种可以在后台执行长时间运行操作而没有用户 界面的应用组件,Service是使程序后台运行的保证.

Service与Activity的区别:

当 Activity 被 finish 之后,你不再持有该 Thread 的引用,并且没有办法在不同的 Activity 中对同一 Thread 进行控制。而任何 Activity 都可以控制同一 Service,而系统也只会创建一个对应 Service 的实例

  • startService()方式:
startService --> onCreate()--> onStartConmon()--> stopService-->onDestroy()
1.多次调用时onCreate方法只执行一次,onStartConmon()会被多次调用。
2.当我们通过startService启动时候,通过intent传值,在onStartConmon()方法中获取值的时候,一定要先判断intent是否为null
  • bindService()方式进行绑定
onCreate()-->onBind()-->unBind()-->onDestroy() 
1.这种方式更方便Activity与Service的通信,activity获取ServiceConnection对象,通过ServiceConnection来获取service中内部类的类对象
2.绑定服务的生命周期跟Activity是不求同时生,但求同时死,Activity没了,服务也要解绑;
3.绑定服务开启的服务,只可以解绑一次,多次解绑会抛异常;
  • 两种方式的优缺点:

1、startService这个方法来启动服务的话,是长期运行的,只有stopService才会停止服务。而bindService来启动服务,不用的时候,需要调用unBindService,否则会导致context泄漏,所以bindService不是长期运行的。当context销毁的时候,则会停止服务运行。

2、startService来启动服务可以长期运行,但是不可以通讯,而bindService的方式来启动服务则可以通讯,两者都有优缺点,所以我们就有了混合起来使用的方法。

到底什么是Binder

  • 通常意义下,Binder指的是一种通信机制
  • 对于传输过程而言,Binder是 可以进行跨进程传递的对象

线程中sleep和wait的区别

(1)这两个方法来自不同的类,sleep是来自Thread,wait是来自Object; (2)sleep方法没有释放锁,而wait方法释放了锁。 (3)wait,notify,notifyAll只能在同步控制方法或者同步控制块里面使用,而sleep可以在任何地方使用。

关键字final和static是怎么使用的。

final:

  • final 修饰的类叫最终类,该类不能被继承。

  • final 修饰的方法不能被重写。

  • final 修饰的变量叫常量,常量必须初始化,初始化之后值就不能被修改。

static:

  • static变量:对于静态变量在内存中只有一个拷贝(节省内存),JVM只为静态分配一次内存, 在加载类的过程中完成静态变量的内存分配,可用类名直接访问(方便),当然也可以通过对象来访问(但是这是不推荐的)。
  • static方法 static方法可以直接通过类名调用,任何的实例也都可以调用,因此static方法中不能用this和super关键字, 不能直接访问所属类的实例变量和实例方法(就是不带static的成员变量和成员成员方法),只能访问所属类的静态成员变量和成员方法。

String,StringBuffer,StringBuilder区别

1、三者在执行速度上:StringBuilder > StringBuffer > String (由于String是常量,不可改变,拼接时会重新创建新的对象)。 2、StringBuffer是线程安全的,StringBuilder是线程不安全的。(由于StringBuffer有缓冲区)

StringBuffer中很多方法可以带有synchronized关键字,所以可以保证线程是安全的,但StringBuilder的方法则没有该关键字,所以不能保证线程安全

View,ViewGroup事件分发

Android的View事件分发 Android ViewGroup事件分发机制 View事件的运行顺序为: dispatchTouchEvent—> setOnTouchListener—> onTouchEvent

VeiwGroup事件的运行顺序: VeiwGroup的dispatchTouchEvent -> VeiwGroup的onInterceptTouchEvent(需要拦截,只要return true) ->View的dispatchTouchEvent ->View的onTouchEvent 在这里插入图片描述

如果最后一个view没有消费事件 ,事件就会依次回传,传递到最高位的Activity,如果Activity也没有消费事件,这个事件就会被抛弃

getParent().requestDisallowInterceptTouchEvent(true); 这样即使ViewGroup在MOVE的时候return true,子View依然可以捕获到MOVE以及UP事件。

Touch事件传递机制

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

  • Activity调用dispathTouchEvent()方法,把事件传递给Window;
  • Window再将事件交给DecorView(DecorView是View的根布局);
  • DecorView再传递给ViewGroup;
  • Activity ——> PhoneWindow ——> 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,表示不消耗事件,交回上层处理。

final,finally,finalize的区别

final:修饰类、成员变量和成员方法,类不可被继承,成员变量不可变,成员方法不可重写 finally:与try...catch...共同使用,确保无论是否出现异常都能被调用到 finalize:类的方法,垃圾回收之前会调用此方法,子类可以重写finalize()方法实现对资源的回收

Serializable 和Parcelable 的区别

Serializable Java 序列化接口 在硬盘上读写 读写过程中有大量临时变量的生成,内部执行大量的i/o操作,效率很低。 Parcelable Android 序列化接口 效率高 使用麻烦 在内存中读写(AS有相关插件 一键生成所需方法) ,对象不能保存到磁盘中

Serializable 方式

使用 Intent 来传递对象通常有两种实现方式,Serializable 和 Parcelable,本小节中我们先来学习一下第一种的实现方式。

Serializable 是序列化的意思,表示将一个对象转换成可存储或可传输的状态。序列化后的对象可以在网络上进行传输,也可以存储到本地。至于序列化的方法也很简单,只需要让一个类去实现 Serializable 这个接口就可以了。

比如说有一个 Person 类,其中包含了 name 和 age 这两个字段,想要将它序列化就可以这样写

public class Person implements Serializable{
	private String name;
	private int age;
	
	public String getName() {
	return name;
	}
	public void setName(String name) {
	this.name = name;
	}
	public int getAge() {
	return age;
	}
	public void setAge(int age) {
	this.age = age;
	}
	
	}

其中 get、set 方法都是用于读取和赋值字段数据的,最重要的部分是在第一行。这里让 Person 类去实现了 Serializable 接口,这样所有的 Person 对象就都是可序列化的了。

接下来在 FirstActivity 中的写法非常简单:

Person person = new Person();
			person.setName("Tom");
			person.setAge(20);
			Intent intent = new Intent(FirstActivity.this, SecondActivity.class);
			intent.putExtra("person_data", person);
			startActivity(intent);

可以看到,这里我们创建了一个 Person 的实例,然后就直接将它传入到 putExtra()方法中了。由Person 类实现了 Serializable 接口,所以才可以这样写。

接下来在 SecondActivity 中获取这个对象也很简单,写法如下:

Person person = (Person) getIntent().getSerializableExtra("person_data");

这里调用了 getSerializableExtra()方法来获取通过参数传递过来的序列化对象,接着再将它向下转型成 Person 对象,这样我们就成功实现了使用 Intent 来传递对象的功能了。

Parcelable 方式

除了 Serializable 之外,使用 Parcelable 也可以实现相同的效果,不过不同于将对象进行序列化,Parcelable 方式的实现原理是将一个完整的对象进行分解,而分解后的每一部分都是 Intent 所支持的数据类型,这样也就实现传递对象的功能了。

下面我们来看一下 Parcelable 的实现方式,修改 Person 中的代码,如下所示:

public class Person implements Parcelable {
	private String name;
	private int age;
	⋯⋯
	@Override
	public int describeContents() {
		return 0;
	}
	
	@Override
	public void writeToParcel(Parcel dest, int flags) {
		dest.writeString(name); // 写出name
		dest.writeInt(age); // 写出age
	}
	
	public static final Parcelable.Creator<Person> CREATOR = new Parcelable.
	Creator<Person>() {
	
	@Override
	public Person createFromParcel(Parcel source) {
		Person person = new Person();
		person.name = source.readString(); // 读取name
		person.age = source.readInt(); // 读取age
		return person;
	}
	
	@Override
	public Person[] newArray(int size) {
		return new Person[size];
	}
	};
	
}

Parcelable 的实现方式要稍微复杂一些。可以看到,首先我们让 Person 类去实现了Parcelable 接口,这样就必须重写 describeContents()和 writeToParcel()这两个方法。其中 describeContents()方法直接返回 0 就可以了,而 writeToParcel()方法中我们需要调用 Parcel 的 writeXxx()方法将 Person 类中的字段一一写出。注意字符串型数据就调用writeString()方法,整型数据就调用 writeInt()方法,以此类推。

除此之外,我们还必须在 Person 类中提供一个名为 CREATOR 的常量,这里创建了Parcelable.Creator 接口的一个实现,并将泛型指定为 Person。接着需要重写createFromParcel()和 newArray()这两个方法,在 createFromParcel()方法中我们要去读取刚才写出的 name 和 age 字段,并创建一个 Person 对象进行返回,其中 name 和 age都是调用 Parcel 的 readXxx()方法读取到的,注意这里读取的顺序一定要和刚才写出的顺 序完全相同。而 newArray()方法中的实现就简单多了,只需要 new 出一个 Person 数组,并使用方法中传入的 size 作为数组大小就可以了。

接下来在 FirstActivity 中我们仍然可以使用相同的代码来传递 Person 对象,只不过在 SecondActivity 中获取对象的时候需要稍加改动,如下所示:

Person person = (Person) getIntent().getParcelableExtra("person_data");

注意这里不再是调用 getSerializableExtra()方法,而是调用 getParcelableExtra() 方法来获取传递过来的对象了,其他的地方都完全相同。

解析Handler

Message Message是在线程之间传递的信息,它可以在内部携带少量的信息,用于在不同线程之间交换信息。 Handler Handler 顾名思义,也就是处理者的意思,它主要用于发送和处理信息,发送信息一般是使用Handler的sendMessage()方法,而发出的一条信息,经过一系列辗转处理后,最终会传递到Handler HandleMessage方法中。

MessageQueue MessageQueue是消息队列的意思,它主要用于存放所有通过handler发送的信息,这部分消息会一直存在于消息队列中,等待被处理,每个线程只会有一个MessageQueue对象。

Looper Looper是每个线程的MessageQueue的管家,调用Looper的loop()方法后,就会进入到一个无限循环当中,然后每当发现MessageQueue中存在一条消息,就会将它取出。并传递到Handler的HandelMessage方法中,每个线程只会有一个looper对象。

在这里插入图片描述 首先需要在主线程中创建一个Handler对象并重写handleMessage方法,然后当子线程中需要进行UI操作时,就创建一个Message对象并通过Handler将这条信息发送出去,之后这条信息会被添加到MessageQueue的队列当中等待被处理,Looper一直会尝试从MessageQueue中取出待处理信息,最后分发给Handler的handleMessage()方法。由于Handler是在主线程中创建的,所以此时handlerMessage()方法中的代码也会在主线程中运行。

Handler的原理

Android中主线程是不能进行耗时操作的,子线程是不能进行更新UI的。所以就有了handler,它的作用就是实现线程之间的通信。 handler整个流程中,主要有四个对象,handler,Message,MessageQueue,Looper。当应用创建的时候,就会在主线程中创建handler对象, 我们通过要传送的消息保存到Message中,handler通过调用sendMessage方法将Message发送到MessageQueue中,Looper对象就会不断的调用loop()方法 不断的从MessageQueue中取出Message交给handler进行处理。从而实现线程之间的通信。

java的重写与重载

  • 重写是子类对父类的允许访问的方法的实现过程进行重新编写, 返回值和形参都不能改变。即外壳不变,核心重写!
  • 重载(overloading) 是在一个类里面,方法名字相同,而参数不同。返回类型可以相同也可以不同。

equals()和 ==的区别

  • == 比较的是变量(栈)内存中存放的对象的(堆)内存地址,用来判断两个对象的地址是否相同,即是否是指相同一个对象。比较的是真正意义上的指针操作。
  • equals用来比较的是两个对象的内容是否相等,由于所有的类都是继承自java.lang.Object类的,所以适用于所有对象。

Java 多线程

在这里插入图片描述

1.初始状态

  • 实现Runnable接口和继承Thread可以得到一个线程类,new一个实例出来,线程就进入了初始状态。

2.1. 就绪状态

  • 就绪状态只是说你资格运行,调度程序没有挑选到你,你就永远是就绪状态。
  • 调用线程的start()方法,此线程进入就绪状态。
  • 当前线程sleep()方法结束,其他线程join()结束,等待用户输入完毕,某个线程拿到对象锁,这些线程也将进入就绪状态。
  • 当前线程时间片用完了,调用当前线程的yield()方法,当前线程进入就绪状态。
  • 锁池里的线程拿到对象锁后,进入就绪状态。

2.2. 运行中状态

  • 线程调度程序从可运行池中选择一个线程作为当前线程时线程所处的状态。这也是线程进入运行状态的唯一一种方式。

3.阻塞状态

  • 阻塞状态是线程阻塞在进入synchronized关键字修饰的方法或代码块(获取锁)时的状态。

4.等待

  • 处于这种状态的线程不会被分配CPU执行时间,它们要等待被显式地唤醒,否则会处于无限期等待的状态。

5.超时等待

  • 处于这种状态的线程不会被分配CPU执行时间,不过无须无限期等待被其他线程显示地唤醒,在达到一定时间后它们会自动唤醒。

6.终止状态

  • 当线程的run()方法完成时,或者主线程的main()方法完成时,我们就认为它终止了。这个线程对象也许是活的,但是,它已经不是一个单独执行的线程。线程一旦终止了,就不能复生。

  • 在一个终止的线程上调用start()方法,会抛出java.lang.IllegalThreadStateException异常。

几个方法的比较

  • Thread.sleep(long millis),一定是当前线程调用此方法,当前线程进入TIMED_WAITING状态,但不释放对象锁,millis后线程自动苏醒进入就绪状态。作用:给其它线程执行机会的最佳方式。

  • Thread.yield(),一定是当前线程调用此方法,当前线程放弃获取的CPU时间片,但不释放锁资源,由运行状态变为就绪状态,让OS再次选择线程。作用:让相同优先级的线程轮流执行,但并不保证一定会轮流执行。实际中无法保证yield()达到让步目的,因为让步的线程还有可能被线程调度程序再次选中。Thread.yield()不会导致阻塞。该方法与sleep()类似,只是不能由用户指定暂停多长时间。

  • Thread.join()/Thread.join(long millis),当前线程里调用其它线程t的join方法,当前线程进入WAITING/TIMED_WAITING状态,当前线程不会释放已经持有的对象锁。线程执行完毕或者millis时间到,当前线程进入就绪状态。

  • obj.wait(),当前线程调用对象的wait()方法,当前线程释放对象锁,进入等待队列。依靠notify()/notifyAll()唤醒或者wait(long timeout) timeout时间到自动唤醒。

  • obj.notify()唤醒在此对象监视器上等待的单个线程,选择是任意性的。notifyAll()唤醒在此对象监视器上等待的所有线程。

  • 调用Thread类的 interrupted() 方法,其本质只是设置该线程的中断标志,将中断标志设置为true,并根据线程状态决定是否抛出异常。因此,通过interrupted()方法真正实现线程的中断原理是 :开发人员根据中断标志的具体值来决定如何退出线程。

synchronized

  • 实际上,synchronized(this)以及非static的synchronized方法只能防止多个线程同时执行同一个对象的同步代码段,即synchronized(this)锁住的只是括号里的当前对象而不是代码,非static的synchronized方法所的也是对象本身this而不是代码。

  • (1)synchronized(Demo.class)锁住的是代码段,锁住了多个对象的同一方法,使用这种全局锁,锁的是类而不是this。

  • (2)static synchronized的类同步方法,由于static方法可以直接使用类名.方法名调用,方法中不能使用this对象,所以它锁的不是this,而是类的Class对象。故static synchronized的类同步方法也相当于全局锁,也相当于锁住了代码段。

死锁 同步的本质在于:一个线程等待另外一个线程执行完毕后才可以继续执行,但是若几个线程出现了相互等待的问题,那么就会造成死锁

Activity的生命周期

在这里插入图片描述

1.打开MainActivity 
(MainActivity)onCreat --> (MainActivity)onStart  --> (MainActivity)onResume

2.点击按钮跳转到SecondActivity
(MainActivity)onPause --> (SecondActivity)onCreate  --> (SecondActivity)onStart  --> (SecondActivity)onResume  --> (MainActivity)onStop

3.点击按钮返回到MainActivity
(SecondActivity)onPause --> (MainActivity)onRestart --> (MainActivity)onStart --> (MainActivity)onResume --> (SecondActivity)onStop --> (SecondActivity)onDestory

4.app 返回到后台
onPasue --> onStop

5.由后台返回到APP
onRestart --> onStart --> onResume

Activity中onSaveInstanceState()和onRestoreInstanceState()

1.onSaveInstanceState(Bundle outState)

  • onSaveInstanceState函数在Activity生命周期中执行。
  • outState 参数作用 : 数据保存 , Activity 声明周期结束的时候, 需要保存 Activity 状态的时候, 会将要保存的数据使用键值对的形式 保存在 Bundle 对象中;

调用时机 :

  • Activity 被销毁的时候调用, 也可能没有销毁就调用了;

  • 按下Home键 : Activity 进入了后台, 此时会调用该方法;

  • 按下电源键 : 屏幕关闭, Activity 进入后台;

  • 启动其它 Activity : Activity 被压入了任务栈的栈底;

  • 横竖屏切换 : 会销毁当前 Activity 并重新创建;

onSaveInstanceState方法调用注意事项

  • 用户主动销毁不会调用 : 当用户点击回退键 或者 调用了 finish() 方法, 不会调用该方法;
  • 调用时机不固定 : 该方法一定是在 onStop() 方法之前调用, 但是不确定是在 onPause() 方法之前 还是 之后调用;
  • 布局中组件状态存储 : 每个组件都 实现了 onSaveInstance() 方法, 在调用函数的时候, 会自动保存组件的状态, 注意, 只有有 id 的组件才会保存;
  • 关于默认的 super.onSaveInstanceState(outState) : 该默认的方法是实现 组件状态保存的;

2.onRestoreInstanceState(Bundle outState)

  • 方法回调时机 : 在 Activity 被系统销毁之后 恢复 Activity 时被调用, 只有销毁了之后重建的时候才调用, 如果内存充足, 系统没有销毁这个 Activity, 就不需要调用;
  • Bundle 对象传递 : 该方法保存的 Bundle 对象在 Activity 恢复的时候也会通过参数传递到 onCreate()方法中;

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

1.如果自己没有配置android:ConfigChanges,这时默认让系统处理,就会重建Activity,此时Activity的生命周期会走一遍。 在这里插入图片描述 2.如果设置android:configChanges="orientation|keyboardHidden|screenSize">,此时Activity的生命周期不会重走一遍,Activity不会重建,只会回调onConfigurationChanged方法。

显示隐式跳转activity

显示隐式跳转activity

显式启动 是明确指定了需要启动的Activity 或 service 的类名或包名。

隐式启动 不明确制定需要启动哪个Activity,而是通过设置action、data、 Category 等让系统来匹配出合适的目标

Android中activity,context,application有什么不同。

Content与application都继承与contextWrapper,contextWrapper继承于Context类。 Context:表示当前上下文对象,保存的是上下文中的参数和变量,它可以让更加方便访问到一些资源。 Context通常与activity的生命周期是一样的,application表示整个应用程序的对象。 Context的数量等于Activity的个数 + Service的个数 + 1,这个1为Application.

IntentService

  • 它本质是一种特殊的Service,继承自Service并且本身就是一个抽象类 它可以用于在后台执行耗时的异步任务,当任务完成后会自动停止
  • 它拥有较高的优先级,不易被系统杀死(继承自Service的缘故),因此比较适合执行一些高优先级的异步任务它内部通过HandlerThread和Handler实现异步操作
  • 创建IntentService时,只需实现onHandleIntent和构造方法,onHandleIntent为异步方法,可以执行耗时操作
  • 即使我们多次启动IntentService,但IntentService的实例只有一个,这跟传统的Service是一样的,最终IntentService会去调用onHandleIntent执行异步任务。
  • 当任务完成后,IntentService会自动停止,而不需要手动调用stopSelf()。另外,可以多次启动IntentService,每个耗时操作都会以工作队列的方式在IntentService中onHandlerIntent()回调方法中执行,并且每次只会执行一个工作线程。

AsyncTask

在android 3.0之前,AsyncTask处理任务时默认采用的是线程池里并行处理任务的方式,而在android 3.0之后 ,为了避免AsyncTask处理任务时所带来的并发错误,AsyncTask则采用了==单线程串行执行任务==。

AsyncTask的三个泛型参数说明

第一个参数:传入doInBackground()方法的参数类型   
第二个参数:传入onProgressUpdate()方法的参数类型 
第三个参数:传入onPostExecute()方法的参数类型,也是doInBackground()方法返回的类型
package com.zejian.handlerlooper;

import android.graphics.Bitmap;
import android.os.AsyncTask;

/**
 * Created by zejian
 * Time 16/9/4.
 * Description:
 */
public class DownLoadAsyncTask extends AsyncTask<String,Integer,Bitmap> {

    /**
     * onPreExecute是可以选择性覆写的方法
     * 在主线程中执行,在异步任务执行之前,该方法将会被调用
     * 一般用来在执行后台任务前对UI做一些标记和准备工作,
     * 如在界面上显示一个进度条。
     */
    @Override
    protected void onPreExecute() {
        super.onPreExecute();
    }

    /**
     * 抽象方法必须覆写,执行异步任务的方法
     * @param params
     * @return
     */
    @Override
    protected Bitmap doInBackground(String... params) {
        return null;
    }

    /**
     * onProgressUpdate是可以选择性覆写的方法
     * 在主线程中执行,当后台任务的执行进度发生改变时,
     * 当然我们必须在doInBackground方法中调用publishProgress()
     * 来设置进度变化的值
     * @param values
     */
    @Override
    protected void onProgressUpdate(Integer... values) {
        super.onProgressUpdate(values);
    }

    /**
     * onPostExecute是可以选择性覆写的方法
     * 在主线程中执行,在异步任务执行完成后,此方法会被调用
     * 一般用于更新UI或其他必须在主线程执行的操作,传递参数bitmap为
     * doInBackground方法中的返回值
     * @param bitmap
     */
    @Override
    protected void onPostExecute(Bitmap bitmap) {
        super.onPostExecute(bitmap);
    }

    /**
     * onCancelled是可以选择性覆写的方法
     * 在主线程中,当异步任务被取消时,该方法将被调用,
     * 要注意的是这个时onPostExecute将不会被执行
     */
    @Override
    protected void onCancelled() {
        super.onCancelled();
    }
}

HandlerThread

1、HandlerThread作用 当系统有多个耗时任务需要执行时,每个任务都会开启一个新线程去执行耗时任务,这样会导致系统多次创建和销毁线程,从而影响性能。为了解决这一问题,Google提供了HandlerThread,HandlerThread是在线程中创建一个Looper循环器,让Looper轮询消息队列,当有耗时任务进入队列时,则不需要开启新线程,在原有的线程中执行耗时任务即可,否则线程阻塞。

2、HanlderThread的优缺点

  • HandlerThread本质上是一个线程类,它继承了Thread;
  • HandlerThread有自己的内部Looper对象,可以进行looper循环;
  • 通过获取HandlerThread的looper对象传递给Handler对象,可以在handleMessage()方法中执行异步任务。
  • 创建HandlerThread后必须先调用HandlerThread.start()方法,Thread会先调用run方法,创建Looper对象。
  • HandlerThread优点是异步不会堵塞,减少对性能的消耗
  • HandlerThread缺点是不能同时继续进行多任务处理,需要等待进行处理,处理效率较低
  • ==HandlerThread与线程池不同,HandlerThread是一个串行队列,背后只有一个线程==。

ListView 如何提高其效率?

  1. convertView重用,利用好 convertView 来重用 View,切忌每次 getView() 都新建。ListView的核心原理就是重用 View,如果重用 view 不改变宽高,重用View可以减少重新分配缓存造成的内存频繁分配/回收;
  2. ViewHolder优化,使用ViewHolder的原因是findViewById方法耗时较大,如果控件个数过多,会严重影响性能,而使用ViewHolder主要是为了可以省去这个时间。通过setTag,getTag直接获取View。
  3. 减少Item View的布局层级,这是所有layout都必须遵循的,布局层级过深会直接导致View的测量与绘制浪费大量的时间。
  4. adapter中的getView方法尽量少使用逻辑
  5. 图片加载采用三级缓存,避免每次都要重新加载。
  6. 尝试开启硬件加速来使ListView的滑动更加流畅。
  7. 使用 RecycleView 代替。

Android中的ANR

在android中Activity的最长执行时间是5秒。
BroadcastReceiver的最长执行时间则是10秒。
Service的最长执行时间则是20秒。

超出执行时间就会产生ANR。注意:ANR是系统抛出的异常,程序是捕捉不了这个异常的。

解决方法:

  1. 运行在主线程里的任何方法都尽可能少做事情。特别是,Activity应该在它的关键生命周期方法(如onCreate()和onResume())里尽可能少的去做创建操作。(可以采用重新开启子线程的方式,然后使用Handler+Message 的方式做一些操作,比如更新主线程中的ui等)

  2. 应用程序应该避免在BroadcastReceiver里做耗时的操作或计算。但不再是在子线程里做这些任务(因为 BroadcastReceiver的生命周期短),替代的是,如果响应Intent广播需要执行一个耗时的动作的话,应用程序应该启动一个 Service。

如何切换 fragement,不重新实例化

翻看了 Android 官方 Doc,和一些组件的源代码,发现 replace()这个方法只是在上一个 Fragment不再需要时采用的简便方法.

正确的切换方式是 add(),切换时 hide(),add()另一个 Fragment;再次切换时,只需 hide()当前,show()另一个。

这样就能做到多个 Fragment 切换不重新实例化:

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

考虑到Intent还包括要启动的Activity等信息,实际可以传的数据略小于512K

Android中的动画有哪些

Android:这是一份全面 & 详细的动画入门学习指南 Android 逐帧动画:关于 逐帧动画 的使用都在这里了! Android:这是一份全面 & 详细的补间动画使用教程
Android 属性动画:这是一篇全面 & 详细的 属性动画 总结&攻略 逐帧动画(Frame Animation) 加载一系列Drawable资源来创建动画,简单来说就是播放一系列的图片来实现动画效果,可以自定义每张图片的持续时间 补间动画(Tween Animation) Tween可以对View对象实现一系列动画效果,比如平移,缩放,旋转,透明度等。但是它并不会改变View属性的值,只是改变了View的绘制的位置,比如,一个按钮在动画过后,不在原来的位置,但是触发点击事件的仍然是原来的坐标。 属性动画(Property Animation) 动画的对象除了传统的View对象,还可以是Object对象,动画结束后,Object对象的属性值被实实在在的改变了

为何大厂APP如微信、支付宝、淘宝、手Q等只适配了armeabi-v7a/armeabi?

Android目前支持以下7种ABIs:

mips, mips64, X86, X86–64, arm64-v8a, armeabi, armeabi-v7a

默认情况下,为了使APP有更好的兼容性,我们使用Android Studio 或者命令打包时,会默认支持所有的架构,但相应的APK size 会疯狂的增大。对于用户来说,目标设备只需要其中一个版本,但当用户下载APK时,会全部下载(对用户来说相当的不友好)。怎么办呢? abifilters 为我们提供了解决方案,abifilters为我们提供了选择适配指定CPU架构的能力,只需要在app下的build.gradle添加如下配置:

android {
        defaultConfig {
            ndk {
                abiFilters 'arm64-v8a', 'x86_64'
            }
        }
}

ListView和RecycleView的区别:

1)ListView布局单一,RecycleView可以根据LayoutManger有横向,瀑布和表格布局。 2)自定义适配器中,ListView的适配器继ArrayAdapter;RecycleView的适配器继承RecyclerAdapter,并将范类指定为子项对象类.ViewHolder(内部类)。 3)ListView优化需要自定义ViewHolder和判断convertView是否为null。 而RecyclerView是存在规定好的ViewHolder。 4)绑定事件的方式不同,ListView是在主方法中ListView对象的setOnItemClickListener方法;RecyclerView则是在子项具体的View中去注册事件。

多次调用异步任务

if(Build.VERSION.SDK_INT >= 11/*HONEYCOMB*/) {
            new searchAsyncTask(searchText)
                    .executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
        } else {
            new searchAsyncTask(searchText)
                    .execute();
        }

Android中子线程为什么不能更新UI

Android中子线程为什么不能更新UI

  1. ViewRootImpl是在Activity的onResume()方法后面创建出来的,所以在onResume之前的UI更新可以在子线程操作而不报错,因为这个时候ViewRootImpl还没有创建,没有执行checkThread()方法。
  2. 安卓系统中,操作viwe对象没有加锁,所以如果在子线程中更新UI,会出现多线程并发的问题,导致页面展示异常。

ConstraintLayout (约束布局)

约束布局ConstraintLayout看这一篇就够了 一文看懂ConstraintLayout的用法 ConstraintLayout,看完一篇真的就够了么? Constraintlayout 2.0:你们要的更新来了

xml中的android、app、tools你真的了解吗

xml中的android、app、tools你真的了解吗

xmlns:android="schemas.android.com/apk/res/and…"

  • xmlns是xml namespace的缩写,意思是xml命名空间。声明这个命名空间引用的是Android系统的.

xmlns:app="schemas.android.com/apk/res-aut…"

  • 在项目需求中,我们往往使用系统自带的属性以及控件是不够的,我们可能需要导入自定义控件的一些属性,或者support支持包之类的。
  • 现在的普遍做法是使用xmlns:app="http://schemas.android.com/apk/res-auto,因为res-auto可以引用所有的自定义包名。

xmlns:tools="schemas.android.com/tools"

  • tools可以告诉Android Studio,哪些属性在运行的时候是被忽略的,只在设计布局的时候有效。
  • tools可以覆盖android的所有标准属性,将android:换成tools:即可;同时在运行的时候就连tools:本身都是被忽略的,不会被带进apk中。

SharedPreferencesapply 和 commit 的区别

apply:异步执行,没有返回值; commit:同步执行,有返回值。 如果不考虑结果并且是在主线程执行推荐使用 apply; 需要确保操作成功且有后续操作的话,用 commit()

自定义view学习

自定义View学习 Android自定义View全解 自定义View,有这一篇就够了

函数作用相关方法
measure()测量View的宽高measure(),setMeasuredDimension(),onMeasure()
layout()计算当前View以及子View的位置layout(),onLayout(),setFrame()
draw()视图的绘制工作draw(),onDraw()
测量模式表示意思
UNSPECIFIED未指定模式,父容器没有对当前View有任何限制,当前View可以任意取尺寸
EXACTLY精确模式,对应于match_parent属性和具体的数值,父容器测量出View所需要的大小,也就是specSize的值。
AT_MOST最大模式,对应于wrap_comtent属性,只要尺寸不超过父控件允许的最大尺寸就行。

==而上面的测量模式跟我们的引用自定义View时在xml中设置wrap_content、match_parent以及写成固定的尺寸有什么对应关系呢?==

match_parent--->EXACTLY。怎么理解呢?match_parent就是要利用父布局给我们提供的所有剩余空间,而父View剩余空间是确定的,也就是这个测量模式的整数里面存放的尺寸。

wrap_content--->AT_MOST。怎么理解:就是我们想要将大小设置为包裹我们的view内容,那么尺寸大小就是父布局给我们作为参考的尺寸,只要不超过这个尺寸就可以啦,具体尺寸就根据我们的需求去设定。

固定尺寸(如100dp)--->EXACTLY。用户自己指定了尺寸大小,我们就不用再去干涉了,当然是以指定的大小为主啦。

 private int getMySize(int defaultSize, int measureSpec) {
        int mySize = defaultSize;

        int mode = MeasureSpec.getMode(measureSpec);
        int size = MeasureSpec.getSize(measureSpec);

        switch (mode) {
            case MeasureSpec.UNSPECIFIED: {//如果没有指定大小,就设置为默认大小
                mySize = defaultSize;
                break;
            }
            case MeasureSpec.AT_MOST: {//如果测量模式是最大取值为size
                //我们将大小取最大值,你也可以取其他值
                mySize = size;
                break;
            }
            case MeasureSpec.EXACTLY: {//如果是固定的大小,那就不要去改变它
                mySize = size;
                break;
            }
        }
        return mySize;
}

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        int width = getMySize(100, widthMeasureSpec);
        int height = getMySize(100, heightMeasureSpec);

        if (width < height) {
            height = width;
        } else {
            width = height;
        }

        setMeasuredDimension(width, height);
}

  • draw()步骤
public void draw(Canvas canvas) {
		...
		//步骤1:绘制View的背景
        drawBackground(canvas);
        ...
        //步骤2:如果需要的话,保存canvas的涂层,为fading做准备
        saveCount = canvas.getSaveCount();
        ...
        canvas.saveLayer(left,top,right, top + length, null, flags);
        ...
        //步骤3:绘制View的内容
        onDraw(canvas);      
        ...
        //步骤4:绘制View的子View
        dispatchDraw(canvas)
        ...
        //步骤5:如果需要的话,绘制View的fading边缘并恢复图层
        canvas.drawRect(left, top, right, top +length, p);
        ...
        canvas.restoreToCount(saveCount);
        ...
		//步骤6: 绘制View的装饰(例如滚动条)
		onDrawScrollBars(canvas)
		...

Android 代码动态布局 LayoutParams 使用

Android 代码动态布局 LayoutParams 使用 LayoutParams 的作用是:子控件告诉父控件,自己要如何布局。

inflate方法

public View inflate(int resource, ViewGroup root, boolean attachToRoot)
  • resource,要添加的 Layout
  • root,父布局
  • attachToRoot是否调用addview添加到父布局
  1. 如果 root 为 null,attachToRoot 将失去作用,设置任何值都没有意义。
  2. 如果 root 不为 null,attachToRoot 设为 true,则会给加载的布局文件的指定一个父布局,即 root。
  3. 如果 root 不为 null,attachToRoot 设为 false,则会将布局文件最外层的所有 layout 属性进行设置,当该 view 被添加到父 view 当中时,这些 layout属性会自动生效。
  4. 在不设置 attachToRoot 参数的情况下,如果 root 不为 null,attachToRoot 参数默认为 true。

layout_width 和 layout_height

它们其实是用于设置 View 在布局中的大小的,也就是说,首先 View 必须存在于一个布局中,layout_width 和 layout_height 才能生效。 平时在 Activity中指定布局文件的时候,最外层的那个布局是可以指定大小的呀,layout_width和 layout_height 都是有作用的。确实,这主要是因为,在 setContentView() 方法中,Android 会自动在布局文件的最外层再嵌套一个 FrameLayout,所以layout_width 和 layout_height 属性才会有效果。

如何自定义控件

  • 1.自定义属性的声明和获取
  • 2.分析需要的自定义属性
    在res/values/attrs.xml定义声明 在layout文件中进行使用 在View的构造方法中进行获取
  • 3.测量onMeasure 布局onLayout(ViewGroup)
  • 4.绘制onDraw
  • 5.处理点击事件

状态的恢复与保存

Android 布局优化之 include、merge、ViewStub

include标签 include 就是为了解决重复定义相同布局的问题。 include 使用注意

1.一个xml布局文件有多个include标签需要设置ID,才能找到相应子View的控件,否则只能找到第一个include的layout布局,以及该布局的控件 2.include标签如果使用layout xx属性,会覆盖被include的xml文件根节点对应的layoutxx属性,建议在include标签调用的布局设置好宽高位置,防止不必要的bug 3.include 添加id,会覆盖被include的xml文件根节点ID,这里建议include和被include覆盖的xml文件根节点设置同名的ID,不然有可能会报空指针异常 4.如果要在include标签下使用RelativeLayout,如layout_margin等其他属性,记得要同时设置layout_width和 layout_height,不然其它属性会没反应

merge 标签 merge标签主要用于辅助 include标签,在使用 include后可能导致布局嵌套过多,多余的 layout 节点或导致解析变慢(可通过 hierarchy viewer 工具查看布局的嵌套情况)。官方文档说明:merge 用于消除视图层次结构中的冗余视图,例如根布局是Linearlayout,那么我们又 include 一个 LinerLayout 布局就没意义了,反而会减慢 UI 加载速度。 merge 官方文档

merge 标签使用注意

1.因为merge标签井不是View,所以在通过LayoutInflate.inflate()方法渲染的时候,第二个参数必须指定一个父容器,且第三个 参数必须为true,也就是必须为merge下的视图指定一个父亲节点. 2.因为merge不是View,所以对merge标签设置的所有属性都是无效的. 3.注意如果include的layout用了merge,调用include的根布局也使用了merge标签,那么就失去布局的属性了 4. merge标签必须使用在根布局 5.ViewStub标签中的layout布局不能使用merge标签

ViewStub 标签 ViewStub 标签最大的优点是当你需要时才会加载,使用它并不会影响 UI 初始化时的性能.各种不常用的布局像进度条、显示错误消息等可以使用 ViewStub标签,以减少内存使用量,加快渲染速度.ViewStub 是一个不可见的,实际上是把宽高设置为 0 的 View.效果有点类似普通的 view.setVisible(),但性能体验提高不少

ViewStub 标签使用注意点

  1. ViewStub标签不支持merge标签
  2. ViewStub的inflate只能被调用一次,第二次调用会抛出异常,setVisibility可以被调用多次,但不建议这么做(ViewStub 调用过后,可能被GC掉,再调用setVisibility()会报异常)
  3. 为ViewStub赋值的android∶layout_XX属性会替换待加载布局文件的根节点对应的属性

Space 组件

在 ConstraintLayout 出来前,我们写布局都会使用到大量的 margin 或padding,但是这种方式可读性会很差,加一个布局嵌套又会损耗性能鉴于这种情况,我们可以使用 space,使用方式和 View一样,不过主要用来占位置,不会有任何显示效果

Android 广播

Android 广播

  1. 广播的分类

(1)按照发送的方式分类

  • 标准广播 是一种异步的方式来进行传播的,广播发出去之后,所有的广播接收者几乎是同一时间收到消息的。他们之间没有先后顺序可言,而且这种广播是没法被截断的。
  • 有序广播 是一种同步执行的广播,在广播发出去之后,同一时刻只有一个广播接收器可以收到消息。当广播中的逻辑执行完成后,广播才会继续传播。

(2)按照注册的方式分类

  • 动态注册广播 顾名思义,就是在代码中注册的。
  • 静态注册广播 动态注册要求程序必须在运行时才能进行,有一定的局限性,如果我们需要在程序还没启动的时候就可以接收到注册的广播,就需要静态注册了。主要是在AndroidManifest中进行注册。

(3)按照定义的方式分类

  • 系统广播 Android系统中内置了多个系统广播,每个系统广播都具有特定的intent-filter,其中主要包括具体的action,系统广播发出后,将被相应的BroadcastReceiver接收。系统广播在系统内部当特定事件发生时,由系统自动发出。
  • 自定义广播 由应用程序开发者自己定义的广播
  1. 动态广播实现

(1)实现一个广播接收器

public class MyBroadcastReceiver extends BroadcastReceiver {

    @Override
    public void onReceive(Context context, Intent intent) {
        Toast.makeText(context, "received in MyBroadcastReceiver", Toast.LENGTH_SHORT).show();
        abortBroadcast();
    }
}

(2)注册广播

public class MainActivity extends AppCompatActivity {

    private IntentFilter intentFilter;
    private MyBroadcastReceiver myBroadcastReceiver;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        intentFilter = new IntentFilter();
        intentFilter.addAction("android.net.conn.CONNECTIVITY_CHANGE");
        myBroadcastReceiver = new MyBroadcastReceiver();
        registerReceiver(myBroadcastReceiver, intentFilter);
        
        Button button = (Button) findViewById(R.id.button);
        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Intent intent = new Intent("android.net.conn.CONNECTIVITY_CHANGE");
                sendBroadcast(intent); // 发送广播
            }
        });
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        unregisterReceiver(myBroadcastReceiver);
    }
}

Android SQLite使用

Android SQLite使用

1.SQL SELECT 语句

SELECT 语句用于从表中选取数据。

从名为 "Persons" 的数据库表,获取名为 "LastName" 和 "FirstName" 的列的内容
SELECT LastName,FirstName FROM Persons

2. WHERE 子句

有条件地从表中选取数据,可将 WHERE 子句添加到 SELECT 语句。

SELECT 列名称 FROM 表名称 WHERE 列 运算符 值
从名为 "Persons" 的数据库表,选取居住在城市 "Beijing" 中的人
SELECT * FROM Persons WHERE City='Beijing'

3.AND 和 OR 运算符

选取所有姓为 "Carter" 并且名为 "Thomas" 的人:
SELECT * FROM Persons WHERE FirstName='Thomas' AND LastName='Carter'
选取所有姓为 "Carter" 或者名为 "Thomas" 的人:
SELECT * FROM Persons WHERE firstname='Thomas' OR lastname='Carter'

4.INSERT INTO 语句

INSERT INTO 语句用于向表格中插入新的行。

INSERT INTO 表名称 (列1, 列2,...) VALUES (值1, 值2,....)
INSERT INTO Persons (LastName, Address) VALUES ('Wilson', 'Champs-Elysees')

5.Update 语句 Update 语句用于修改表中的数据。

UPDATE 表名称 SET 列名称 = 新值 WHERE 列名称 = 某值
将列名称为LastName = 'Wilson',对应的Address修改为'Zhongshan 23',City修改为'Nanjing'
UPDATE Person SET Address = 'Zhongshan 23', City = 'Nanjing'
WHERE LastName = 'Wilson'

6.DELETE 语句

DELETE 语句用于删除表中的行。

DELETE FROM 表名称 WHERE 列名称 =

Hander

Handler一定要在主线程实例化吗?new Handler()和new Handler(Looper.getMainLooper())的区别

不带参数的实例化:Handler handler = new Handler();那么这个会默认用当前线程的looper;

一般而言,如果你的Handler是要来刷新操作UI的,那么就需要在主线程下跑。

  • 1.要刷新UI handler要用到主线程的looper。那么在主线程 Handler handler = new Handler();,如果在其他线程,也要满足这个功能的话,要Handler handler = new Handler(Looper.getMainLooper());
  • 2.不用刷新ui,只是处理消息。
  1. 当前线程如果是主线程的话,Handler handler = new Handler();
  2. 不是主线程的话Handler handler = new Handler(Looper.getMainLooper()); 或者手动调用Looper.prepare(); Handler handler = new Handler();Looper.loop();

在非主线程中直接new Handler() 会报错,原因是非主线程中默认没有创建Looper对象,需要先调用Looper.prepare()启用Looper。 主线程系统会自动为其创建Looper对象,开启消息循环。

Looper.prepare()和Looper.loop() —深入版

WebView

Android:这是一份全面 & 详细的Webview使用攻略 Android:你要的WebView与 JS 交互方式 都在这里了