读后总结--Android开发艺术探索

第一章 Activity的生命周期与启动模式

  • 从Activity A启动Activity B回调是先调用A的onPause() -> B的onCreate() -> B的onStart() -> B的onResume() -> A的onStop() 因此不能在onPause()中做耗时操作,不然新的Activity将延迟显示
  • onStart() 与 onResume()的区别是是否在前台(但是View是在onResume执行完才add到windowManager中,然后才绘制的)
  • onSaveInstance()调用时间在onStop()之前,跟onPause并无时序关系,注意onSaveInstance要调用一个参数的.
  • 当Activity被意外终止时(包括configuration发生变化以及Activity被意外杀死)系统会为我们保存一些View的状态与数据具体保存的内容在各个View的onSaveInstance()内,Activity的onCreate()内的bundle对象可能为空,onRestoreInstance里面的bundle对象一定不为空
  • 当Activity A(standard模式)被Activity B启动 则Activity A将被放入到Activity B所在的栈中,所以当使用Application.startActivity时就会报错提示需要加ACTIVITY_NEW_TASK flag,因为系统不知道把该Activity放入哪个栈中.
  • luncher应用程序在开启一个应用的时候会对intent设置ACTIVITY_NEW_TASK flag,会创建一个新栈。
  • singleTask 栈内复用模式.就是一个栈中只会存在一个该Activity实例并且具有清除top的效果,如当前栈中存在ABCD四个Activity再次启动B(singleTask)栈中会变成AB,CD被清除掉了,并且会调用B的onNewIntent(),如果该Activity的taskAffinity与启动它的Activity的不同则会新建一个栈并将其压入.
  • singleInstance 启动具有该启动模式的Activity系统会为它新建一个栈并将其压入(该栈中只会拥有该Activity一个实例),再次启动该Activity,不会再创建实例了,除非该栈已经销毁.
  • Flag FLAG_ACTIVITY_NEW_TASK 相当于singleTask,FLAG_ACTIVITY_SINGLE_TOP 相当于singleTop, FLAG_ACTIVITY_CLEAR_TOP 当栈中已经含有将要启动的Activity实例时,会先调用栈中该Activity的onNewIntent然后销毁该实例及其上面的Activity,然后再重新创建该实例。
  • 查看当前的所有任务栈

        adb shell dumpsys activity
  • 隐式启动的Activity必须加上默认的category,因为当调用startActivity()、startActivityForResult()时系统会验证是否含有默认的category,如果不加Activity就无法启动了(也就是说至少要有一个默认的category,入口Activity是特例不需要默认category),要启动一个组件,action必须是该组件配置的,intent里面的category必须是组件配置的子集.当指定data时如果只指定了mimetype而没指定URI则具有默认Scheme(content,file)注意android7.0及以上不能用file开头要用FileProvider

第二章 IPC机制

  • 每个android进程都含有一个虚拟机,而每个虚拟机加载同一个类对象时都会新创建class实例.进程一修改了A类中的一个静态值,但是进程二获取的还是原值,这就是由于两个进程的虚拟机各自拥有各自的class实例,进程一只更改了自己实例的值,对进程二无影响.
  • 开启多个进程会导致应用被开启多次,Application被创建多次(由于每次启动启动一个组件前系统都会调用makeApplication()如果该进程中没有application,则会创建一个application对象,然后调用application.create())
  • Android进程间通信方式有

    1.使用Intent传递数据
    2.共享文件和sp
    3.使用AIDL和messenger
    4.使用Socket
    5.使用Binder
  • 保存对象到文件中,首先该类必须实现序列化接口然后使用ObjectInputStream/ObjectOutputStream进行读取.
  • 实现了serializable接口的类最好指定private static final serialVersionUID,若不指定编译器就会根据当前类自动加上该字段,当类字段发生变化时就会反序列化失败,因为序列化时的UID与当前类的UID不同,手动指定就不会导致该问题.当然类结构比如变量类型发生变化反序列化也会失败.
  • 序列化的时候不会把静态变量和transient(作用就是不参与序列化)标记的成员变量序列化.因为静态变量属于类而不属于对象
  • 实现了parcelable接口的类writeToParcel和createFromParcel的读写顺序必须要一致,不然会出错,parcelable原理是使用共享内存,当使用内存序列化时使用Parcelable,其他情况使用Serialable,毕竟Serialable使用简单,但是开销大。
  • Binder对于framework来说是用于连接各种Manger(ActivityManager,WindowManager)和相应的ServiceManager(AMS,WMS)的桥梁,其只能传递实现了序列化接口的类,其跨进程传递的数据只能是parcelable以及基本数据类型。
  • AS使用aidl的坑https://www.cnblogs.com/rookiechen/p/5352053.html,要使用自定义的类必须让该类实现Parcelable接口并且创建同名.aidl文件声明自己,最后在主aidl中引用。除此之外除基本数据类型,其他类型类型都得标上方向:in、out、或者inout,并且aidl里面不能声明静态变量。
  • 系统为AIDL生成的类

    package com.hfw.androidtest;
    
    public interface IBookManager extends android.os.IInterface {
    
    public static abstract class Stub extends android.os.Binder implements com.hfw.androidtest.IBookManager {
        private static final java.lang.String DESCRIPTOR = "com.hfw.androidtest.IBookManager";
    
        /**
         * Construct the stub at attach it to the interface.
         */
        // 服务端先调用binder = new Stub(),然后实现接口定义的方法,
        // 服务端的binder对象就保存了一个<DESCRIPTOR,binder>
        // 该方法就是用来判断服务端和客户端是否是同一进程
        public Stub() {
            this.attachInterface(this, DESCRIPTOR);
        }
    
        /**
         * Cast an IBinder object into an com.hfw.androidtest.IBookManager interface,
         * generating a proxy if needed.
         */
        // 客户端调用通过bindService获取IBind对象,然后调用IBookManager.Stub.asInterface(binder),
        // 方法把binder对象转换为aidl接口的实现类,方法内部判断是否与客户端在同一进程(因为在同一
        // 进程根据DESCRIPTOR,一定可以找到binder对象,找不到就说明是不同进程因为是两个对象)如在同一进程因为
        // bindService返回的binder就是服务返回的对象,直接就能调用方法,如果不是同一进程,返回的只是
        // 一个BinderProxy对象其实现了transact方法通过native层挂起当前线程,等待服务端执行。
        public static com.hfw.androidtest.IBookManager asInterface(android.os.IBinder obj) {
            if ((obj == null)) {
                return null;
            }
            android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
            if (((iin != null) && (iin instanceof com.hfw.androidtest.IBookManager))) {
                return ((com.hfw.androidtest.IBookManager) iin);
            }
            return new com.hfw.androidtest.IBookManager.Stub.Proxy(obj);
        }
    
        @Override
        public android.os.IBinder asBinder() {
            return this;
        }
    
        @Override
        public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException {
            switch (code) {
                case INTERFACE_TRANSACTION: {
                    reply.writeString(DESCRIPTOR);
                    return true;
                }
                case TRANSACTION_getBookList: {
                    data.enforceInterface(DESCRIPTOR);
                    java.util.List<com.hfw.androidtest.Book> _result = this.getBookList();
                    reply.writeNoException();
                    reply.writeTypedList(_result);
                    return true;
                }
                case TRANSACTION_addBook: {
                    data.enforceInterface(DESCRIPTOR);
                    com.hfw.androidtest.Book _arg0;
                    if ((0 != data.readInt())) {
                        _arg0 = com.hfw.androidtest.Book.CREATOR.createFromParcel(data);
                    } else {
                        _arg0 = null;
                    }
                    this.addBook(_arg0);
                    reply.writeNoException();
                    return true;
                }
            }
            return super.onTransact(code, data, reply, flags);
        }
    
        private static class Proxy implements com.hfw.androidtest.IBookManager {
            private android.os.IBinder mRemote;
    
            Proxy(android.os.IBinder remote) {
                mRemote = remote;
            }
    
            @Override
            public android.os.IBinder asBinder() {
                return mRemote;
            }
    
            public java.lang.String getInterfaceDescriptor() {
                return DESCRIPTOR;
            }
    
            /**
             * Demonstrates some basic types that you can use as parameters
             * and return values in AIDL.
             */
            @Override
            public java.util.List<com.hfw.androidtest.Book> getBookList() throws android.os.RemoteException {
                android.os.Parcel _data = android.os.Parcel.obtain();
                android.os.Parcel _reply = android.os.Parcel.obtain();
                java.util.List<com.hfw.androidtest.Book> _result;
                try {
                    _data.writeInterfaceToken(DESCRIPTOR);
                    mRemote.transact(Stub.TRANSACTION_getBookList, _data, _reply, 0);
                    _reply.readException();
                    _result = _reply.createTypedArrayList(com.hfw.androidtest.Book.CREATOR);
                } finally {
                    _reply.recycle();
                    _data.recycle();
                }
                return _result;
            }
    
            @Override
            public void addBook(com.hfw.androidtest.Book book) throws android.os.RemoteException {
                android.os.Parcel _data = android.os.Parcel.obtain();
                android.os.Parcel _reply = android.os.Parcel.obtain();
                try {
                    _data.writeInterfaceToken(DESCRIPTOR);
                    if ((book != null)) {
                        _data.writeInt(1);
                        book.writeToParcel(_data, 0);
                    } else {
                        _data.writeInt(0);
                    }
                    mRemote.transact(Stub.TRANSACTION_addBook, _data, _reply, 0);
                    _reply.readException();
                } finally {
                    _reply.recycle();
                    _data.recycle();
                }
            }
        }
    
        static final int TRANSACTION_getBookList = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
        static final int TRANSACTION_addBook = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);
    }
    
    /**
     * Demonstrates some basic types that you can use as parameters
     * and return values in AIDL.
     */
    public java.util.List<com.hfw.androidtest.Book> getBookList() throws android.os.RemoteException;
    
    public void addBook(com.hfw.androidtest.Book book) throws android.os.RemoteException;
    }
    
  • android5.0 API21及以上要求bindService或者startService都得使用显示或者调用intent.setPackage(目标service包名),因为ComtextImpl中有这么一段代码。

        private void validateServiceIntent(Intent service) {
        if (service.getComponent() == null && service.getPackage() == null) { //不是显示调用并且packagename == null
            if (getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.LOLLIPOP) {
                IllegalArgumentException ex = new IllegalArgumentException(
                        "Service Intent must be explicit: " + service);
                throw ex;
            } else {
                Log.w(TAG, "Implicit intents with startService are not safe: " + service
                        + " " + Debug.getCallers(2, 3));
            }
        }
    }
  • Intent能传输Serialable、Parcelable以及基本数据类型
  • 观察者模式 首先服务端创建一个接口里面有方法A,每个客户端都实现该接口,客户端需要通过aidl调用服务端的注册方法,服务端当达成某一条件时调用所有注册的客户端A
  • 服务端程序验证客户端程序主要有两种方式一种是通过自定义权限,另一种是通过getCallingUid获取包名进行验证。

第三章 View的事件体系

  • TouchSlop常量当两次滑动距离小于该值时系统不认为滑动。可以通过以下代码获取

    ViewConfiguration.get(getContext()).getScaledTouchSlop()
  • VelocityTracker用于获取水平或垂直的速度典型的代码如下所示

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        VelocityTracker velocityTracker = VelocityTracker.obtain();
        velocityTracker.addMovement(event);
        velocityTracker.computeCurrentVelocity(1000);
        Log.d(TAG, "onTouchEvent: x="+velocityTracker.getXVelocity()+";y="+velocityTracker.getYVelocity());
        return super.onTouchEvent(event);
    }
    
  • 一共有三种方法可以实现View的滑动1.View的scrollTo()、scrollBy().2.给View添加平移动画.3.改变View的layoutParams

  • scrollTo()与scrollBy()这两个方法都是移动View内的内容比如调用Linearlayout.scrollTo()是将Linearlayout内部的内容移动,当调用TextView.scrollTo()时是将其上的文字移动view本身不移动,同时参数x为正时向左移动,y为正时向上移动。两者的区别时前者是相对于初始位置,连续调用同样参数无效,后者相对于当前位置可以重复执行(内部也是调用前者只是x,y每次都改变)。

  • 使用普通动画不会改变View的属性,只是用户看起来改变了而已,对于系统来说View的属性是没有改变的,并且如果不设置setFillAfter(true)在动画结束后又会回到原位置,因此button在动画结束的位置触发不了单击事件,而在初始位置可以触发。
  • 当改变了一个View的layoutParams时有两种方法可以生效,一种是再设置回去View.setLayoutParams,另一种是调用View.requestLayout().

  • Scroller 自定义View时用到一般代码结构如下,原理是startScroll以后会调用invalidate(),该方法会再调用view的onDraw()方法,onDraw()里面会再调用computeScroll()该方法在View内部是空方法。

    private void smoothScrollTo(int scrollX){//开启scroller进行滑动
            mScroller.startScroll(getScrollX(),getScrollY(),scrollX-getScrollX(),getScrollY());
            invalidate();
    }
    @Override
    public void computeScroll() {//重写View的该方法,里面根据时间判断是否结束,不结束就scrollTo
        super.computeScroll();
        if(mScroller.computeScrollOffset()){
            scrollTo(mScroller.getCurrX(),mScroller.getCurrY());
            postInvalidate();
        }
    }
  • 弹性滑动实现方式有Scroller、ObjectAnimate、属性动画配合Scroller以及延迟策略(滑动一下睡眠一下)

  • 只要一个View的clickable或longclickable有一个为true就会消耗点击事件,setClickListener会把clickable设置为true,setLongClickListener同理

第四章 View的工作原理

  • invalidate重新调用onDraw(),requestLayout()重新调用onMeasure()与onLayout()不调用onDraw()
  • ActivityThread通过ActivityManagerProxy与ActivityManagerService进行通信调用其的attachApplication()将当前application的ApplicationThread对象传递过去该对象是一个Binder服务端对象,这样AMS就能使用Binder调用该对象中的方法从而来启动Activity等等。

  • decorView是一个FrameLayout

  • ViewRoot的作用主要是与WMS通信绘制GUI和给decorView分配用户事件

  • MeasureSpec 分为以下三种

    1.UNSPECIFIED 容器不对View的大小做任何限制,子View要多大给多大,一般用于系统内部

    2.EXACTLY 确定值,父容器已经检测出当前View所需的大小,对应于layoutParams里面的match_parent以及确定值

    3.AT_MOST 父容器指定了一个大小,子View不能超过这个大小对应于layoutParams里面的wrap_content

  • onMeasure()后getMeasuredWidth(height)才被赋值,其值大部分情况下等于getWidth(height),最好是在onLayout中去获取最终高度。
  • View真正进行measure layout draw 是在Activity的onResume执行完后,因此onResume时布局并不可见。

  • Activity启动的时候就获取View的宽高,onCreate() onStart() onResume()里面获取不到

    1.在Activity/View onWindowFoucsChanged()里面获取

    2.View.post(runnable)

    3.为View的ViewTreeObserver添加addOnGlobalLayoutListener //最快

    4.View.measure()

  • 直接继承之View的自定义View需要注意解决warp_content情况因为如果不做处理warp_content将于match_parent效果一致,此外还必须处理padding(不需要处理margin,margin由父控件处理)处理方法就是在onDraw()里面考虑内边界。
  • 当View被从Window中移除时会调用View的onDetachedFromWindow()方法该方法内部可以做一些资源回收,停止线程的操作。

第五章 理解RemoteViews

  • RemoteViews用于notification中的自定义View以及桌面小部件,而其(RemoteViews)归根到底其实都是在SystemServer进程里面加载的,所以是跨进程的。
  • 创建桌面小部件的步骤

    1.新建一个布局文件比如a.xml

    2.然后在res里面新建文件夹xml,再在该文件夹下面新建一个布局比如b.xml里面代码如下所示

    <?xml version="1.0" encoding="utf-8"?>
    <appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
        android:initialLayout="@layout/a" 
        android:minHeight="84dp" android:minWidth="84dp"
        android:updatePeriodMillis="5000000"><!--自动更新时间-->
    </appwidget-provider>
    

    3.新建一个类继承自AppWidgetProvider,小组件的点击事件在onUpdate()里面进行设置,该方法会在每个小组件添加时就自动调用一次
    4.在Manifast文件中进行注册如下所示

    <receiver android:name=".MyAppWidget">
        <meta-data android:name="android.appwidget.provider"
            android:resource="@xml/app"/>
        <intent-filter>
            <action android:name="anniu" /> <!--下行必须加不然小组件将无法显示-->
            <action android:name="android.appwidget.action.APPWIDGET_UPDATE"/>
            <category android:name="android.intent.category.DEFAULT" />
        </intent-filter>
    </receiver>
    
  • PendingIntent构造时的flag待学习一般使用PendingIntent.FLAG_UPDATE_CURRENT
  • RemoteViews里面只能使用一些系统内置的控件,其子控件及自定义控件都无法使用,如果使用了logcat里面会报Inflate错误。
  • RemoteViews的原理,当我们调用RemoteViews的setXXX方法是系统都会创建一个Action,并将其放入一个ArrayList中进行保存,只有调用对应manager中的更新方法时才会调用RemoteViews里面的apply方法,该方法里面再一一执行action的apply方法。
  • 获取本应用中的资源文件可以通过getResource().getIdentifer(“布局文件名字”,”layout”,getPackageName())获取。

第六章android的Drawable

  • 在写selector文件时系统是根据从上到下的顺序来匹配drawable的所以必须把默认图案的drawable放到最下面,不然将一直显示默认图片!!

第七章 Android动画深入分析

  • Android的动画分为三种:View动画,帧动画,属性动画。View动画既可以是单种动画,也可以是四种动画组合。组合动画所对应的类是AnimationSet类,推荐在xml里面定义动画。
  • 帧动画在res/drawable目录下创建一个xml文件根节点为animation-list然后在代码中View.setBackgroundResource(),然后再View.getBackground().start();
  • LayoutAnimation的使用先在anim目录下新建一个以layoutAnimation为根节点的xml文件,其中有个属性animation需要依赖一个其他的animation,然后在布局文件中为ViewGroup设置layoutAnimation就行了(案例是给ListView设置,会出现刚刚载入布局时每个item有序的从右到左从完全透明到完全不透明的动画)
  • Activity的切换动画 使用overridePendingTransition()制定开启动画以及暂停动画,该方法必须紧挨startActivity()或finish()后面否则将不起作用,其中exitAnim的动画是给需要消失的Activity使用的,enterAnim是给将要显示的Activity设置的,finish时的动画需要重新创建。
  • 属性动画ObjectAnimator继承自ValueAnimator,AnimatorSet代表动画集合。ObjectAnimator用于对改变一个对象的属性并且赋予动画,ValueAnimator需要自己设置监听自己更改,前两个对象一般都通过xxxAnimator.ofXXX()获取,AnimatorSet一般使用playTogether(ValueAnimator…)或者playSequentially(),如果要对Color使用属性动画要设置

    setEvaluator(new ArgbEvaluator());
  • 给ValueAnimator设置AnimatorListener时如果不需要实现所有的回调应该使用AnimatorListenerAdapter
  • 使用ObjectAnimator要求对象必须要有setXXX的方法,因为属性动画的原理就是根据时间来生成一个值然后一个个通过反射调用setXXX方法设置回去,并且如果value可变参数只指定一个那么该对象就必须要有getXXX方法因为当属性动画开始时系统会通过反射调用该方法,如果找不到该方法,程序会直接崩溃。
  • 当属性动画显示出的效果不如预期,得先看看是否该对象的set和get方法在操作同一个属性,如果不是可以通过包装类进行实现。

八 理解window和windowManager

  • Activity内可以直接调用getWindowManager()
  • Activity其实也是通过调用WindowManager.addView()来添加的(在ActivityThread里面的handleResumeActivity())
  • 在系统界面上显示一个window

    private void showWindow() {
        //在系统界面上显示一个小图标
        Button bt_window = new Button(this);
        bt_window.setText("我是悬浮");
        WindowManager wm = (WindowManager) getSystemService(Service.WINDOW_SERVICE);
        WindowManager.LayoutParams params = new WindowManager.LayoutParams(-2,-2,0,0, PixelFormat.TRANSPARENT);
        params.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE |//表示自己不需要获取焦点,事件向下传递,同时拥有下面的flag特性
                WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL |//表示范围内的事件接受范围外不接受
                WindowManager.LayoutParams.FLAG_TOUCHABLE_WHEN_WAKING;//已经过时
        params.type = WindowManager.LayoutParams.TYPE_SYSTEM_ERROR;//type必须设置,并且该type需要加上权限 system_alert_window
        //6.0以后得跳转到设置界面
        if (wm != null) {
            wm.addView(bt_window,params);
        }
    }
    if (Build.VERSION.SDK_INT >= 23) {
            if (Settings.canDrawOverlays(this)) {
                showWindow();
            } else {
                //打开设置去授权悬浮框权限
                Intent intent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION);
                startActivityForResult(intent,1);
            }
        } else {
            showWindow();
        }
    }
    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        if(requestCode == 1){
            if(Build.VERSION.SDK_INT >= 23){
                if(Settings.canDrawOverlays(this)){
                    showWindow();
                }
            }
        }
    }
  • WindowManager主要有三个重要方法 addView、removeView、updateViewLayout其中addView通过IPC调用WMS的addView方法,removeView分为两个方法removeView、removeImmediate前者只是发了一个消息就返回了,不一定立马执行,后者直接调用建议使用前者,die()方法里面会调用dispatchDetachedFromWindow()里面会调用该View的onDetachedFromWindow()方法,然后通过IPC调用WMS的removeWindow方法。

  • PhoneWindow里面维护了一个DecorView对象,并且可以设置Callback(比如activity里面的dispatchTouchEvent()等等)
  • Toast对NMS发出请求,NMS在能弹出的时候通过IPC回调Tn里面的show()方法,show()方法运行在binder线程池中需要通过Handler转到原来的线程,所以Toast无法在没looper的线程中使用,因为Handler需要looper

九 四大组件的工作过程

  • 当程序中使用startActivity()、startService()、bindService()时会通过IPC调用AMS里面的对应的方法后经过一系列流程通过调用ApplicationThread里面的方法,最后调到H类里面对应的方法。
  • Service的启动过程,ComtextImpl.startService() -> 验证Intent内是否包含了包名 -> 调用AMS的startService() -> 经过一系列操作 给client发送一个message让ActivityThread创建Service对象,并且调用Service.onCreate(), -> client告诉AMS已经执行完毕 -> AMS给client发送消息调用Service.onStartCommand()
  • Service的bind过程,ComtextImpl.bindService() -> 验证Intent内是否包含了包名 -> 将ServiceConnection对象进行包装成Binder对象 -> 调用AMS的bindService() -> AMS给指定service所在的应用程序发送消息 -> client创建service对象调用onCrete() -> client告诉AMS已经执行完毕 -> AMS给client发送消息调用Service.onBind() -> client告诉AMS onBind完毕并且把onBind()返回的对象传递回来 -> AMS给client发送消息 -> AMS通过IPC调用ServiceConnect.connected().

十 Android的消息机制

  • Handler作用就是把当前线程切换到Handler里面的Looper所在的线程,比如在主线程创建Handler,在子线程获取数据再通过Handler切换到主线程执行,要使用Handler当前线程必须要先含有Looper.
  • LocalThread.get()可以取出当前线程中所set的值,在android消息机制中用于保存每个线程的Looper.Looper类就维护了一个LocalThread对象用于保存各个线程的Looper。
  • Looper.loop()死循环唯一跳出这个循环的条件是messageQuene.next()返回null(只有调用了Looper.quit()方法才能退出循环),当取出消息后,会进行dispatch然后再调用meassageQuene.next()再阻塞。
  • 当调用Handler.sendXXX时Handler内部就是把该message对象插入到消息队列中去,然后等待Looper去取消息。总的流程A线程Looper.prepare() -> A线程创建Handler -> A线程Looper.loop() -> B线程Handler.sendXXX ->
    A线程MessageQuene.next() -> A线程Handler.handlerMessage()。
  • 使用Handler时如果不想创建其子类的实例可以使用handler.post(Runnable),或者在创建Handler对象时传入一个Callback对象。

十一 Android的线程和线程池

  • IntentService 处理逻辑一般在onHandleIntent()中处理该方法是在子线程中运行的,当该方法运行完并且没有再次startService()才会自动销毁该service,多次startService()会多次调用onHandleIntent()方法。
  • AsyncTask的用法

    class MyUpdateTask extends AsyncTask<Void,Integer,Void>{
    
        @Override
        protected void onPreExecute() {
            super.onPreExecute();//主线程
        }
    
        @Override
        protected Void doInBackground(Void... voids) {
            for(int i=1;i<100;i++){
                SystemClock.sleep(100);//该方法运行与子线程
                publishProgress(i);//该方法会进行进程切换调用主线程的onProgressUpdate()
                if(isCanceled()){
                        break;//取消了把线程结束
                }
            }
            return null;//返回值作为onPostExecute()的参数
        }
    
        @Override
        protected void onPostExecute(Void result) {
            super.onPostExecute(result)//主线程
        }
    
        @Override
        protected void onProgressUpdate(Integer... values) {
            super.onProgressUpdate(values);
            pb.setProgress(values[0]);
        }
    }
  • AsyncTask的注意点
    1. AsuncTask对象必须在主线程中创建
    2. AsyncTask.execute也必须在主线程中创建
    3. AsyncTask只能调用execute方法一次,再次执行会报运行时异常,因为系统会在调用execute时检查当前的状态
    4. 不要在程序中显示的调用其4个重要的方法
    5. AsyncTask.execute()是串行执行的当一个任务执行完,才会执行下一个任务,如果需要并行执行可以调用AsyncTask.executeOnExecutor()

十二 Bitmap的加载和Cache

  • Bitmap加载前需要进行缩放,因为一般ImageView的大小都比图片的小,可以使用BitmapFactory.options里面的inSampleSize,当该参数<=1时不缩放,为2时宽高都为原来的1/2,占用空间变为原来的1/4。
  • 压缩加载Bitmap的步骤

    1. 将BitmapFactory.Options的inJustDecodeBounds设置为true去加载图片,该参数作用是不把图片加载进内存,但可以获取其宽高
    2. 从BitmapFactory.Options中取出图片的原始宽高信息对应于outWidth和outHeight.
    3. 根据ImageView大小以及原图大小计算出inSampleSize
    4. 将inJustDecodeBounds设置为false,然后重新加载图片
    public static Bitmap decodeSampledBitmapFromResource(Resources resources,int resId, int reqWidth, int reqHieght){
        BitmapFactory.Options options = new BitmapFactory.Options();
        options.inJustDecodeBounds = true;
        BitmapFactory.decodeResource(resources, resId, options); //为了获取原始图片的宽高
        options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHieght);
        options.inJustDecodeBounds = false;
        return BitmapFactory.decodeResource(resources, resId, options);
    }
    
    private static int calculateInSampleSize(BitmapFactory.Options options, int reqWidth, int reqHieght) {
        final int height = options.outHeight;
        final int width = options.outWidth;
        int inSampleSize = 1;
        if(height > reqHieght || width > reqWidth){
            final int halfHeight = height / 2;
            final int halfWidth = width / 2;
            while((halfHeight / inSampleSize) >= reqHieght && (halfWidth / inSampleSize) >= reqWidth){
                inSampleSize *= 2;
            }
        }
        return inSampleSize;
    }

十三 综合技术

  • 创建一个类实现UncaughtExceptionHandler实现其uncaughtException()方法,然后调用Thread.setDefaultUncaughtException()来捕获全局崩溃信息。
  • 一个dex文件的方法数量最多为65536个,当方法数量过多时会自动进行dex拆分。
    1. defaultConfig中加入multiDexEnabled = true
    2. dependencies中加入依赖compile ‘com.android.support:multidex:1.0.0’
    3. 应用的application使用MultiDexApplication或其子类。
  • 当执行一个同步方法时,会检查所有其他线程是否在执行同步方法(执行其他同步方法也视为有)如果有则等待。
  • 定义常量都用static final修饰

总结

  • 动态注册广播的返回值用于获取一些系统的参数,自定义的广播返回值为null.

    Intent intent = registerReceiver(null, new IntentFilter(Intent.ACTION_BATTERY_CHANGED));//从返回值获取结果
    registerReceiver(receiver,filter);//自定义返回值为null.
  • 本地广播LocalBroadcastManager,发出的广播只有本应用能够接收到,使用方法是把context.sendBroadcastReceiver()等的context改成LocalBroadcastManager.getInstance(Context context)代替,还有一个sticky广播,广播会一直保留下去,也就是说当你处理了一个该广播,当又注册了一个该广播又会被接收到。
  • 写在manifast里面的四大组件都是由PMS在应用安装时进行注册的,动态注册广播经过以下几步.

        context.registerReceiver(intent,filter);
        private Intent registerReceiverInternal(BroadcastReceiver receiver, int userId,   //调用registerReceiverInternal
            IntentFilter filter, String broadcastPermission,
            Handler scheduler, Context context) {
        IIntentReceiver rd = null;
        if (receiver != null) {
            if (mPackageInfo != null && context != null) {
                if (scheduler == null) {
                    scheduler = mMainThread.getHandler();
                }
                rd = mPackageInfo.getReceiverDispatcher(
                    receiver, context, scheduler,
                    mMainThread.getInstrumentation(), true);
            } else {
                if (scheduler == null) {
                    scheduler = mMainThread.getHandler();
                }
                rd = new LoadedApk.ReceiverDispatcher(
                        receiver, context, scheduler, null, true).getIIntentReceiver(); //因为注册是跨进程需要对Receiver进行包装.
            }
        }
        try {
            final Intent intent = ActivityManagerNative.getDefault().registerReceiver( //调用AMS注册广播
                    mMainThread.getApplicationThread(), mBasePackageName,
                    rd, filter, broadcastPermission, userId);
            if (intent != null) {
                intent.setExtrasClassLoader(getClassLoader());
                intent.prepareToEnterProcess();
            }
            return intent;
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
    }
    //然后AMS保存那个经过包装的Receiver对象和Intent Filter
    //客户端发送广播,AMS查询匹配,而后发送一个消息给H,最后执行Receiver.onReceiver()
  • 系统会在调用onResume()的时候判断当前Activity是否已经stop如果已经停止了会先调用onRestart()然后再调用onStart(),最后调用onResume()
  • Activity这个类实现了ContextWrapper类其实内部逻辑都是由ContextImpl实现的是典型的包装模式。
  • onStart()和onResume()的区别其实是Activity是否显示在前台,在ActivityTransitionState的onResume()中有这么一个注释,如果当前Activity还没有显示在栈顶,会等待一秒.

        // After orientation change, the onResume can come in before the top Activity has
        // left, so if the Activity is not top, wait a second for the top Activity to exit.
  • Activity的生命周期必须全部都调用super.xxx()因为Activity里面做了分配生命周期到fragment,给application发送消息(ActivityLifecycleCallbacks才会生效),如果不调用会抛出类似异常.

        throw new SuperNotCalledException(
                        "Activity " + r.intent.getComponent().toShortString() +
                        " did not call through to super.onCreate()");
  • Application可以注册registerActivityLifecycleCallbacks各个Activity调用生命周期都会回调,暂时不知道有什么用。
  • Java中的main方法执行时是不会去初始化该类中的其他非静态成员变量的,只有当new该类时才会去初始化非静态变量,非静态变量属于对象,当对象没有创建时是不会对其进行初始化的.当执行完一个构造方法后就创建了对象然后开始初始化该对象的成员变量。
  • 每个Activity都存在一个ActivityThread实例。
  • 注意在AMS里面的attachApplication方法里面通过IPC调用ApplicationThread.bindApplication()以及Application.launchApplication()并不能马上得到执行应该那时候主线程还只创建了handler并没有调用Looper.loop()所以AMS只是把消息发送到了主线程的消息队列中而已。
  • ActivityThread的handleBindApplication主要做了什么事情?
    1. 判断是否处于debug模式,如果是的话
      1. 通过IPC调用AMS的showWaitingForDebugger(参数二指明是show dialog,还是dismiss dialog,dialog对应的实现类是AppWaitingForDebuggerDialog,AMS只是发了一个消息就返回了所以是异步执行的)
      2. 调用Debug.waitForDebugger();里面就是去等待debug attach(通过死循环不断的等实现),只有当这个方法调用完了以后才可以进行断点调试,这也是ActivityThread.main()不能断点调试的原因。
      3. 通过IPC调用AMS的showWaitingForDebugger()去dismissDialog,所以说debug那个dialog是AMS弹出的而不是应用程序。
    2. 通过IPC调用ConnectivityService的getProxyForNetwork()获取到系统的网络代理设置,并且设置给client进程的Proxy类.
    3. 反射创建application对象,然后创建contextImpl对象,调用application.attach() -> application.onCreate()
  • Activity的attach做了些什么?

    1. 把ActivityThread创建的ContextImpl对象赋值给其成员变量mBase
    2. 创建了PhoneWindow对象 PhoneWindow mWindow = new PhoneWindow(this, window(null));
    3. mWindow.setWindowControllerCallback(this);
    4. mWindow.setCallback(this);最后这个callback又设置给其成员变量,这样PhoneWindow就持有了Activity的引用,就可以通过ViewRootImpl -> DecorView(PhoneWindow的内部类) -> 把点击事件等等传递给Activity
    5. mWindow.setOnWindowDismissedCallback(this);
    6. mWindow.getLayoutInflater().setPrivateFactory(this); mWindow的layoutInflate里面又保存了一份activity实例
    7. mWindow.setWindowManager(
      (WindowManager)context.getSystemService(Context.WINDOW_SERVICE),
      mToken, mComponent.flattenToString(),
      (info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0);
      给PhoneWindow设置windowManger
    8. WindowManager = mWindow.getWindowManager(); Activity也持有一份WindowManger
  • android的组件可以在清单文件中指定scheme然后HTML也就可以通过超链接跳转到该组件了。

  • fragmentPagerAdapter适用于页面较少的情况下,切换页面并不会释放fragement,fragmentStatePagerAdapter适用于页面较多的情况,切换页面会释放fragment引用。
  • service与Thread的区别,service运行与主线程与Thread没任何关系。
  • service.startCommand返回值用于表明服务被系统杀死后,当内存空闲时系统会做什么.
  • activity调用fragment,直接调用因为activity拥有fragment引用,fragment调用activity可以在fragment中声明一个接口activity去实现它,然后调用getActivity().xxx(),fragment之间进行通信可以使用getActivity().findFragmentById().xxx();
  • attach以后就调用了Activity.onCreate()然后调用该Activity里面所有的fragment的attach()然后再调用fragment.onCreate()
  • 跳转Activity应该使用intent.resolveActivity()或者packageManager.resolveActivity()进行检查。
  • Inflate规则
    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。
    5. 当一个View位于最外层的布局时,其layout_width与layout_height会失去作用,Activity布局文件不失去作用的原因的因为在布局文件外还有一层布局,因此当inflate一个布局时又不指定parentView该布局中的根布局的layout_width与layout_height将失去作用。
  • View的更新不一定要在主线程,而是要在创建ViewRootImpl对象的线程更新
  • WebView内存泄露,WebView应该动态添加到布局文件中去,并且参数Context要使用弱引用并且在Activity的onDestory()中要调用
    WebView.getParent().removeView(webview);WebView.removeAllViews();
    WebView.destroy();

  • android 系统启动过程先创建init进程,init进程执行init.rc然后执行frameworks/base/cmds/app_process/app_main.cpp的main()该方法内部创建了虚拟机并制定加载类,创建Zygote进程进入到com.android.internal.os.ZygoteInit.main()内部又通过fork()创建system_server(内部首先启动Binder线程池),然后自己进入死循环等待连接,当luncher应用程序启动其他应用时AMS会判断进程是否已经创建了,如果进程没创建AMS会发会组建一个消息发送给Zygote进程,然后Zygote进程fork自己并且将生成的进程号返回,client进程一创建就会运行ActivityThread.main(),然后进行启动全过程.

  • Activity生命周期全流程
    1. onCreate()
    2. onStart()
    3. onnRestoreInstanceState()如果activity被意外杀死或者config发生变化,bundle一定不为空,framework已经判断了
    4. onPostCreate()
    5. onResume()
    6. onPostResume()
  • View的绘制全过程
    1. 获取可用窗口的大小context.getResource().getConfiguration().compatScreenHeightDp()/compatScreenWidthDp()
  • 一个应用程序启动全过程
    1. ActivityThread.main()
    2. Looper.prepareMainLooper()
    3. new ActivityThread()实际上就是创建了H类
    4. attach(false) //attach为true是在SystemService中调用ActivityThread.systemMain()执行
    5. ActivityThread IPC调用 AMS.attachApplication()
    6. AMS IPC调用 ApplicationThread.bindApplication内部(保存到main线程的消息队列中并不能得到执行)
      1. 处理debug模式通知AMS去弹出一个等待debug的dialog
      2. 创建LoadApk对象
      3. 创建ApplicationContext对象
      4. 创建Application对象
      5. 将ApplicationContext与Application进行关联
      6. Application.attach(ApplicationContext)
      7. 调用Application.onCreate()
    7. ApplicationThread.scheduleLaunchActivity启动MainActivity
      1. 创建该Activity的ContextImpl对象
      2. 通过反射创建Activity对象
      3. 检测当前进程是否存在application对象(如果不存在会创建一个并且调用其onCreate())
      4. Activity.attach(创建phoneWindow对象并且设置了callback)
      5. Activity.setTheme()
      6. Activity.onCreate(判断调用两个参数的还是一个参数的,两个参数的内部其实也是调用一个参数的)
        1. 用户调用setContentView()
        2. 创建decorView对象并与phoneWindow关联
        3. 根据theme选取对应的布局文件
        4. 将布局文件inflate成View并且加入到android.R.id.content中
        5. 回调Activity.onContentChange()
      7. Activity.onStart(也要调用super(),调用内部fragment.onstart())
      8. Activity.onResume(同样调用fragment.onResume())
      9. WMS.addView(DecorView) 将decorView加到WMS中
        1. 创建ViewRootImpl对象
        2. ViewRootImpl.setView() 将decorView与ViewRootImpl相关联
          1. 与WMS进行通信
          2. 调用requestLayout()
          3. 调用performTraversals()进行View的三大流程
          4. 到此View正式显示出来

应用架构设计

  • MVC m -> json、sql等,v -> xml、java,c -> Activity、fragment 缺点Activity同时承担V和C层导致过于臃肿、m与v之前相互耦合
  • MVP 通过Presenter层使m、v两层分离

ClassLoader

  • java中的ClassLoader包含以下三种BootstrapClassLoader、 ExtensionsClassLoader和 AppClassLoader(后两种都是在Launch类中进行初始化的,并且两者都继承自URLClassLoader),它们各自都有加载的目录,其中BootstrapClassLoader是用C++代码写的java并不能访问,JVM需要加载一个类,会先查找AppClassLoader的缓存中是否含有该类若没有则去查找ExtensionsClassLoader最后一直到BootstrapClassLoader的缓存,若还找不到BootstrapClassLoader会加载其加载路径上的类若找不到会调用ExtensionsClassLoader去加载该类,若还找不到则调用AppClassLoader去加载如还找不到就报ClassNotFountException(双亲委托模式)
  • android中的ClassLoader包括BootClassLoader、PathClassLoader和DexClassLoader三个都是java类

猜你喜欢

转载自blog.csdn.net/qq_22194581/article/details/79915164