Android开发艺术探索学习笔记

4.2.1 MeasureSpec

MeasureSpec代表一个32位的值,高2位代表SpecMode,低30位代表SpecSize.
SpecMode:测量模式
SpecSize:在某种测量模式下的规格大小。

SpecMode的三种模式:
1.UNSPECIFIED:(父容器不对View有任何限制),要多大给多大,一般用于系统内部,表示一种测量状态。
2.EXACTLY:父容器已经测量出View的所需的(精确大小),这个时候View的最终大小为SpecSize所指定的值,对应于LayoutParams中的match_parent和具体两种模式。
3.AT_MOST:父容器指定可用大小即:SpecSize,View的大小不能大于这个值,具体是什么值要看不同View的表现,它对应于LayoutParams中的wrap_content.

4.3 View的绘制流程

1.measure测量
2.layout布局
3.draw绘制

    1.1 measure 测量
        1.在Activity/View#onWindowFocusChanged(代表View初始化完毕,宽/高已经准备好)可以获取View的宽高
        2.view.post(runnable);初始化完成
        3.ViewTreeObserver可以使用回调OnGlobalLayoutListener接口,当View树状态发生改变时onGlobalLayou会被调用多次
    2.2 layout 过程
    2.3 draw 过程
        1.background.draw(canvas)。绘制背景
        2.OnDraw(绘制自己)。
        3.绘制children(dispatchDraw)。
        4.(onDrawScrollBars)绘制装饰。

4.4 自定义View

1.自定义View的分类:
    1.继承View重写onDraw。需要自己支持wrap_content,并且padding也需要自己处理
    2.继承ViewGroup派生特殊Layout。主要用于自定义布局,即除了传统的系统布局,需要合适的处理ViewGroup的测量,布局的这两个过程,并同时处理子元素的测量和布局。
    3.继承特定的ViewGroup(比如TextView,ImageView)。不需要自己支持wrap_content,padding等。
    4.继承特定的ViewGroup(比如LinearLayout)。不需要自己处理ViewGroup的测量和布局两个过程。方法二更接近底层。

2.自定义View须知:
    1.让View支持wrap_content;
    如果不在onMeasure中做特殊处理,那么外界在使用wrap_content时无法达到预期效果,原因是因为onMeasure需要多次测量,可能导致测量不准确。
    2.如果有必要,让你的View支持padding。
    这事因为直接继承View的控件,如果不在draw方法处理padding,那么padding属性将不起作用。另外在ViewGroup的控件需要在onMeasure和onLayout中考虑padding和子元素的margin对其造成的影响,不然将导致padding和子类的margin失效。
    3.尽量不要在View中使用Handler,没必要。
    因为View本身提供post系列方法。
    4.View中如果有线程或者动画,需要及时停止,参考View#onDetachedFromWindow(在View被销毁时被调用)与之相对的方法onAttachedToWindow。
    5.VIew带有滑动嵌套时,需要处理好滑动冲突。

3.自定义View实例:
        1.自定义属性步骤:
            1.在values目录下创建自定义属性的XML。
        
            
                
                ......
            
        

        format属性值:reference 、color、boolean、dimension、float、integer、string、fraction、enum、flag

        2.在View的构造方法中解析自定义属性的值并做相应处理
                TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.CircleView);//回去style中的属性
                mColor=typedArray.getColor(R.styleable.CircleView_circle_color,Color.RED);
                typedArray.recycle();

        3.在XML里使用

5.1RemoteViews在通知栏上的应用(RemoteViews作用:在其他线程中显示并更新View界面,进程间通信,弊端:支持View的数量有限,不支持自定义View) 业务复杂程度决定使用RemoteViews还是AIDL

    自定义Notification:
        1.Notifivcation notification = new Notification().Builder(this);
           .setSmallIcon(R.mipmap.ic_launcher) // 设置状态栏中的小图片,尺寸一般建议在24×24, 这里也可以设置大图标
            .setTicker("有新短消息了!")// 设置显示的提示文字
            .setContentTitle("标题")// 设置显示的标题
            .setContentText("CONTEXT")// 消息的详细内容
            .setFullScreenIntent(pendingIntent, true)//设置为悬挂式
            .setContentIntent(pendingIntent) // 关联PendingIntent
            .setCategory(Notification.CATEGORY_MESSAGE)//设置类型
            .setPriority(Notification.PRIORITY_DEFAULT)//设置优先级
            .setColor(Color.GREEN)
            .setSubText("副标题")
            .setNumber(100) // 在TextView的右方显示的数字,可以在外部定义一个变量,点击累加setNumber(count),这时显示的和
            .getNotification(); // 需要注意build()是在API level16及之后增加的,在API11中可以使用getNotificatin()来代替
    notify.flags = Notification.FLAG_AUTO_CANCEL;

    RemoteViews:
    RemoteViews remoteViews = new RemoteViews(getPackageName(), R.layout.activity_nitification);
    remoteViews.setTextViewText(R.id.text, "TEXT");//定义Textview
    remoteViews.setImageViewResource(R.id.img, R.mipmap.ic_launcher);//定义ImageView
    remoteViews.setOnClickPendingIntent(R.id.LeftImg, createPend(MainActivity.class, 1));//设置监听
    PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, new Intent(this, MainActivity.class), PendingIntent.FLAG_UPDATE_CURRENT);
    notify.bigContentView = remoteViews;//大格式,无限制大小
    //notify.contentView = remoteViews;默认的大小
    notify.contentIntent = pendingIntent;
    managerNotifition.notify(0, notify);

//创建pendingintent方法
private PendingIntent createPend(Class classes,int requestCode){
    PendingIntent pend = null;
    Intent intent = new Intent(MainActivity.this,classes);
    intent.putExtra("code",""+requestCode);
    pend = PendingIntent.getActivity
            (MainActivity.this, requestCode, intent, PendingIntent.FLAG_CANCEL_CURRENT);
    return pend;
}

5.2 PendingIntent

1.PendingIntent不确定时间发生。Intent立即发生。
2. 要给RemoteViews设置单击事件,需要用到PendingIntent。
3.PendingIntent通过send和cancel方法来发送和取消
4.PendingIntent支持三种意图:
    4.1.getActivity();该意图发生时相当于是Context.startAcctivity(intent);
    4.2.getServer();
    4.3.getBroadcast();

5.PendingIntent的FLAG的理解:
    1.FLAG_ONE_SHOT:只能被调用一次,然后她就会自动被cancel,后续的通知单击后无法打开。

    2.FLAG_NO_CREATE:不会主动被创建,设计开发中无意义

    3.FLAG_CANCEL_CURRENT:当前描述的PendintInten存在,他们头会被cencel,然后系统创建新的PendingIntent,那些被cencel会无法打开。

    4.FLAG_UPDATE_CURRENT:PendingIntent已经存在,那么他们都会被更新,即他们的Intent中的Extras会被替换成最新的。(常用)

6.在NotificationManager.notifiid,notification);
如果id是常量,那么多次调用只能弹出一个通知,后续的通知会把前面的通知完全替换掉;
如果每次的id不同多次调用会弹出多个通知。   

5.3 RemoteViews的内部机制:

1.RemoteViews所支持布局的类型:Layout:LinearLayout,FrameLayout,RelativeLayout,GridLayout
                                View:AnalogClock,Button,Chronometer,ImageButton,ImageView,ProgressBar,
                                TextView,ViewFlipper,ListViiew,GridView,StackView,AdapterViewFlipper,ViewStub.

2.RemoteViews没有提供findViewBy方法,因此无法直接访问里面的View元素,而必须通过它本身的set方法来实现。

3.通知栏和桌面小布局部分分别由:NotificationManager和AppWidthManager管理,而NotificationManager和AppWidthManager通过Binder分别和SystemServer进程中的NotificationManagerService以及AppWidthSerVice进行通信,  这就构成了跨进程通信,他们在对应的Service中被加载。
然后会通过LayoutInflater去加载RemoteViews中的布局文件;
在SystemService进程中加载后的布局文件是一个普通的View,只不过相对于我们进程它是一个RemoteViews而已,
接着系统会对View执行一系列的界面更新任务,这些任务就是我们之前set方法提交的(set方法对View所做的更新并不是立即执行的,在RemoteViews内部会记录所有的更新操作,具体执行时机要等到RemoteViews加载以后才执行,这样RemotrViews在SystemServer进程显示了,这就是我们看到的通知栏消息和桌面小部件);
当需要更新RemoteViews时,我们需要调用一些列set方法,并通过NotificationManager和AppWidthManager来提交更新任务,
具体的操作也是在SystemServer进程中完成。

6. View动画

    6.1:动画种类:android3.0之前 TweenAnimation(补间动画) FrameAnimation(帧动画) android3.0之后增加:Property Animation(属性动画),AnimationSet:一系列动画集合

    AnimationUtils:动画工具类,loadAnimation(从XML代码里加载动画);
    可以在XML里写(),也可以在代码里写;
    AnimationListener:动画监听。

    6.2 TweenAnimation(补间动画):透明度,旋转,位移,缩放动画。

    6.3 自定义动画继承自Animation


    2.将上述Drawable作为View的背景并通过Drawable来播放动画

            3. AnimationDrawable drawable = (AnimationDrawable) View.getBackground();

            drawable.start();

    6.5 LayoutAnimation :作用于ViewGroup指定一个动画,经常用于ListView

1.在res/anim/layout_animation ->android:delay(延迟)

android:animationOrder: normal:顺序 reverse:倒序 random随机
设置一个
android:animation:设置想要的item动画

2.给ListView设置layoutAnimation代码里设置可以用:LayoutAnimationController来实现

    6.6 nineoldandroids:动画第三方框架收

    6.7 AnimationUpdateListenter:监听动画整个过程

    6.8 对任意属性做动画处理:
            1.用一个包装类来做处理,间接提供get,set方法
            2.属性动画工作原理

7.理解Window和WindowManager:

Window的Flags:
FLAG_NOT_FOCUSABLE:表示不需要获取焦点,也不需要接收输入事件,同事会开启FLAG_NOT_TOUCT_MODAL,最终会把事件传递给下层具有焦点的window.

FLAG_NOT_TOUCH_MODAL:系统会将Windows当前以外的单击事件传递给底层的windows,当前的自己处理

FLAG_SHOW_WHEN_LOCKED:让widows显示在锁屏的界面上

Type参数表示Widows的类型,Windows有三种类型,分别是:1,应用的windows:对应着一个Activity

2,子windows:子view不能单独存在,他需要在附属在特定的windows之中,如:dialog就是在子windows

3,系统windows:需要声明权限才能创建widows,比如toast和系统状态栏这些都是系统windows

windows是分层的,层级大的会覆盖在层级晓得上面。
WindowsManager所提供的功能简单,即添加View,更新view,删除view,这三个定义在ViewManager中,而WindowsManager继承了ViewManager-->addView(...),updateViewLayout(...),removeView(...)

Windows内部机制:Widnows是一个抽象的概念,每一个window都对相应着一个View和ViewRootImpl,Window通过ViewRootImpl来建立联系,因此Windows并不是实际存在的,它是以View的形式存在。

10.Android消息机制:

1、首先Looper.prepare()在本线程中保存一个Looper实例,然后该实例中保存一个MessageQueue对象;因为Looper.prepare()在一个线程中只能调用一次,所以MessageQueue在一个线程中只会存在一个。

2、Looper.loop()会让当前线程进入一个无限循环,不端从MessageQueue的实例中读取消息,然后回调msg.target.dispatchMessage(msg)方法。

3、Handler的构造方法,会首先得到当前线程中保存的Looper实例,进而与Looper实例中的MessageQueue想关联。

4、Handler的sendMessage方法,会给msg的target赋值为handler自身,然后加入MessageQueue中。

5、在构造Handler实例时,我们会重写handleMessage方法,也就是msg.target.dispatchMessage(msg)最终调用的方法。

好了,总结完成,大家可能还会问,那么在Activity中,我们并没有显示的调用Looper.prepare()和Looper.loop()方法,为啥Handler可以成功创建呢,这是因为在Activity的启动代码中,已经在当前UI线程调用了Looper.prepare()和Looper.loop()方法。

11.Android线程与线程池:

1.
HandlerThread使用场景:
假设我们要做一个股市数据实时更新的app,我们可以在网上找个第三方的股市数据接口,然后在我们的app中每隔1分钟(合适的时间)去更新数据,然后更新我们的UI即可。

HandlerThread:实际上就一个Thread,只不过它比普通的Thread多了一个Looper。
创建HandlerThread时要把它启动了,即调用start()方法。然后创建Handler时将HandlerThread中的looper对象传入。
HandlerThread thread = new HandlerThread("MyHandlerThread");
thread.start();
mHandler = new Handler(thread.getLooper());
mHandler.post(new Runnable(){...});
那么这个Handler对象就是与HandlerThread这个线程绑定了(这时就不再是与UI线程绑定了,这样它处理耗时操作将不会阻塞UI)。


2.IntentService的使用场景:
    在Android开发中,我们或许会碰到这么一种业务需求,一项任务分成几个子任务,子任务按顺序先后执行,子任务全部执行完后,这项任务才算成功。那么,利用几个子线程顺序执行是可以达到这个目的的,但是每个线程必须去手动控制,而且得在一个子线程执行完后,再开启另一个子线程。

    在IntentService内有一个工作线程来处理耗时操作,启动IntentService的方式和启动传统Service一样,同时,当任务执行完后,IntentService会自动停止,而不需要我们去手动控制。另外,可以启动IntentService多次,而每一个耗时操作会以工作队列的方式在IntentService的onHandleIntent回调方法中执行,并且,每次只会执行一个工作线程,执行完第一个再执行第二个,以此类推。
    所有请求都在一个单线程中,不会阻塞应用程序的主线程(UI Thread),同一时间只处理一个请求。

    用IntentService有什么好处呢?
    首先,我们省去了在Service中手动开线程的麻烦,
    第二,当操作完成时,我们不用手动停止Service。


3.Android中的线程池:
    使用线程池的好处?
    (1)重用线程池中的线程池,避免线程池的创建和销毁所带来性能的开销。
    (2)能有效的控制线程池的最大并发数,避免线程之间相互抢占而阻塞。
    (3)能够对线程简单的管理,并提供定时执行以及指定间隔循环执行等功能。


    1.newFixedThreadPool线程固定的线程池,在空闲时并不会被回收,除非线程关闭。
    ExecutorService executorService = Executors.newFixedThreadPool(5);
    executorService.execute(runnable);

    2.newCachedThreadPool线程数量不固定,只有非核心线程池,最大线程数为Integer.MAX_VALUE;
    ExecutorService executorService1 = Executors.newCachedThreadPool();
    executorService1.execute(runnable);

    3.newScheduledThreadPool核心线程数固定,非核心线程没有限制,并且当非核心线程闲置时会被立即回收。
    ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(5);
    //延迟2000后执行runnable
    scheduledExecutorService.schedule(runnable,2000, TimeUnit.MILLISECONDS);
    //延迟10后,每隔1000执行一次runnable
    scheduledExecutorService.scheduleAtFixedRate(runnable,10,1000,TimeUnit.MILLISECONDS);

    4.newSingleThreadExecutor内部只有一个核心线程,确保所有任务都在一个线程中按顺序执行,不需要处理线程同步问题。
    ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor();
    singleThreadExecutor.execute(runnable);

你可能感兴趣的:(Android)