Android 学习笔记

Android 学习笔记

  • 最全 Android 常用开源库总结!
  • Android Debug
  • Adb常用命令收录
  • Android studio 最全最好用的插件
  • Android shape属性大全
  • UML
  • Android改主题字体和字体大小
  • Android Live Template(实时模板)的使用
  • Android studio Debug
  • 设计模式
  • Gradle
  • AppTheme属性设置集合
  • Android中style和theme的区别
  • Android Fragment 非常详细的一篇
  • Fragment的replace、add、remove方法
  • Fragment在ViewPager中的生命周期
  • FragmentPagerAdapter与FragmentStatePagerAdapter区别
  • Fragment生命周期与Activity生命周期之间的关系
  • Android 设计模式
    • 关于Android架构,你是否还在生搬硬套
    • Android 设计模式—MVC
    • Android 设计模式—MVP
    • Android 设计模式—MVVM
      • 先说说MVVM是怎么解决了其他两个架构所在的缺陷和问题:
  • Android几种进程
  • Android开发基础之服务Service
  • 到底什么是Binder
  • 线程中sleep和wait的区别
  • View,ViewGroup事件分发
  • Touch事件传递机制
  • Intent传递数据和Bundle传递数据
  • Serializable 和Parcelable 的区别
    • Serializable 方式
    • Parcelable 方式
  • 解析Handler
  • Handler的原理
  • Hander
  • Activity的生命周期
  • Activity中onSaveInstanceState()和onRestoreInstanceState()
  • 横竖屏切换时Activity的生命周期变化?
  • 显示隐式跳转activity
  • Android中activity,context,application有什么不同。
  • IntentService
  • AsyncTask
  • HandlerThread
  • Android 多线程
  • ListView 如何提高其效率?
  • ListView和RecycleView的区别:
  • Android中的ANR
  • 如何切换 fragement,不重新实例化
  • Android中的动画有哪些
  • 为何大厂APP如微信、支付宝、淘宝、手Q等只适配了armeabi-v7a/armeabi?
  • 多次调用异步任务
  • Android中子线程为什么不能更新UI
  • ConstraintLayout (约束布局)
  • xml中的android、app、tools你真的了解吗
  • SharedPreferences apply 和 commit 的区别
  • 自定义view学习
  • Android 代码动态布局 LayoutParams 使用
  • inflate方法
  • layout_width 和 layout_height
  • 如何自定义控件
  • Android 布局优化之 include、merge、ViewStub
  • Android 优化之路(一)布局优化
  • Android 广播
  • Android SQLite使用
  • WebView
  • WebView对象复用池context的处理
  • Android IPC
  • Android AIDL
  • 反编译APP
  • SystemClock.sleep和Thread.sleep区别及使用场景
  • Android中tools:replace的使用
  • Android存储

最全 Android 常用开源库总结!

最全 Android 常用开源库总结!

Android Debug

Android程序调试
在 Android Studio 调试应用

Adb常用命令收录

Adb常用命令收录

Android studio 最全最好用的插件

热门Android Studio 插件,这里是Top 20

Android shape属性大全

Android shape属性大全

UML

Android 学习笔记_第1张图片
Android 学习笔记_第2张图片

UML学习链接

Android改主题字体和字体大小

Android 学习笔记_第3张图片

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

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

idea中live template的详细使用教程

Android studio Debug

Android Studio Debug 的 9 个小技巧

设计模式

设计模式

Gradle

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

AppTheme属性设置集合

AppTheme属性设置集合

Android中style和theme的区别

Android中style和theme的区别

Style和Theme的有哪些不同点和相同点

不同点:

  • Theme是应用于Activity或者是整个Application的,作用于单个Activity或者所有Acity,不能作用于某个控件的
  • Style是应用于某个(些)控件,Layout的,作用于控件级别的。
  • 两者总结一句就是:相对而言Theme是作用于全局的,而Style是作用于局部的。定义方式一样,使用的地方不一样。

相同点:

  • 都位于values文件夹下的style.xml中,定义的方法一样,都是控制UI的一堆属性。
    注意:当一个Activity中的控件应用了Theme中的样式又应用了Style中的样式,那么Style中的样式优先于Theme。

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()获取参数

getSupportFragmentManager().beginTransaction()
        .add(R.id.container, Fragment1.newInstance("hello world"), "f1")

// 第三个参数是fragment的tag名,指定tag的好处是后续我们可以通过
Fragment1 frag = getSupportFragmentManager().findFragmentByTag("f1")
// 从FragmentManager中查找Fragment对象。


addToBackStack("fname")是可选的。FragmentManager拥有回退栈(BackStack),类似于Activity的任务栈,如果添加了该语句,就把该事务加入回退栈,当用户点击返回按钮,会回退该事务(回退指的是如果事务是add(frag1),那么回退操作就是remove(frag1);如果没添加该语句,用户点击返回按钮会直接销毁Activity。

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,那么在按回退按钮的时候是不是一层一层的再拿出来呢?笔者这里告诉大家,根本不会,除非加入了回退栈。

Fragment在ViewPager中的生命周期

Fragment在ViewPager中的生命周期

FragmentPagerAdapter与FragmentStatePagerAdapter区别

使用FragmentPagerAdapter时 页面切换,只是调用detach,而不是remove,所以只执行onDestroyView,而不是onDestroy,不会摧毁Fragment实例,只会摧毁Fragment 的View;

使用FragmentStatePageAdapter时 页面切换,调用remove,执行onDestroy。直接摧毁Fragment。

FragmentPagerAdapter最好用在少数静态Fragments的场景,用户访问过的Fragment都会缓存在内存中,即使其视图层次不可见而被释放(onDestroyView) 。因为Fragment可能保存大量状态,因此这可能会导致使用大量内存。

页面很多时,可以考虑FragmentStatePagerAdapter

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

Android 学习笔记_第4张图片

  • 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

Android 学习笔记_第5张图片
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 学习笔记_第6张图片
Android 学习笔记_第7张图片

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

Android 学习笔记_第8张图片

  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

先说说MVVM是怎么解决了其他两个架构所在的缺陷和问题:

  • 解决了各个层级之间耦合度太高的问题,也就是更好的完成了解耦。MVP层中,Presenter还是会持有View的引用,但是在MVVM中,View和Model进行双向绑定,从而使viewModel基本只需要处理业务逻辑,无需关系界面相关的元素了。

  • 解决了代码量太多,或者模式化代码太多的问题。由于双向绑定,所以UI相关的代码就少了很多,这也是代码量少的关键。而这其中起到比较关键的组件就是DataBinding,使所有的UI变动都交给了被观察的数据模型。

  • 解决了可能会有的内存泄漏问题。MVVM架构组件中有一个组件是LiveData,它具有生命周期感知能力,可以感知到Activity等的生命周期,所以就可以在其关联的生命周期遭到销毁后自行清理,就大大减少了内存泄漏问题。

  • 解决了因为Activity停止而导致的View空指针问题。在MVVM中使用了LiveData,那么在需要更新View的时候,如果观察者的生命周期处于非活跃状态(如返回栈中的 Activity),则它不会接收任何 LiveData 事件。也就是他会保证在界面可见的时候才会进行响应,这样就解决了空指针问题。

  • 解决了生命周期管理问题。这主要得益于Lifecycle组件,它使得一些控件可以对生命周期进行观察,就能随时随地进行生命周期事件。

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

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

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

  • bindService demo
public class BindService extends Service {
    private static final String TAG = "BindService";

    public class CommunicateBinder extends Binder {
        void callInnerMethod(){
            innerMethod();
        }
    }

    private void innerMethod() {
        Log.d(TAG, "innerMethod was called...");
    }

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        Log.d(TAG, "onBind");
        return new CommunicateBinder();
    }

    @Override
    public boolean onUnbind(Intent intent) {
        Log.d(TAG, "onUnbind");
        return super.onUnbind(intent);
    }

    @Override
    public void onCreate() {
        Log.d(TAG, "onCreate");
        super.onCreate();

    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        Log.d(TAG, "onStartCommand");
        return super.onStartCommand(intent, flags, startId);
    }

    @Override
    public void onDestroy() {
        Log.d(TAG, "onDestroy");
        super.onDestroy();
    }
}
public class BindServiceActivity extends AppCompatActivity implements View.OnClickListener {
    private Button bt_bindService, bt_unbindService, bt_callSeviceMethod;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_bind_service);
        bt_bindService = findViewById(R.id.bindService);
        bt_bindService.setOnClickListener(this);
        bt_unbindService = findViewById(R.id.unbindService);
        bt_unbindService.setOnClickListener(this);
        bt_callSeviceMethod = findViewById(R.id.bt_callSeviceMethod);
        bt_callSeviceMethod.setOnClickListener(this);


    }

    @Override
    public void onClick(View view) {
        switch (view.getId()){
            case R.id.bindService:
                //创建意图对象
                Intent intent = new Intent(this, BindService.class);
                // 第一个是参数是意图对象,第二个参数是回调,第三个参数是标记,这个是自动创建的意,如果服务没有start,那么会自己创建。
                bindService(intent, mServiceConnection, BIND_AUTO_CREATE);
                break;
            case R.id.unbindService:
                //解绑服务
                if (mServiceConnection != null) {
                    unbindService(mServiceConnection);
                }
                break;
            case R.id.bt_callSeviceMethod:
            	//调用服务里的方法
                callServiceMethod();
                break;
            default:break;
        }
    }

    private BindService.CommunicateBinder mCommunicateBinder;
    private ServiceConnection  mServiceConnection = new ServiceConnection(){
//        当调用bindService方法后就会回调Activity的onServiceConnected,在这个方法中会向Activity中传递一个IBinder的实例,Acitity需要保存这个实例
        @Override
        public void onServiceConnected(ComponentName componentName, IBinder service) {
            if (service instanceof BindService.CommunicateBinder) {
                mCommunicateBinder = (BindService.CommunicateBinder) service;
            }
        }

        @Override
        public void onServiceDisconnected(ComponentName componentName) {
   		//这个方法只有出现异常时才会调用,服务正常退出不会调用。
        }
    };

到底什么是Binder

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

线程中sleep和wait的区别

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

View,ViewGroup事件分发

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

VeiwGroup事件的运行顺序:
VeiwGroup的dispatchTouchEvent -> VeiwGroup的onInterceptTouchEvent(需要拦截,只要return true) ->View的dispatchTouchEvent ->View的onTouchEvent
Android 学习笔记_第9张图片

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

子View可以通过调用getParent().requestDisallowInterceptTouchEvent(true); 阻止ViewGroup对其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,表示不消耗事件,交回上层处理。

Intent传递数据和Bundle传递数据

// 1.直接用intent.putExtra
Intent intent = new Intent(MainActivity.this,OtherActivity.class);
                  intent.putExtra("name", "Nicole");
                  intent.putExtra("age", 25);
                  intent.putExtra("address", "Shenzhen");
//2.用Bundle
Intent intent = new Intent(MainActivity.this,OtherActivity.class);
                  Bundle bundle = new Bundle();
                  bundle.putString("name", "Ben");
                  bundle.putInt("age", 28);
                  bundle.putString("address", "China");
                  intent.putExtras(bundle);     //将bundle传入intent中。

Intent传递数据和Bundle传递数据的区别?
Intent传递数据和Bundle传递数据是一回事,Intent传递时内部还是调用了Bundle。

	// Intent putExtra源码 
	
	
	public @NonNull Intent putExtra(String name, Bundle value) {
        if (mExtras == null) {
            mExtras = new Bundle();
        }
        mExtras.putBundle(name, value); //使用bundle添加参数
        return this;
    }
   
	public @NonNull Intent putExtra(String name, String value) {
	        if (mExtras == null) {
	            mExtras = new Bundle();
	        }
	        mExtras.putString(name, value); //使用bundle添加参数
	        return this;
	    }
    
	public @NonNull Intent putExtra(String name, int value) {
	        if (mExtras == null) {
	            mExtras = new Bundle();
	        }
	        mExtras.putInt(name, value); //使用bundle添加参数
	        return this;
	    }
	    
	public @NonNull Intent putExtra(String name, Parcelable value) {
	        if (mExtras == null) {
	            mExtras = new Bundle();
	        }
	        mExtras.putParcelable(name, value); //使用bundle添加参数
	        return this;
	    }
	    
	 public @NonNull Intent putExtra(String name, Serializable value) {
        if (mExtras == null) {
            mExtras = new Bundle();
        }
        mExtras.putSerializable(name, value); //使用bundle添加参数
        return this;
    }
	... 其他的类型

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对象。

Android 学习笔记_第10张图片
首先需要在主线程中创建一个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进行处理。从而实现线程之间的通信。

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() —深入版

Activity的生命周期

Android 学习笔记_第11张图片

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

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

3.点击按钮返回到MainActivitySecondActivity)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的生命周期会走一遍。
Android 学习笔记_第12张图片
2.如果设置android:configChanges=“orientation|keyboardHidden|screenSize”>,此时Activity的生命周期不会重走一遍,Activity不会重建,只会回调onConfigurationChanged方法。

显示隐式跳转activity

显示隐式跳转activity

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

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

解锁Activity的跳转新姿势———使用scheme跳转
浏览器为什么能唤起App的Activity?

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

Android进阶-Context
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是一个串行队列,背后只有一个线程

Android 多线程

Android多线程:线程池ThreadPool全面解析
Android多线程:手把手教你全面学习神秘的Synchronized关键字
Android多线程:带你了解神秘的线程变量 ThreadLocal

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 代替。

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中去注册事件。

Android中的ANR

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

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

解决方法:

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

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

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

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

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

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'
            }
        }
}

多次调用异步任务

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=“http://schemas.android.com/apk/res/android”

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

xmlns:app=“http://schemas.android.com/apk/res-auto”

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

xmlns:tools=“http://schemas.android.com/tools”

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

SharedPreferences apply 和 commit 的区别

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

自定义view学习

刘望舒-Android View体系
Android自定义View全解
自定义View,有这一篇就够了
Android 开发进阶: 自定义 View 1-1 绘制基础

函数 作用 相关方法
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);
}

Android中View测量之MeasureSpec
Android 学习笔记_第13张图片

  • 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.处理点击事件
  • 6.状态的恢复与保存

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 优化之路(一)布局优化

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 列名称 =

WebView

Android:这是一份全面 & 详细的Webview使用攻略
Android:你要的WebView与 JS 交互方式 都在这里了
Android:手把手教你构建 全面的WebView 缓存机制 & 资源加载方案
Android WebView 性能轻量优化
满满的WebView优化干货,让你的H5实现秒开

网易考拉移动团队-如何设计一个优雅健壮的Android WebView?(上)
网易考拉移动团队-如何设计一个优雅健壮的Android WebView?(下)
JsBridge 实现 JavaScript 和 Java 的互相调用
WebView与JavaScript交互——JsBridge

shouldOverrideUrlLoading返回值的处理及webview重定向造成无法返回的解决
Android WebView 优化梳理

类名 作用 常用方法
Webview 创建对象
生命周期管理
状态管理
●loadUrl (): 加载网页
●goBack: () :后退
WebSettings 配置&管理WebView ●与JS交互: setJavaScriptEnabled ()
●缓存: setCacheMode ()
WebViewClient 处理各种通知&请求事件 ●shouldOverrideUrlL oading (): 如果遇到了重定向,或者点击了页面中的a标签实现页面跳转,那么会回调这个方法
●shouldInterceptRequest(): 无论是普通的页面请求(使用GET/POST),还是页面中的异步请求,或者页面中的资源请求,都会回调这个方法,给开发一次拦截请求的机会。
●onPageStarted () :载入页面调用(如加载进度)
●onPageFinished () :页面加载结束时调用
WebChromeClient 辅助WebView处理Javascript对话框 ●onProgressChanged () :获得网页的加载进度&显示
●onReceivedTitle () :获取Web页中的标题
●onJsAlert () :支持javascript的警告框
●onJsConfrm () :支持javascript的确认框
onJsPrompt () :支持javascript输入框
类型 调用方式 优点 缺点 使用场景
Android调用JS loadUr () 方便简洁 效率低、获取返回值麻烦 不需要获取返回值,对性能要求较低时
evaluateJavascript () 效率高 向下兼容性差
(仅Android 4.4以上可用)
Android 4.4以上
JS调用Android 通过addJavascriptInterface ()进行添加对象映射 方便简洁 Android 4. 2以下存在漏洞问题 Android 4.2以上相对简单互调场景
通过WebViewClient.shouldOverrideUrlL oading ()回调拦截url 不存在漏洞问题 使用复杂:需要进行协议的约束;
从Native层往Web层传递值比较繁琐
不需要返回值情况下的互调场景
通过WebChromeClient的onJsAler(). onJsConfirm()、onJsPrompt () 方法回调拦截JS对话框消息 不存在漏洞问题 使用复杂:需要进行协议的约束; 能满足大多数情况下的互调场景

WebView对象复用池context的处理

MutableContextWrapper是Context家族中一员。其显著特点是可以动态替换baseContext来达到预加载需要Context创建的资源

腾讯祭出大招VasSonic,让你的H5页面首屏秒开
关于第四点,我们想分享一些Android平台上的细节,由于Android系统的生态原因,导致用户的系统版本和系统Webkit内核处于极其分裂状态,所以我们公司在手Q和微信统一使用X5内核。相对系统WebView来说,首次启动X5内核时,创建WebView比较耗时,因此我们尽量想复用WebView,但是WebView却是与Activity Context绑定。销毁复用的时候,需要释放Activity的Context,否则会内存泄露。针对这种情况,有没有一种两全其美的办法呢?

计算机有一句经典的名言:计算机领域任何一个问题都可以通过引入中间层来解决。于是我们通过包装的方式,实现了一个Context的壳,真正的实现体包装在里面,逻辑调用真正调用到对应的实现体的函数。 经过实验发现,Android系统本身提供了这么一个MutableContextWrapper,作为Context的一个中间层。

我们会将Activity context包在MutableContextWrapper里面,destory的时候,会将WebView的Context设置为Application的Context,从而释放Activity Context。
类似如下:


//precreate WebView
MutableContextWrapper contextWrapper = new MutableContextWrapper(BaseApplicationImpl.sApplication);
mPool[0] = new WebView(contextWrapper);

//reset WebView 
ct =(MutableContextWrapper)webview.getContext();
ct.setBaseContext(getApplication());

//reuse WebView
((MutableContextWrapper)webview.getContext()).setBaseContext(activityContext);

缓存机制 原理 优点 应用场景 备注
浏览器 根据HTTP协议头里的Cache-Control (或Expires)和 Last-Modified (或Etag)等字段来控制文件缓存的机制 支持Http协议层 存储静态文件(如JS、字体文件) Android 4.4后的WebView浏览器版本内核:Chrome 浏览器缓存机制是浏览器内核的机制,一般是标准实现,不能更改或设置
Application Cache 以文件为单位进行缓存,且文件有一定更新机制 (类似于浏览器缓存机制) 方便构建Web App的缓存 WebApp的商线缓存; 存储静态文件(如JS、字体文件) 对浏览器缓存机制的补充; (根据官方文档,AppCache已经不推荐使用)
Dom Storage 通过存储字符串的Key - Value对来提供 存储空间大(5MB); 存储安全、便捷:Dom Storage存储的数据在本地 存储临时、简单的数据 (Cookie的扩展) 代替将不需要让服务器知道的信息存储到cookies的这种传统方法; 类似于 Android 的 Shared Preference 机制
Web SQL Database 基于SQL 充分利用数据库的优势,可方便对数据进行増加、删 除、修改、查询 存储复杂、数据是大的结构化数据 Web SQL Database存储机制不再推荐使用(不再维护); (使用IndexedDB代替)
Indexed DB 通过存储字符串的Key - Value对来提供 (同Dom Storage存储机制) 功能强大、存储空间大、使用简单&灵活 (集合了 Dom Storage 和 Web SQL Database优点) 存储复杂、数据量大的结构化数据 取代Web SQL Database存储机制
File System API 提供一个虚拟的文件系统 (提供了临时&持久性的存糖空间) 可存储数据体积较大的二进制数据 可预加载资源文件 可直接编辑文件 通过文件系统管理数据 虚拟的文件系统是运行在沙盒中 不同WebApp的虚拟文件系统是互相隔离的,虚拟文件系统与本地文件系 统也是互相隔离的 Android WebView不支持

Android IPC

在Android中使用多进程只有一种方法,那就是给四大组件(Activity、Service、Receiver、ContentProvider)在AndroidManifest中指定android:process属性。

	<activity
		 android:name="com.ryg.chapter_2.MainActivity"
		 android:configChanges="orientation|screenSize"
		 android:label="@string/app_name"
		 android:launchMode="standard" >
		 <intent-filter>
			 <action android:name="android.intent.action.MAIN" />
			 <category android:name="android.intent.category.LAUNCHER" />
		 intent-filter>
	 activity
	 
		 android:name="com.ryg.chapter_2.SecondActivity"
		 android:configChanges="screenLayout"
		 android:label="@string/app_name"
		 android:process=":remote" />
	 <activity
		 android:name="com.ryg.chapter_2.ThirdActivity"
		 android:configChanges="screenLayout"
		 android:label="@string/app_name"
		 android:process="com.ryg.chapter_2.remote" />

上面的示例分别为SecondActivity和ThirdActivity指定了process属性,并且它们的属性值不同,这意味 着当前应用又增加了两个新进程。假设当前应用的包名为com.ryg.chapter_2,当SecondActivity启动时, 系统会为它创建一个单独的进程,进程名为com.ryg.chapter_2: remote;当ThirdActivity启动时,系统也会为它创建一个单独的进程,进程名为com.ryg.chapter_2.remote。同时入口Activity是MainActivity,没有 为它指定process属性,那么它运行在默认进程中,默认进程的进程名是包名。

用shell来查看,命令为:adb shell ps或者adb shell ps | grep com.ryg.chapter_2其中com.ryg.chapter_2是包名,如图2-2所示,通过ps命令也可以查看一个包名中当前所存在的进程信息。
通过ps命令来查看进程信息

SecondActivity 和 ThirdActivity 的 android:process 属 性 分 别 为:remotecom.ryg.chapter_2.remote,那么这两种方式有区别吗?其实是有区别的,区别有两方面: 首先,:的含义是指要在当前的进程名前面附加上当前的包名,这是一种简写的方法,对于SecondActivity来说,它完整的进程名为com.ryg.chapter_2:remote,这一点通过上图进程信息也能看出来,而对于ThirdActivity中的声明方式,它是一种完整的命名方式,不会附加包名信息;其次,进程名以:开头的进程属于当前应用的私有进程,其他应用的组件不可以和它跑在同一个进程中,而进程名不以:开头的进程属于全局进程。

我们知道Android为每一个应用分配 了一个独立的虚拟机,或者说为每个进程都分配一个独立的虚拟机,不同的虚拟机在内存分配上有不同的地址空间,这就导致在不同的虚拟机中访问同一个类的对象会产生多份副本。

一般来说,使用多进程会造成如下几方面的问题:

  • (1)静态成员和单例模式完全失效。
  • (2)线程同步机制完全失效。(既然都不是一块内存了,那么不管是锁对象还是锁全局类都无法保证线程同步,因为不同进程锁的不是同一个对象)
  • (3)SharedPreferences的可靠性下降。(因为SharedPreferences不支持两个进程同时去执行写操作,否则会导致一定几率的数据丢失,这是因 为SharedPreferences底层是通过读/写XML文件来实现的,并发写显然是可能出问题的,甚至并发读/写都 有可能出问题。)
  • (4)Application会多次创建。(因此,相当于系统又把这 个应用重新启动了一遍,既然重新启动了,那么自然会创建新的Application。这个问题其实可以这么理 解,运行在同一个进程中的组件是属于同一个虚拟机和同一个Application的,同理,运行在不同进程中的 组件是属于两个不同的虚拟机和Application的。)

为了更加清晰地展示Application会多次创建,下面我们来做一个测试,首先在Application的onCreate方法中打印出当前进程的名字,然后连续启动三个同一个应用内但属于不同进 程的Activity,按照期望,Application的onCreate应该执行三次并打印出三次进程名不同的log,代码如下所示。

public class MyApplication extends Application {
 private static final String TAG = "MyApplication";
 
 @Override
 public void onCreate() {
	 super.onCreate();
	 String processName = MyUtils.getProcessName(getApplicationContext(),
	 Process.myPid());
	 Log.d(TAG,"application start,process name:" + processName);
 	}
}

通过log可以看出,Application执行了三次onCreate,并且每次的进 程名称和进程id都不一样,它们的进程名和我们为Activity指定的android:process属性一致。这也就证实了 在多进程模式中,不同进程的组件的确会拥有独立的虚拟机、Application以及内存空间,这会给实际的开 发带来很多困扰,是尤其需要注意的。
在这里插入图片描述

名 称 优 点 缺 点 适用场景
Bundle 简单易用 只能传输Bundle支持的数据类型 四大组件间的进程间通信
文件共享 简单易用 不适合高并发场景,并且无法做到进程间的即时通信 无并发访问情形,交换简单的数据实时性不高的场景
AIDL 功能强大,支持一对多并发通信,支持实时通信 使用稍复杂,需要处理好线程同步 对多通信且有RPC需求
Messenger 功能一般,支持一对多串行通 信,支持实时通信 不能很好处理高并发情形,不支持RPC,数据通过Message进行传输,因此只能传输Bundle 支持的数据类型 低并发的一对多即时通信,无RPC需求,或者无须要返回结果的RPC需求
Content Provider 在数据源访问方面功能强大,支持一对多并发数据共享,可通过 Call方法扩展其他操作 可以理解为受约束的AIDL, 主要提供数据源的CRUD操作 一对多的进程间的数据共享
Socket 功能强大,可以通过网络传输字节流,支持一对多并发实时通信 实现细节稍微有点烦琐,不支持直接的RPC 网络数据交换

Android AIDL

Android AIDL使用详解
参考视屏: 安卓高级开发—架构师/1.3.2 Binder核心原理/1.3.2.3NetEase_Binder
Android AIDL与proxy,stub
Android WebView独立进程解决方案
哔哩哔哩-构建跨进程webview架构

反编译APP

Mac环境下反编译apk

SystemClock.sleep和Thread.sleep区别及使用场景

Thread.sleep()会抛出异常,而SystemClock.sleep()不会抛出异常 对于Android,推荐直接使用 SystemClock.sleep()即可 通过查看源码,发现 SystemClock.sleep() 其实调用的就是 Thread.sleep()方法 除了抛不抛异常,本质的区别是: SystemClock.sleep()不能被中断,无论如何都会让当前线程休眠指定的时间 而Thread.sleep()可以被中断,有可能在指定的休眠时间前被中断

Android中tools:replace的使用

Android中tools:replace的使用
当我们的项目的某些属性和第三方库中的属性有冲突时或者我们想修改第三方库中某些资源的属性时,我们就需要使用tools:replace来处理。

那么解决办法就是在你的Application节点中加入tools:replace来表示替换三方库中的相关属性,如下:

<application
        android:name=".MyApplication"
        android:allowBackup="true"
        android:icon="@drawable/box_icon"
        android:label="@string/app_name"
        android:theme="@style/AppTheme"
        tools:replace="android:icon, android:label">

Android存储

Android 学习笔记_第14张图片
看了这篇文章,终于搞懂了 Android 存储!

一篇文章搞懂android存储目录结构

彻底搞懂Android文件存储—内部存储,外部存储以及各种存储路径解惑

Android存储及getCacheDir()、getFilesDir()、getExternalFilesDir()、getExternalCacheDir()区别

Android-FileProvider-轻松掌握

你可能感兴趣的:(Android学习笔记,Android进阶,android)