Android包工头

android架构师

本文章属于Android进阶教程,参考的资料来源于台湾Android架构师高焕堂视频,通过本文章我们可以深入了解android的一些底层原理,尤其是线程,进程之间的通信,UI架构等深入的技术点,如果你有较深的Java功底,阅读本文应该是很容易的
本文章按照模块来排序,首先介绍android应用框架里面的一些东西EIT模型

EIT模型

什么是EIT模型,有什么用呢?
这个模式大概是说android里面的类和父类以及接口调用的东西,执行一个子类一定是先从其父类开始执行,最后执行到子类,android里面主要的代码都是使用java语言进行编写,java是一门比较成熟的语言,封装了很多方法,比如线程,线程池,消息队列,集合,泛型等,

说说EIT,这是一种设计模式。就像是引擎,接口,轮胎
为什么这么比喻,我们知道汽车引擎(一个对象),接口(也是一个对象,除此之外还可以表示一种规范,协议,用来衔接两个或者多个不同规格,不同形状,不同类别的对象,使其能够运转起来,也可以看成不同类别对象之间的通信),轮胎是另一个对象,毫无疑问引擎很难直接去驱动轮胎,需要使用接口来实现?
## 接口如何让引擎和轮胎工作起来 ##
首先我们让引擎实现一个接口(也是一种继承),我们都知道实现一个接口就必须实现其中的全部方法,这样看起来就像是将接口和引擎组合起来了,接口中的方法就是怎么驱动轮胎,因为接口中的抽象方法已经定义好了,在接口方法里可以将轮胎作为一个参数传入,这样引擎就可以在准备驱动的时候去调用轮胎这个对象。
当然实现这种功能也可以通过其他方式,比如在引擎里面专门提供一个方法将轮胎作为参数传递进来,然后操作轮胎,但是这样会存在一个问题,

什么问题

我们知道引擎可以驱动的东西(对象)太多了,轮胎,转轴,皮带等,如果下一次我们要去驱动别的对象,难道我们再在引擎里再写一个驱动皮带的对象吗,如果驱动的更多,引擎这个对象就会越来越臃肿,在对引擎进行升级改造的时候,继承原来的引擎,但是新的引擎准备抛弃原来的某些方法,怎么办

android通信机制

Android进程通信
Android 中一个APP一般在一个进程process里面(当然也可以在mainfest.xml文件中指定一个服务或者其他组件在另一个进程里面) activity,service ,broadcast都是在主线程即UI线程运行
android不同进程之间通信可以使用binder来实现,在binder的内部并不是直接和另一个进程联系,而是通过底层Linux驱动层,C/C++层来转换完成(进程A通过binder连接到C/C++层代码,再连接到底层Linux驱动层,底层在传递到另个进程B的底层代码,然后传递给进程B的上层,其中binder是双方进程通信的协议也是接口,在接口里规定了如何发送消息,如何接受消息,之所以两个进程不能直接通信,我想大概是由于不同进程所占的内存区域不同,Java不方便直接操作指针,而C/C++则可以实现访问底层硬件)
在android中activity和service之间通信也可以采用binder来实现(实现binder接口)不过这种情况一般属于进程内通信,甚至是线程内通信
Android线程
一UI线程只能在主线程执行,是为了避免线程安全的问题(原则上通过线程锁可以解决多线程安全的问题,多线程会加快读取速度,但是UI线程时直接跟用户交互的对象,需要最高的响应速度,采用加锁机制会影响性能),所以Android权衡速度和安全,找到一个平衡点
二 但是在android的某些应用场景中某些视图组件需要长时间的耗时,迅速更新UI画面(view的绘图变化很快),或者多线程访问。Android提供了surfaceview 预览视图 可以有背景线程去访问比如在播放动画,视频的时候需要画面及时更新,UI线程由于不能再view视图改变上耗费太多的时间(UI线程除了处理一般的view还要处理诸如点击,触摸监听等事情)
三 android提供一个surfaceview 开发者可以在主线程创建,通过子线程去执行其中的方法,关于线程安全的问题需要自己去解决
下面提供几个解决方法:
1 禁止其他线程进入,维持单线程模式
2 允许多个线程进入,但是不共用变量
3 使用同步锁来解决多线程安全问题
Android通信机制
对象之间的简单通信可以使用接口的形式来实现
线程之间的通信:UI线程和子线程
背景介绍:android中activity,service,广播都是运行在主线程
除此之外UI线程还负责处理与用户交互,任务很重,所以一些耗时的操作不要放在UI线程里,主线程之所以能够不断的响应用户事件(在java中一个线程里的代码执行完毕后,该线程就会被系统回收,为了达到在任何时候用户都能和UI线程交互,Android必须让UI线程不停的运行,所以设计了looper 这一结构,保证UI线程不被回收)如果想在主线程中创建的新线程也可以循环执行,就必须为该线程创建线程循环机制,也就是借助looper,UI线程默认已经创建了loop ,在UI线程中用户的点击事件,生命周期的执行都是生成一个message 然后将消息放进messagequeue里面,主线程在执行loop回 的时候会从消息队列里面取出message去执行
Android中线程之间传递消息是借助handler ,handler可以发送一个消息,也可以从消息池里取出一个消息去处理;
子线程—》UI线程:在UI线程创建handler对象,在子线程使用此handler发送消息,在UI线程使用此handler处理消息,这样子线程的message就被丢到主线程的messagequeen里面
UI线程—》子线程:由于发送消息的时间不确定,有可能在UI线程还没有向子线程发送消息的时候,子线程已经执行完毕被回收了
所以要让子线程一直等待UI线程发送过来的消息,在子线程中建立loop机制使其一直执行,接着在子线程创建handler对象,在主线程中使用该handler对象,发送消息,在子线程中使用该handler处理消息,这样UI线程发送的消息就被丢到子线程的messagequeue里面
如果在同一个线程内部,要实现通信业可以使用MQ和handler,就是在同一个线程里使用handler发送消息并处理消息(这种方式要求线程实现loop循环,线程将消息丢到自己的消息队列里面)
Handler延迟发送的意思是:过多少毫秒之后再发送消息,而不是把信丢到指定邮箱,然后延迟处理
Android游戏循环
贴图原理
游戏的本质就是循环贴图,而不是通过代码布局来进行绘制(比如通过布局layout来实现)在view类的ondraw()方法里进行换图
游戏的基本动作:不断的绘图,不断的刷新
Ondraw()函数执行绘图,将图画在画板上canvas,invalid()函数执行刷新,也就是重新执行一遍ondraw()函数
Ondraw和invalid如何工作:
View工作的时候有两个缓冲区buffer 画布和投射,已经显示在view上面的图像数据来源于投射缓冲区,这个时候如果重新绘图,执行ondraw()会将新的图像数据写入画布缓冲区,当执行invalid进行刷新操作时,view会将投射缓冲区的数据标示为过期数据,然后将画布缓冲区里的数据重新投射到画布上,这时画布缓冲区的数据已经显示到view上,画布缓冲区的数据就会被标示为过期数据,这一系列操作为一个循环,其中前景缓冲区和背景缓冲区来回交替保证了图像的更新。

Invalid的作用就是将画图区变投射区,投射到UI;原来的投射区就变成画图区并回传给ondraw(参数接收一个画布)
Invalid和postinvalid的区别:当在主线程(UI线程)进行画图区和投射区变换 使用invalid;如果在非UI线程进行绘制,需要将绘制完成的画布传递给UI线程变成投射区时需要使用postinvalid函数(内部使用handler消息传递机制,将子线程的MQ发送到主线程消息队列里面)invalid方法来自于view
Gameloop 游戏循环
在一个自定义view里面画图

public class myView extends View {
    private Paint paint;
    private int x, y;
    private int lin_x = 100;
    private int lin_y = 100;
    private float count = 0;

    public myView(Context context) {
        super(context);
        paint = new Paint();
    }

    public myView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        canvas.drawColor(Color.WHITE);
        paint.setColor(Color.BLUE);
        //set kong xin yuan
        paint.setStrokeWidth(3);
        canvas.drawLine(lin_x, lin_y, lin_x + x, lin_y + y, paint);
        paint.setStrokeWidth(2);
        paint.setColor(Color.RED);
        canvas.drawRect(lin_x - 5, lin_y - 5, lin_x + 5, lin_y + 5, paint);
        paint.setColor(Color.CYAN);
        canvas.drawRect(lin_x - 3, lin_y - 3, lin_x + 3, lin_y + 3, paint);
        myThread myThread = new myThread();
        myThread.start();
    }

    public void Update() {
        if (count > 12)
            count = 0;
        x = (int) (75.0 * Math.cos(2 * Math.PI * count / 12.0));
        y = (int) (75.0 * Math.sin(2 * Math.PI * count / 12.0));
        count++;
    }

    class myThread extends Thread {
        @Override
        public void run() {
            super.run();
            Update();
            postInvalidateDelayed(500);
            //view.psoinvalid()---onDraw()
        }
    }

}

上面实现的效果如下
画中指针一直转动,类似表针
步骤:
1 自定义view类,根据view的ondraw()传递过来的canvas画板,创建画笔,在画板上画直线和矩形
2 画图区执行完毕后启动一个自定义线程,子线程处理图形参数,变换x,y的值 处理完毕后 使用postinvalid让主线程对view进行更新,将画图区变成投射区,原来的投射区作为参数传给ondraw,准备下一次绘制
3 主线收到消息后(主线程默认实现loop,虽然没有写handler的处理消息过程,但是postinvalid已经代替我们实现了)重新调用view的ondraw()进行下一次绘图,绘制完毕后再一次启动一个线程变换参数,发送消息,如此循环下去
为了提高代码的可重用性,我们把子线程提取出来,子线程的构造方法接收一个view参数,也就是把这个自定义视图传进来

public class GameLoop extends Thread {
    myView view;
    public GameLoop(View view) {
        this.view = (myView) view;
    }

    @Override
    public void run() {
        super.run();
        view.Update();
        view.postInvalidateDelayed(5);

这两者的顺序不要变,要保证在任意时刻view中x,y只能被一个线程操作
}
}
然后在myView的ondraw()方法的绘制结束后启动一个GameLoop游戏线程
可能的疑问:不是说view是单线程环境,不能再子线程中操作吗
View对象创建于主线程中view的单线程环境是指不能通过其他线程来执行view类的父类方法,比如ondraw()但是view子类中自定义的变量在保证线程安全的前提下,以及子类view自定义的方法(方法里面不能调用父类方法)在保证线程安全的前提下是可以被其他线程操作的
Android中只是保证官方提供的类里面的变量以及方法在单线程环境运行,用户自己扩展的对量和方法在不干涉view系统变量和方法前提按照普通的对象处理(设计线程安全机制)
GameLoop升级改造
为什么要进行升级改造?
上面的代码中仔细阅读就会发现一个问题,在主线程执行view的ondraw()方法时会新创建一个游戏线程,游戏线程去执行变换,但是当游戏线程将run中的代码执行完毕后,游戏线程就会被注销掉,那么下一次执行ondraw方法时又会新创建一个游戏线程,线程执行完毕后释放对象,这样只会增加系统的开销
如何优化?
既然存在上面的问题解决办法也就简单了,在主线程中声明变量view在oncreate中进行view初始化,所以view对象一般不会被回收,那么在view的代码里 在view的构造方法中我们进行游戏线程的创建,并初始化让线程跑起来,对象的构造方法在类加载时会执行,只要对象不被回收,那么构造器中的变量就一直有效,在view绘图完毕之后我们让游戏线程去执行我们想要执行的代码,执行完毕后继续去循环,如此交替进行
游戏线程如何改?
我们想要达到的目的是游戏线程在view类创建时就开始运行,在我们需要游戏线程的时候,游戏线程可以去执行我们需要的代码,执行完毕后继续循环


public void run() {
    super.run();
    while (true)
    {
        if (isRun)
        {
            view.Update();
            view.postInvalidate();
            onPase();
        }
    }
}

public void onPase(){
    isRun=false;
}
public void isResume(){
    isRun=true;
}

这样的修改就可以达到,在view对象中进行控制
疑问?在测试APP中 发现一个这样的现象,当指针摆动起来后,过一会就停止了,但是当我们把view.postInvalidate();
修改为view.postInvalidateDelayed(500);延迟500毫秒发送就不会停止了,这个问题以后来解决

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值