这个一个每个人都喜欢的简单的小游戏

Snake是游戏的实现类,通过控制小蛇在花园中游走寻找苹果,注意,每吃掉一个苹果,小蛇身体不但会变的更长,还会移动的更敏捷,一旦撞上四周的墙或是碰到自己就会结束这次游戏。

代码结构分析:

Snake            : 主游戏窗口

SnakeView     : 游戏视图类,是实现游戏的主体类

TileView        : 一个处理图片或其它

Coordinate   :这是一个包括两个参数,用于记录X轴和Y轴简单类,其中包括一个比较函数.

RefshHandler :用于更新视图

Snake

这个类是游戏的主游戏窗口,是框架容器,

  1. 游戏的开始:oncreate此外的亮点是:setContentView(R.layout.snake_layout);设置窗口的布局文件,这里Android123给大家说明的是,这里 的snake_layout使用了自定义资源标签的方式,大家注意学习:这里我们可以看到来自SnakeView这个派生类的名称,由于Android内 部的R.资源不包含SnakeView类,所以我们必须写清楚Package,比如 com.exmple.android.snake.SnakeView 然后和其他控件使用一样,都是一个id然后宽度、高度、以及自定义的标签tileSize(尾巴长度),如下:

<com.example.android.snake.SnakeView

android:id="@+id/snake"

android:layout_width="fill_parent"

android:layout_height="fill_parent"

tileSize="12"

/>

  1. onPause:关于这点,大家可以参考下在我blog中关于active生命周期http://xusaomaiss.javaeye.com/admin/blogs/379826

在玩游戏过程中,如果有来电或是其它事件中断,这时应该把当前状态保存。以便返回时,还可以继续玩游戏。这就使用onSaveInstanceState实现保存当前状态。

TileView

注:此部分解析来自: Android示例程序Snake贪食蛇代码分析(三)

TileView,从名称上不难看出这是一个方砖类,就是生成一个方块。 TileView使用了Android平台的显示基类View,View类是直接从java.lang.Object派生出来的,是各种控件比如 TextView、EditView的基类,当然包括我们的窗口Activity类,这些在SDK文档中都说的比较清楚。

这里定义了 5个int型全局的变量,分别是方砖的数量mTileSize;方砖水平x防线的数量mXTileCount;以及竖直y方向上的方砖数量 mYTileCount,下面是一个相对偏移位置mXOffsetmYOffset;这里主让要大家了解如何自定义View在 Android开发中,在一个View类中主要是重写onSizeChanged方法来控制改变部分,以及onDraw实现画布的修改,实现的简写如下:

@Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {}
@Override
    public void onDraw(Canvas canvas) { super.onDraw(canvas);}
  我们自定义的TileView类需要自己添加一个构造方法,根据需要,我们还重载了一种包含样式的方法,这里大家可以多看下Gallery控件的实现,就好理解了,下面是基本框架。
public TileView(Context context, AttributeSet attrs, int defStyle) {super(context, attrs, defStyle);}
public TileView(Context context, AttributeSet attrs) { super(context, attrs);}
  在贪食蛇游戏中我们知道Snake是移动的,所以加入了一个清除显示的clearTiles方法,通过一个二维数组保存一个gird网格型的运动轨迹,下一次我们将会讲解android贪食蛇的游戏逻辑和完整的关联拼接实现。

SnakeView

在这个类中实现的游戏的实体,从游戏需求的角色,这个游戏包括了如下方面:

1.  随机产生小苹果,apples这里是复数,当然是是大于1个苹果,所以代码中产生了两个苹果。

2.  游戏状态管理

3.  画蛇,view的更新

4.  吃掉苹果后小蛇状态的变化

5.  画围墙

如果实现吃掉苹果小蛇速度变快?

关键是:mMoveDelay这个变量,以下是涉及到这个变量的函数,

每次吃掉苹果后,就会updateSnake一下,里面就把时间处理了:mMoveDelay *= 0.9;

小蛇其实就是一个数组,google的代码就是好注释写的清楚:

/**

* mSnakeTrail: a list of Coordinates that make up the snake's body

* mAppleList: the secret location of the juicy apples the snake craves.

*/

private ArrayList<Coordinate> mSnakeTrail = new ArrayList<Coordinate>();

private ArrayList<Coordinate> mAppleList = new ArrayList<Coordinate>();

mSnakeTrail:一个由Coordinates列表组织的蛇身.

mAppleList:存放鲜美多汁的苹果列表

通过这个数组画出小蛇不难,问题是如何判断游戏是否结束?

问题是如何判断游戏的状态

 

所有以下的代码来自updateSnake

1.  吃了苹果

// Look for apples

int applecount = mAppleList.size();

for (int appleindex = 0; appleindex < applecount; appleindex++) {

Coordinate c = mAppleList.get(appleindex);

if (c.equals(newHead)) {

mAppleList.remove(c);

addRandomApple();

mScore++;

mMoveDelay *= 0.9;

growSnake = true;

}

}

2.  碰到了自己

// Look for collisions with itself

int snakelength = mSnakeTrail.size();

for (int snakeindex = 0; snakeindex < snakelength; snakeindex++) {

Coordinate c = mSnakeTrail.get(snakeindex);

if (c.equals(newHead)) {

setMode(LOSE);

return;

}

}

3.碰到墙了

// Collision detection

// For now we have a 1-square wall around the entire arena

if ((newHead.x < 1) || (newHead.y < 1) || (newHead.x > mXTileCount - 2)

|| (newHead.y > mYTileCount - 2)) {

setMode(LOSE);

return;

}

源代码分析

Snake状态分析:

在snakeView中定义了snake游戏的几种状态:

private int mMode = READY;

public static final int PAUSE = 0;  //暂定

public static final int READY = 1;  //准备好了

public static final int RUNNING = 2;//正在运行

public static final int LOSE = 3;  //结束,输了游戏

各种游戏状态

rady  running

pausedlose

以上状态是通过:void setMode(int newMode)函数实现。

如何实现画出小方块:

参看:http://yuefeng.javaeye.com/blog/206706

public class DrawView extends View {

private final int mTileSize = 12;

private final String TAG="DEMO";

private Paint pa = new Paint();

private Bitmap mTileArray;

void loadImage(){

Resources r = this.getContext().getResources();

Drawable tile = r.getDrawable(R.drawable.redstar);

Bitmap bitmap = Bitmap.createBitmap(mTileSize, mTileSize,

Bitmap.Config.ARGB_8888);

Canvas canvas = new Canvas(bitmap);

tile.setBounds(0, 0, mTileSize, mTileSize);

tile.draw(canvas);

mTileArray = bitmap;

}

public DrawView(Context context, AttributeSet attrs, int defStyle) {

super(context, attrs, defStyle);

// TODO Auto-generated constructor stub

loadImage();

x = 10;

y = 10;

Log.i(TAG, "DrawView 2");

}

//如果没有这段代码,大家可以试一下,改用上面的代码,程序能否通过。

public DrawView(Context context, AttributeSet attrs) {

super(context, attrs);

// TODO Auto-generated constructor stub

loadImage();

Log.i(TAG, "DrawView 3");

}

@Override

protected void onDraw(Canvas canvas) {

super.onDraw(canvas);

Log.i(TAG, "onDraw 1");

canvas.drawBitmap(mTileArray, x, y, pa);

}

}

通过上面的文章可以画出小方块,但注意到SnakeView一共有两构造函数,那个函数才真正起作用呢?

public SnakeView(Context context, AttributeSet attrs)

public SnakeView(Context context, AttributeSet attrs, int defStyle)

通过加log的方式,判断是第一个构造函数起作用。

在第一个构造函数上方有一段注释:通过XML文件构造出SnakeView

* Constructs a SnakeView based on inflation from XML

如果不使用这个构造函数,将会造成错误,可以试一下,看一下结果是怎样!本人得到如下的错误提示:

05-21 14:13:26.079: ERROR/AndroidRuntime(711): Caused by: java.lang.NoSuchMethodException: DrawView

按键处理:

public boolean onKeyDown(int keyCode, KeyEvent event) {

// TODO Auto-generated method stub

if (keyCode == KeyEvent.KEYCODE_DPAD_UP) {

Log.i(TAG, "KEYCODE_DPAD_UP");

}

return super.onKeyDown(keyCode, event);

}

如何让我们的小方块动起来?

实现小方块动起来的秘密在于view的public void invalidate ()

大家可以参看SDK文档中关于View中Drawing中的一小段话

To force a view to draw, call invalidate().//为了让view重画,可以调用invalidate函数

方法:

在DrawView类中添加两个成员:

private int x,y;

同时实现get,set方法,

在构造函数中添加他们的初始值,

修改onDraw

@Override

protected void onDraw(Canvas canvas) {

super.onDraw(canvas);

Log.i(TAG, "onDraw 1");

canvas.drawBitmap(mTileArray, x, y, pa);

}

4.修改onKeyDown函数

@Override

public boolean onKeyDown(int keyCode, KeyEvent event) {

// TODO Auto-generated method stub

if (keyCode == KeyEvent.KEYCODE_DPAD_UP) {

Log.i(TAG, "KEYCODE_DPAD_UP");

dv.setX(dv.getX()+10);

dv.invalidate();

}

return super.onKeyDown(keyCode, event);

}

最后运行结果如下图:

附:网络上关于snake分析的三篇文章

上一次我们大概讲解了下Android SDK中的演示程序Snake游戏的主框架,今天我看来看下实现的基础类TileView,从名称上不难看出这是一个方砖类,就是生成一个方块。 TileView使用了Android平台的显示基类View,View类是直接从java.lang.Object派生出来的,是各种控件比如 TextView、EditView的基类,当然包括我们的窗口Activity类,这些在SDK文档中都说的比较清楚。

这里定义了 5个int型全局的变量,分别是方砖的数量mTileSize;方砖水平x防线的数量mXTileCount;以及竖直y方向上的方砖数量 mYTileCount,下面是一个相对偏移位置mXOffset和mYOffset;这里android123主让要大家了解如何自定义View在 Android开发中,在一个View类中主要是重写onSizeChanged方法来控制改变部分,以及onDraw实现画布的修改,实现的简写如下:

@Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {}

@Override
    public void onDraw(Canvas canvas) { super.onDraw(canvas);}

我们自定义的TileView类需要自己添加一个构造方法,根据需要,我们还重载了一种包含样式的方法,这里大家可以多看下Gallery控件的实现,就好理解了,下面是基本框架。
public TileView(Context context, AttributeSet attrs, int defStyle) {super(context, attrs, defStyle);}

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

在贪食蛇游戏中我们知道Snake是移动的,所以加入了一个清除显示的clearTiles方法,通过一个二维数组保存一个gird网格型的运动轨迹,下一次我们将会讲解android贪食蛇的游戏逻辑和完整的关联拼接实现。

今天我们分析下最复杂的SnakeView的设计,它是派生于TileView方砖类,TileView构建是基于Android直接的显示类View,如果不明白的可以查看Android示例程序Snake贪食蛇代码分析(二)一文有关TileView类的实现, 首先我们看到整个游戏分 READY、PAUSE 、RUNNING 、LOSE四种mMode状态模式,分别对应准备、暂停、运行中、结束(死亡),毕竟贪食蛇没有胜利这个结果。

整个Snake的运行分4个方向,NORTH、SOUTH 、EAST、WEST分别对应了北、南、东、西四个方向,其中变量mDirection对应当 前的方向,而mNextDirection对应下个运行时的位置。这里星星分3种,使用的是一个Drawable图片,分RED_STAR、 YELLOW_STAR和GREEN_STAR三种颜色,游戏的星星出现位置由Random随机数生成器来决定,这里Random一般和Timer系统时 钟来随机生成更真实一些,通过一个Handler对象来控制画面的更新,使用了this.update();和this.invalidate();这两 个本地方法,Update和invaildate均为android.view.View类的本地方法。这里资源的使用通过Resources r = this.getContext().getResources();获取了r对象的实例,通过 r.getDrawable(R.drawable.redstar)获取资源名为redstar的资源,返回的是一个Drawable对象。

对于按键信息,直接重写View类的onKeyDown方法,这里KeyEvent传递的是按键的映射,比如KEYCODE_DPAD_UP向上,KeyEvent.KEYCODE_DPAD_DOWN向下等等,详细的查看SDK中的onKeyDown

@Override
    public boolean onKeyDown(int keyCode, KeyEvent msg) {

if (keyCode == KeyEvent.KEYCODE_DPAD_UP) {}

}

整个游戏的控制流程就是上面这些,对于游戏的逻辑而言比较简单,这个贪食蛇并没有包含3D设计和类似Nokia的能量走廊、6边形轨迹,有空了我们一起来完善一个3D的贪食蛇游戏

下面这篇文章来自:http://www.jizhuomi.com/android/example/177.html

贪吃蛇剖析(一:暂停/继续、穿墙和全屏)

本文开始将为大家剖析Android示例程序-Snake贪吃蛇。贪吃蛇游戏大部分人都玩过,它是怎样实现的呢?Android示例程序给出了代码,下面进行详细分析。

游戏暂停/继续机制

由于原来的代码中在游戏运行时没有提供控制选项(比如暂停/继续),因此除非你死了,否则只能玩到底。我这里对代码进行一些修改,加入一个Option Menu来提供暂停/继续机制。

首先加入一个变量记录游戏当前状态:

private int mState = SnakeView.READY;

然后重载onCreateOptionsMenu函数,创建一个控制菜单项,并对其进行处理,提供暂停/继续机制。

Java代码

  1. /*
  2. * @see android.app.Activity#onOptionsItemSelected(android.view.MenuItem)
  3. * @Author:phinecos
  4. * @Date:2009-08-28
  5. */
  6. @Override
  7. public boolean onOptionsItemSelected(MenuItem item)
  8. {
  9. switch (item.getItemId())
  10. {
  11. case MENU_CONTROL:
  12. {
  13. if (mState == SnakeView.PAUSE)
  14. {//此前状态是"停止",则转为"运行"
  15. mState = SnakeView.RUNNING;
  16. mSnakeView.setMode(SnakeView.RUNNING);
  17. item.setIcon(android.R.drawable.ic_media_pause).setTitle(R.string.cmd_pause);
  18. }
  19. else if(mState == SnakeView.RUNNING)
  20. {//此前状态是"运行",则转为“暂停"
  21. mState = SnakeView.PAUSE;
  22. mSnakeView.setMode(SnakeView.PAUSE);
  23. item.setIcon(android.R.drawable.ic_media_play).setTitle(R.string.cmd_run);
  24. }
  25. else if(mState == SnakeView.READY)
  26. {//此前是"初始状态",则转为"运行"
  27. mState = SnakeView.RUNNING;
  28. }
  29. return true;
  30. }
  31. }
  32. return super.onOptionsItemSelected(item);
  33. }
  34. /*
  35. * @see android.app.Activity#onOptionsItemSelected(android.view.MenuItem)
  36. * @Author:phinecos
  37. * @Date:2009-08-28
  38. */
  39. @Override
  40. public boolean onCreateOptionsMenu(Menu menu)
  41. {
  42. super.onCreateOptionsMenu(menu);
  43. menu.add(0, MENU_CONTROL, 0, R.string.cmd_pause).setIcon(android.R.drawable.ic_media_pause);
  44. return true;
  45. }

修改后运行截图如下:

当然,这段代码还是有问题的,游戏刚开始时,必须先点击菜单确认,再按上方向键才能开始。(以后再来修改。。。)

    穿墙贪食蛇

第二个修改是把这个普通的贪食蛇改成可以穿墙(呵呵,这样就可以不死了。。。)。想必要修改的就是撞墙检测那段代码?没错,就是下面这段!

Java代码

  1. // Collision detection
  2. // For now we have a 1-square wall around the entire arena
  3. if ((newHead.x < 1) || (newHead.y < 1) || (newHead.x > mXTileCount - 2)|| (newHead.y > mYTileCount - 2))
  4. {//撞墙
  5. setMode(LOSE);
  6. return;
  7. }

原来的版本是发现撞墙时就直接判定为失败,我这里做个小小的修改,让它可以穿墙而去:

Java代码

  1. private void updateSnake()
  2. {
  3. boolean growSnake = false;
  4. // grab the snake by the head
  5. Coordinate head = mSnakeTrail.get(0);
  6. Coordinate newHead = new Coordinate(1, 1);
  7. mDirection = mNextDirection;
  8. switch (mDirection)
  9. {
  10. case EAST:
  11. {
  12. newHead = new Coordinate(head.x + 1, head.y);
  13. break;
  14. }
  15. case WEST:
  16. {
  17. newHead = new Coordinate(head.x - 1, head.y);
  18. break;
  19. }
  20. case NORTH:
  21. {
  22. newHead = new Coordinate(head.x, head.y - 1);
  23. break;
  24. }
  25. case SOUTH:
  26. {
  27. newHead = new Coordinate(head.x, head.y + 1);
  28. break;
  29. }
  30. }
  31. //穿墙的处理
  32. if (newHead.x == 0)
  33. {//穿左边的墙
  34. newHead.x = mXTileCount - 2;
  35. }
  36. else if (newHead.y == 0)
  37. {//穿上面的墙
  38. newHead.y = mYTileCount - 2;
  39. }
  40. else if (newHead.x == mXTileCount - 1)
  41. {//穿右边的墙
  42. newHead.x = 1;
  43. }
  44. else if (newHead.y == mYTileCount - 1)
  45. {//穿下面的墙
  46. newHead.y = 1;
  47. }
  48. // 判断是否撞到自己
  49. int snakelength = mSnakeTrail.size();
  50. for (int snakeindex = 0; snakeindex < snakelength; snakeindex++)
  51. {
  52. Coordinate c = mSnakeTrail.get(snakeindex);
  53. if (c.equals(newHead))
  54. {
  55. setMode(LOSE);
  56. return;
  57. }
  58. }
  59. // 判断是否吃掉“苹果”
  60. int applecount = mAppleList.size();
  61. for (int appleindex = 0; appleindex < applecount; appleindex++)
  62. {
  63. Coordinate c = mAppleList.get(appleindex);
  64. if (c.equals(newHead))
  65. {
  66. mAppleList.remove(c);
  67. addRandomApple();
  68. mScore++;
  69. mMoveDelay *= 0.9;
  70. growSnake = true;
  71. }
  72. }
  73. // push a new head onto the ArrayList and pull off the tail
  74. mSnakeTrail.add(0, newHead);
  75. // except if we want the snake to grow
  76. if (!growSnake)
  77. {
  78. mSnakeTrail.remove(mSnakeTrail.size() - 1);
  79. }
  80. int index = 0;
  81. for (Coordinate c : mSnakeTrail)
  82. {
  83. if (index == 0)
  84. {
  85. setTile(YELLOW_STAR, c.x, c.y);
  86. }
  87. else
  88. {
  89. setTile(RED_STAR, c.x, c.y);
  90. }
  91. index++;
  92. }
  93. }

其实修改后的代码非常简单,就是把新节点的值做些处理,让它移动到对应的行/列的头部或尾部即可。下面就是修改后的“穿墙”贪食蛇的运行截图:

全屏机制

游戏一般都是全屏的,原始代码也考虑到标题栏太过难看了,于是使用下面这句代码就去掉了标题栏:

Java代码

  1. requestWindowFeature(Window.FEATURE_NO_TITLE);

可还是没有达到全屏的效果,在Android1.5中实现全屏效果非常简单,只需要一句代码即可实现:

Java代码

  1. getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN);

运行效果如下图所示:

贪吃蛇剖析(二:FrameLayout与RelativeLayout)

前一节中将了贪吃蛇Snake游戏的暂停/继续、穿墙和全屏功能的实现,本文继续分析此示例程序中体现的Android Layout机制。

1、FrameLayout

先来看官方文档的定义:FrameLayout是最简单的一个布局对象。它被定制为你屏幕上的一个空白备用区域,之后你可以在其中填充一个单一对象 — 比如,一张你要发布的图片。所有的子元素将会固定在屏幕的左上角;你不能为FrameLayout中的一个子元素指定一个位置。后一个子元素将会直接在前 一个子元素之上进行覆盖填充,把它们部份或全部挡住(除非后一个子元素是透明的)。

有点绕口而且难理解,下面还是通过一个实例来理解吧。我们仿照Snake项目中使用的界面一样,建立一个简单的FrameLayout,其中包含两个Views元素:ImageView和TextView,而后面的TextView还包含在一个RelativeLayout中。

XML/HTML代码

  1. <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
  2. android:layout_width="fill_parent"
  3. android:layout_height="fill_parent">
  4. <ImageView
  5. android:layout_width="fill_parent"
  6. android:layout_height="fill_parent"
  7. android:scaleType="center" android:src="@drawable/img0"/>
  8. <RelativeLayout
  9. android:layout_width="fill_parent"
  10. android:layout_height="fill_parent" >
  11. <TextView
  12. android:text="Hello Android"
  13. android:visibility="visible"
  14. android:layout_width="wrap_content"
  15. android:layout_height="wrap_content"
  16. android:layout_centerInParent="true"
  17. android:gravity="center_horizontal"
  18. android:textColor="#ffffffff"
  19. android:textSize="24sp"/>
  20. </RelativeLayout>
  21. </FrameLayout>

效果如下图所示:

2、UI优化

Android的tools目录下提供了许多实用工具,这里介绍其中一个用于查看当前UI结构视图的工具hierarchyviewer。打开tools/hierarchyviewer.bat后,查看上面这个示例的UI结构图可得:

我们可以很明显的看到由红色线框所包含的结构出现了两个framelayout节点,很明显这两个完全意义相同的节点造成了资源浪费(这里可以提醒大家在 开发工程中可以习惯性的通过hierarchyViewer查看当前UI资源的分配情况),那么如何才能解决这种问题呢(就当前例子是如何去掉多余的 frameLayout节点)?这时候就要用到<merge />标签来处理类似的问题了。我们将上边xml代码中的framLayout替换成merge:

XML/HTML代码

  1. <merge  xmlns:android="http://schemas.android.com/apk/res/android">
  2. <ImageView
  3. android:layout_width="fill_parent"
  4. android:layout_height="fill_parent"
  5. android:scaleType="center" android:src="@drawable/img0"/>
  6. <RelativeLayout
  7. android:layout_width="fill_parent"
  8. android:layout_height="fill_parent" >
  9. <TextView
  10. android:text="Hello Android"
  11. android:visibility="visible"
  12. android:layout_width="wrap_content"
  13. android:layout_height="wrap_content"
  14. android:layout_centerInParent="true"
  15. android:gravity="center_horizontal"
  16. android:textColor="#ffffffff"
  17. android:textSize="24sp"/>
  18. </RelativeLayout>
  19. </merge >

运行程序后在Emulator中显示的效果是一样的,可是通过hierarchyviewer查看的UI结构是有变化的,当初多余的 FrameLayout节点被合并在一起了,或者可以理解为将merge标签中的子集直接加到Activity的FrameLayout跟节点下(这里需 要提醒大家注意:所有的Activity视图的根节点都是frameLayout)。如果你所创建的Layout并不是用framLayout作为根节点 (而是应用LinerLayout等定义root标签),就不能应用上边的例子通过merge来优化UI结构。

3、RelativeLayout

   RelativeLayout允许子元素指定他们相对于其它元素或父元素的位置(通过ID指定)。因此,你可以以右对齐,或上下,或置于屏幕中央的形式 来排列两个元素。元素按顺序排列,因此如果第一个元素在屏幕的中央,那么相对于这个元素的其它元素将以屏幕中央的相对位置来排列。如果使用XML来指定这个layout,在你定义它之前,被关联的元素必须定义。

   解释起来也比较麻烦,不过我做个对比实验可以明白它的用处了,试着把上面例子里的RelativeLayout节点去掉看看,效果如下图所示,可以看到 由于FrameLayout的原因,都在左上角靠拢了,而使用了RelativeLayout,则可以让TextView相对于屏幕居中。

4、Snake的界面分析

有了上述Layout的基础知识,我们再来看Snake的布局文件就很好理解了,就是一个SnakeView和一个TextView,启动后,后者会覆盖在前者上面。

XML/HTML代码

  1. <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
  2. android:layout_width="fill_parent"
  3. android:layout_height="fill_parent">
  4. <com.example.android.snake.SnakeView
  5. android:id="@+id/snake"
  6. android:layout_width="fill_parent"
  7. android:layout_height="fill_parent"
  8. tileSize="24"
  9. />
  10. <RelativeLayout
  11. android:layout_width="fill_parent"
  12. android:layout_height="fill_parent" >
  13. <TextView
  14. android:id="@+id/text"
  15. android:text="@string/snake_layout_text_text"
  16. android:visibility="visible"
  17. android:layout_width="wrap_content"
  18. android:layout_height="wrap_content"
  19. android:layout_centerInParent="true"
  20. android:gravity="center_horizontal"
  21. android:textColor="#ff8888ff"
  22. android:textSize="24sp"/>
  23. </RelativeLayout>
  24. </FrameLayout>

也就是这样的效果:

那么相应的代码是如何实现这个效果的呢? SnakeView有一个私有变量存放覆盖其上的TextView:

Java代码

  1. private TextView mStatusText;

在Snake这个Activity的onCreate方法中,首先将Layout文件中的SnakeView和TextView关联起来:

Java代码

  1. setContentView(R.layout.snake_layout);
  2. mSnakeView = (SnakeView) findViewById(R.id.snake);
  3. mSnakeView.setTextView((TextView) findViewById(R.id.text));

然后设置SnakeView的状态为Ready:

Java代码

  1. mSnakeView.setMode(SnakeView.READY);

这一句代码会调用下述函数:

Java代码

  1. public void setMode(int newMode)
  2. {
  3. int oldMode = mMode;
  4. mMode = newMode;
  5. if (newMode == RUNNING & oldMode != RUNNING)
  6. {//游戏进入“运行”状态,则隐藏文字信息
  7. mStatusText.setVisibility(View.INVISIBLE);
  8. update();
  9. return;
  10. }
  11. //根据新状态,设置待显示的文字信息
  12. Resources res = getContext().getResources();
  13. CharSequence str = "";
  14. if (newMode == PAUSE)
  15. {//新状态为“暂停”
  16. str = res.getText(R.string.mode_pause);
  17. }
  18. if (newMode == READY)
  19. {//新状态为“准备开始”
  20. str = res.getText(R.string.mode_ready);
  21. }
  22. if (newMode == LOSE)
  23. {//新状态为“游戏失败”
  24. str = res.getString(R.string.mode_lose_prefix) + mScore
  25. + res.getString(R.string.mode_lose_suffix);
  26. }
  27. //设置文字信息并显示
  28. mStatusText.setText(str);
  29. mStatusText.setVisibility(View.VISIBLE);
  30. }

在mStatusText.setVisibility(View.VISIBLE);这一句后就显示出上面这个游戏起始画面了。

贪吃蛇剖析(三:界面UI、游戏逻辑和Handler)

往往我们在程序设计的时候喜欢将界面与处理分开,这样降低耦合性,易于维护扩展。在贪吃蛇Snake这个示例程序中同样将界面UI和游戏逻辑进行了分离, 它的实现方式就是,用父类TileView来实现比较基础的界面UI部分,而TileView类的子类SnakeView类完成了游戏控制逻辑部分,这样 就成功的将两者进行了分离,对后面的扩展和维护奠定了良好的基础。

界面UI

首先来看界面UI部分,基本思想大家都非常清楚:把整个屏幕看做一个二维数组,每一个元素可以视为一个方块,因此每个方格在游戏进行过程中可以处于不同的 状态,比如空闲,墙,苹果,贪食蛇(蛇身或蛇头)。我们在操作游戏的过程,其实就是不断修改相应方格的状态,然后再让整个View去重绘制自身(当然,还需要加入一些游戏当前所处状态(失败或成功)的判定机制)。TileView的数据成员如下:

Java代码

  1. //方格的大小
  2. protected static int mTileSize;
  3. //方格的行数和列数
  4. protected static int mXTileCount;
  5. protected static int mYTileCount;
  6. //xy坐标系的偏移量
  7. private static int mXOffset;
  8. private static int mYOffset;
  9. //存储三种方格的图标文件
  10. private Bitmap[] mTileArray;
  11. //二维方格地图
  12. private int[][] mTileGrid;

那么在游戏还未正式开始前,首先要做一些初始化工作,在View第一次加载时会首先调用onSizeChanged,这里就是做这些事的最好时机。

Java代码

  1. @Override
  2. protected void onSizeChanged(int w, int h, int oldw, int oldh)
  3. {
  4. //计算屏幕中可放置的方格的行数和列数
  5. mXTileCount = (int) Math.floor(w / mTileSize);
  6. mYTileCount = (int) Math.floor(h / mTileSize);
  7. mXOffset = ((w - (mTileSize * mXTileCount)) / 2);
  8. mYOffset = ((h - (mTileSize * mYTileCount)) / 2);
  9. mTileGrid = new int[mXTileCount][mYTileCount];
  10. clearTiles();
  11. }

注意模拟器屏幕默认的像素是320×400,而代码中默认的方格大小为12,因此屏幕上放置的方格数为26×40,把屏幕剖分成这么大后,再设置一个相应 的二维int型数组来记录每一个方格的状态,根据方格的状态,可以从mTileArray保存的图标文件中读取对应的状态图标。

  第一次调用完onSizeChanged后,会紧跟着第一次来调用onDraw来绘制View自身,当然,此时由于所有方格的状态都是0,所以它在屏幕上等于什么也不会去绘制。

Java代码

  1. public void onDraw(Canvas canvas)
  2. {
  3. super.onDraw(canvas);
  4. for (int x = 0; x < mXTileCount; x += 1)
  5. {
  6. for (int y = 0; y < mYTileCount; y += 1)
  7. {
  8. if (mTileGrid[x][y] > 0)
  9. {
  10. canvas.drawBitmap(mTileArray[mTileGrid[x][y]],
  11. mXOffset + x * mTileSize,
  12. mYOffset + y * mTileSize,
  13. mPaint);
  14. }
  15. }
  16. }
  17. }

onDraw要做的工作非常简单,就是扫描每一个方格,根据方格当前状态,从图标文件中选择对应的图标绘制到这个方格上。当然这个onDraw在游戏进行过程中,会不断地被调用,从而界面不断被更新。

  游戏逻辑

  再来看子类SnakeView是如何在父类TileView的基础上,加入特定的游戏逻辑,从而完成Snake这个程序的。

Java代码

  1. private ArrayList<Coordinate> mSnakeTrail = new ArrayList<Coordinate>();//组成贪食蛇的方格列表
  2. private ArrayList<Coordinate> mAppleList = new ArrayList<Coordinate>();//苹果方格列表

由于SnakeView从TileView继承而来,则可以说它已经拥有这个二维方格地图了(只是此时地图里的所有方格状态都是0)。那么它有了这么一个二维方格地图,如何去初始化这个地图呢?这在initNewGame函数中实现。

Java代码

  1. private void initNewGame()
  2. {
  3. //清空蛇和苹果占据的方格
  4. mSnakeTrail.clear();
  5. mAppleList.clear();
  6. //目前组成蛇的方格式固定的,而且方向也固定朝北
  7. mSnakeTrail.add(new Coordinate(7, 7));
  8. mSnakeTrail.add(new Coordinate(6, 7));
  9. mSnakeTrail.add(new Coordinate(5, 7));
  10. mSnakeTrail.add(new Coordinate(4, 7));
  11. mSnakeTrail.add(new Coordinate(3, 7));
  12. mSnakeTrail.add(new Coordinate(2, 7));
  13. mNextDirection = NORTH;
  14. //随即加入苹果
  15. for (int i = 0; i < nApples; ++i)
  16. {
  17. addRandomApple();
  18. }
  19. //初始化运动速率和玩家成绩
  20. mMoveDelay = 600;
  21. mScore = 0;
  22. }

想象下对整个游戏屏幕拍张照,然后对其下一个状态再拍张照,那么两张照片之间的区别是怎么产生的呢?对于系统来说,它只知道不断调用onDraw,后者负 责对整个屏幕进行绘制,那要产生两个屏幕之间的差异,肯定要通过一些手段对某些数据结构(比如这里的二维方格地图)进行调整(比如用户的控制指令,定时器 等),然后等到下一次onDraw时就会把这些更改在界面上反映出来。

这里要着重说明下private long mMoveDelay = 600;这个成员变量,虽然很不起眼,但仔细考虑它的作用就会发现很有趣,那么改变它的大小到底是如何让我们感觉到游戏变快或变慢呢?

可以打个简单的比方,在时刻0游戏启动,首先把蛇和苹果的位置都在方格地图上作好了标记,然后我们在update函数中修改蛇身让蛇向北前进一步,而这个 改变此时还只是停留在内部的核心数据结构上(即二维方格地图),还没有在界面上显示出来。当然,我们马上想到要想让这更改显示出来,让系统调用 onDraw去绘制不就完了吗?可是问题是我们不知道系统是隔多长时间去调用onDraw函数,于是mMoveDelay此时就发挥作用了,通过它就可以 设置休眠的时间,等时间一到,马上就会通知SnakeView去重绘制。你可以试试把mMoveDelay数值调大,就会看出我上面提到的“拍照“的效 果。

  Handler的使用

   写过JavaScript或者ActionScript的开发者,对于setInterval的用法会非常了解。那么在Android中如何实现 setInterval的方法呢?其中有两种方法可以实现类似的功能,其中一个是在线程中调用Handler方法,另外一个是应用Timer。Snake 中使用了前者。

Java代码

  1. class RefreshHandler extends Handler
  2. {
  3. @Override
  4. public void handleMessage(Message msg)
  5. {//“苏醒”后的处理
  6. SnakeView.this.update();
  7. SnakeView.this.invalidate();
  8. }
  9. public void sleep(long delayMillis)
  10. {//休眠delayMillis毫秒
  11. this.removeMessages(0);
  12. sendMessageDelayed(obtainMessage(0), delayMillis);
  13. }
  14. };

而实际调用的处理函数update就可以说是整个游戏的引擎,正是由于它的工作(修改蛇和苹果的状态到一个新的状态,然后休眠自己,然后等到苏醒后在 Handler中就会让系统区绘制上次修改过的二维方块地图,然后再次调用update,如此循环反复,生生不息),才使得游戏不断被推进,因此,比做 “引擎“不为过。

Java代码

  1. public void update()
  2. {
  3. if (mMode == RUNNING)
  4. {
  5. long now = System.currentTimeMillis();
  6. if (now - mLastMove > mMoveDelay)
  7. {
  8. clearTiles();
  9. updateWalls();
  10. updateSnake();
  11. updateApples();
  12. mLastMove = now;
  13. }
  14. mRedrawHandler.sleep(mMoveDelay);
  15. }
  16. }

既然update是游戏的动力,要让游戏停止下来只要不再调用update就可以了(因为此时其实是画面静止了),因此游戏进入暂停(这个状态还可以转为 “运行“,其实就是继续可以修改,再绘制),若进入失败(其实此时二维方块地图还停留在最后一个画面处,这也是为什么在开始时要首先清理掉整个地图)【这 一点,可以在游戏失败后,再次开始新游戏,此时通过设置的断点即可观察到上次游戏运行时的底层数据】。

  一点困惑

  可是个人认为Snake下面这段代码读起来有点怪,有点像一个“先有鸡,还是先有蛋?“的问题,导致我的思维逻辑上出现一个“怪圈“。

Java代码

  1. public void handleMessage(Message msg)
  2. {
  3. SnakeView.this.update();
  4. SnakeView.this.invalidate();
  5. }

按照这段代码的意思来看,当休眠的时间已经到了,首先去调用update,即为下一次绘制做准备工作,再让自己休眠起来,最后通知系统重绘制自己。

   哎,这让我难以理解,还是回到时刻0的例子来说,在时刻0时让蛇身向北前进了一步(指的是底层的二维方格地图的修改,不是界面),然后让自己休眠0.6 毫秒,当时间到了,首先去调用update方法,那么就又会让蛇身做出修改,也就是把上一次还没绘制的覆盖掉了(那么上一次的修改岂不是白费,还没画上去 呢),更何况在update中又会让自己去休眠(还没调用invalidate,怎么又去休眠了?),又怎么还能去通知系统调用我的onDraw方法呢? 也就是说invalidate根本没有执行???

  按我的理解,应该把顺序颠倒一下,先通知系统去调用onDraw方法重绘,使得上一次 对底层二维方格地图的修改显示出来,然后再去为下一次修改做准备工作,最后让自己进入休眠,等待苏醒过来,如此循环反复。实验证明,颠倒过来也是正确的, 不过关于这一个迷惑我的地方,希望有朋友能指点我一下!

记得在javascript里使用setInterval时,也是先写处理逻辑,然后在末尾处写上一句setInterval(这也是我习惯的思维方式了),难道google上面这种写法有何深意?

    此外,感觉每次绘制时都重新绘制墙壁,有点浪费时间,因为墙壁根本没有任何变化的。还有就是mLastMove这个变量设置的初衷是保证当前时间点距上一 次变化已经过去了mMoveDelay毫秒,可是既然已经用了sleep机制,再使用这个时间差看上去并无必要。

LunarLander游戏

前面有几篇文章写的是对Android示例程序贪吃蛇Snake程序的剖析,本文继续分析Android自带的另一个小游戏LunarLander的程序。在贪吃蛇Snake程序中采用了“定时器+系统调用onDraw”的架构,而LunarLander程序采用的是“多线程+强制自行绘制”的架构思路,比前者更为实用。

与贪吃蛇Snake程序的对比

就界面Layout来说,这个程序其实和Snake没有什么不同,同样是采用了FrameLayout,而且游戏的主界面由一个自定义的View来 实现,这里是LunarView。读过贪吃蛇程序剖析文章的朋友也许会发现,Snake的架构是“定时器+系统调用onDraw”来实现的,这里有一个最 大的缺陷就是onDraw是由Android系统来调用的,我们只能依赖它,却无法自行控制。这就好比一个黑盒,当然,总是能把我们要的东西给做出来,可 却无法控制其做事的细节,这对于游戏这样高效率的东西可是不利的,因此最好的解决之道当然是把绘制这部分工作自己”承包“过来,告别吃大锅饭的,进入”联 产承包制”时代。

此外,由于游戏的本质就是连续两帧图片之间发生些许差异,那么要不断催生这种差异的发生,只要有某种连续不 断发生的事件在进行就可以,例如Snake中使用的定时器,就是在不断地产生这种“差异源”,与此类似,一个线程也是不断在运行中,通过它也是可以不断产 生这种“差异源”的。

  SurfaceView初探

如果说Snake中使用的Layout加自定义View是一把小型武器的话,那在SurfaceView对于android中游戏的开发来说就算是重型武 器了。我们使用前者时总是容易把游戏中某个对象(比如上文的每一个方格)当做一个小组件来处理,而后者则根本没有这种划分的概念,在它眼中,所有东西都是 在Canvas(画布)中自行绘制出来的(背景,人物等)。

   SurfaceView提供直接访问一个可画图的界面,可以控制在界面顶部的子视图层。SurfaceView是提供给需要直接画像素而不是使用窗体部 件的应用使用的。Android图形系统中一个重要的概念和线索是surface。View及其子类(如TextView, Button)要画在surface上。每个surface创建一个Canvas对象(但属性时常改变),用来管理view在surface上的绘图操 作,如画点画线。还要注意的是,使用它的时候,一般都是出现在最顶层的:The view hierarchy will take care of correctly compositing with the Surface any siblings of the SurfaceView that would normally appear on top of it. 使用的SurfaceView的时候,一般情况下还要对其进行创建、销毁、改变时的情况进行监视,这就要用到 SurfaceHolder.Callback。

Java代码

  1. class LunarView extends SurfaceView implements SurfaceHolder.Callback
  2. {
  3. public void surfaceChanged(SurfaceHolder holder,int format,int width,int height){}
  4. //在surface的大小发生改变时激发
  5. public void surfaceCreated(SurfaceHolder holder){}
  6. //在创建时激发,一般在这里调用画图的线程。
  7. public void surfaceDestroyed(SurfaceHolder holder) {}
  8. //销毁时激发,一般在这里将画图的线程停止、释放。
  9. }

surfaceCreated会首先被调用,然后是surfaceChanged,当程序结束时会调用surfaceDestroyed。下面来看看LunarView最重要的成员变量,也就是负责这个View所有处理的线程。

Java代码

  1. private LunarThread thread; // 实际工作线程
  2. thread = new LunarThread(holder, context, new Handler() {
  3. @Override
  4. public void handleMessage(Message m)
  5. {
  6. mStatusText.setVisibility(m.getData().getInt("viz"));
  7. mStatusText.setText(m.getData().getString("text"));
  8. }
  9. });

   这个线程由私有类LunarThread实现,它里面还有一个自己的消息队列处理器,用来接收游戏状态消息,并在屏幕上显示当前状态(而这个功能在 Snake中是通过View自己控制其包含的TextView是否显示来实现的,相比之下,LunarThread的消息处理机制更为高效)。由于有了 LunarThread这个负责具体工作的对象,所以LunarView的大部分工作都委托给后者去执行。

Java代码

  1. public void surfaceChanged(SurfaceHolder holder, int format, int width,int height){
  2. thread.setSurfaceSize(width, height);
  3. }
  4. public void surfaceCreated(SurfaceHolder holder)
  5. {//启动工作线程结束
  6. thread.setRunning(true);
  7. thread.start();
  8. }
  9. public void surfaceDestroyed(SurfaceHolder holder)
  10. {
  11. boolean retry = true;
  12. thread.setRunning(false);
  13. while (retry)
  14. {
  15. try
  16. {//等待工作线程结束,主线程才结束
  17. thread.join();
  18. retry = false;
  19. }
  20. catch (InterruptedException e)
  21. {
  22. }
  23. }
  24. }

  工作线程LunarThread

  由于SurfaceHolder是一个共享资源,因此在对其操作时都应该实行“互斥操作“,即需要使用synchronized进行”封锁“机制。

再来讨论下为什么要使用消息机制来更新界面的文字信息呢?其实原因是这样的,渲染文字的工作实际上是主线程(也就是LunarView类)的父类View 的工作,而并不属于工作线程LunarThread,因此在工作线程中式无法控制的。所以我们改为向主线程发送一个Message来代替,让主线程通过Handler对接收到的消息进行处理,从而更新界面文字信息。再回顾Android示例程序剖析之Snake贪吃蛇(三:界面UI、游戏逻辑和Handler)中SnakeView里的文字信息更新,由于是SnakeView自己(就这一个线程)对其包含的TextView做控制,当然没有这样的问题了。

Java代码

  1. public void setState(int mode, CharSequence message)
  2. {
  3. synchronized (mSurfaceHolder)
  4. {
  5. mMode = mode;
  6. if (mMode == STATE_RUNNING)
  7. {//运行中,隐藏界面文字信息
  8. Message msg = mHandler.obtainMessage();
  9. Bundle b = new Bundle();
  10. b.putString("text", "");
  11. b.putInt("viz", View.INVISIBLE);
  12. msg.setData(b);
  13. mHandler.sendMessage(msg);
  14. }
  15. else
  16. {//根据当前状态设置文字信息
  17. mRotating = 0;
  18. mEngineFiring = false;
  19. Resources res = mContext.getResources();
  20. CharSequence str = "";
  21. if (mMode == STATE_READY)
  22. str = res.getText(R.string.mode_ready);
  23. else if (mMode == STATE_PAUSE)
  24. str = res.getText(R.string.mode_pause);
  25. else if (mMode == STATE_LOSE)
  26. str = res.getText(R.string.mode_lose);
  27. else if (mMode == STATE_WIN)
  28. str = res.getString(R.string.mode_win_prefix)
  29. + mWinsInARow + " "
  30. + res.getString(R.string.mode_win_suffix);
  31. if (message != null) {
  32. str = message + "\n" + str;
  33. }
  34. if (mMode == STATE_LOSE)
  35. mWinsInARow = 0;
  36. Message msg = mHandler.obtainMessage();
  37. Bundle b = new Bundle();
  38. b.putString("text", str.toString());
  39. b.putInt("viz", View.VISIBLE);
  40. msg.setData(b);
  41. mHandler.sendMessage(msg);
  42. }
  43. }
  44. }

   下面就是LunaThread这个工作线程的执行函数了,它一直不断在重复做一件事情:锁定待绘制区域(这里是整个屏幕),若游戏还在进行状态,则更新底层的数据,然后直接强制界面重新绘制。

Java代码

  1. public void run()
  2. {
  3. while (mRun)
  4. {
  5. Canvas c = null;
  6. try
  7. {
  8. //锁定待绘制区域
  9. c = mSurfaceHolder.lockCanvas(null);
  10. synchronized (mSurfaceHolder)
  11. {
  12. if (mMode == STATE_RUNNING)
  13. updatePhysics();//更新底层数据,判断游戏状态
  14. doDraw(c);//强制重绘制
  15. }
  16. }
  17. finally
  18. {
  19. if (c != null) {
  20. mSurfaceHolder.unlockCanvasAndPost(c);
  21. }
  22. }
  23. }
  24. }

这里要注意的是最后要调用unlockCanvasAndPost来结束锁定画图,并提交改变。

  强制自行绘制

doDraw这段代码就是在自己的Canvas上进行绘制,具体的绘制就不解释了,主要就是用drawBitmap,drawRect,drawLine。值得注意的一段代码是下面这个:

Java代码

  1. canvas.save();
  2. canvas.rotate((float) mHeading, (float) mX, mCanvasHeight
  3. - (float) mY);
  4. if (mMode == STATE_LOSE) {
  5. mCrashedImage.setBounds(xLeft, yTop, xLeft + mLanderWidth, yTop
  6. + mLanderHeight);
  7. mCrashedImage.draw(canvas);
  8. } else if (mEngineFiring) {
  9. mFiringImage.setBounds(xLeft, yTop, xLeft + mLanderWidth, yTop
  10. + mLanderHeight);
  11. mFiringImage.draw(canvas);
  12. } else {
  13. mLanderImage.setBounds(xLeft, yTop, xLeft + mLanderWidth, yTop
  14. + mLanderHeight);
  15. mLanderImage.draw(canvas);
  16. }
  17. canvas.restore();

在绘制火箭的前后,调用了save()和restore(),它是先保存当前矩阵,将其复制到一个私有堆栈上。然后接下来对rotate的调用还是在原有 的矩阵上进行操作,但当restore调用后,以前保存的设置又重新恢复。不过,在这里还是看不出有什么用处。

  暂停/继续机制

LunarLancher的暂停其实并没有不再强制重绘制,而是没有对底层的数据做任何修改,依然绘制同一帧画面,而继续则是把mLastTime设置为 当前时间+100毫秒的时间点,因为以前暂停时mLastTime就不再更新了,这样做事为了与当前时间同步起来。

Java代码

  1. public void pause()
  2. {//暂停
  3. synchronized (mSurfaceHolder)
  4. {
  5. if (mMode == STATE_RUNNING)
  6. setState(STATE_PAUSE);
  7. }
  8. }
  9. public void unpause()
  10. {// 继续
  11. // Move the real time clock up to now
  12. synchronized (mSurfaceHolder)
  13. {
  14. mLastTime = System.currentTimeMillis() + 100;
  15. }
  16. setState(STATE_RUNNING);
  17. }

这样做的目的是为了制造“延迟“的效果,都是因为updatePhysics函数里这两句:

Java代码

  1. if (mLastTime > now) return;
  2. double elapsed = (now - mLastTime) / 1000.0;

至于游戏的控制逻辑和判定部分就不介绍了,没有多大意思。

转载于:https://www.cnblogs.com/olvo/archive/2013/05/28/3103370.html

snake与LunarLander源代码分析相关推荐

  1. Android系统默认Home应用程序(Launcher)的启动过程源代码分析

    在前面一篇文章中,我们分析了Android系统在启动时安装应用程序的过程,这些应用程序安装好之后,还需要有一个Home应用程序来负责把它们在桌面上展示出来,在Android系统中,这个默认的Home应 ...

  2. 《LINUX3.0内核源代码分析》第一章:内存寻址

    https://blog.csdn.net/ekenlinbing/article/details/7613334 摘要:本章主要介绍了LINUX3.0内存寻址方面的内容,重点对follow_page ...

  3. Scrapy源代码分析-经常使用的爬虫类-CrawlSpider(三)

    CrawlSpider classscrapy.contrib.spiders.CrawlSpider 爬取一般站点经常使用的spider.其定义了一些规则(rule)来提供跟进link的方便的机制. ...

  4. Android 中View的绘制机制源代码分析 三

    到眼下为止,measure过程已经解说完了,今天開始我们就来学习layout过程.只是在学习layout过程之前.大家有没有发现我换了编辑器,哈哈.最终下定决心从Html编辑器切换为markdown编 ...

  5. Android应用程序进程启动过程的源代码分析(1)

    Android应用程序框架层创建的应用程序进程具有两个特点,一是进程的入口函数是ActivityThread.main,二是进程天然支持Binder进程间通信机制:这两个特点都是在进程的初始化过程中实 ...

  6. AFNetworking 源代码分析

    关于其他 AFNetworking 源代码分析的其他文章: AFNetworking 概述(一) AFNetworking 的核心 AFURLSessionManager(二) 处理请求和响应 AFU ...

  7. Hadoop源代码分析 - MapReduce(转载)

    1. Hadoop源代码分析(MapReduce概论) http://caibinbupt.javaeye.com/blog/336467

  8. RTMPdump(libRTMP) 源代码分析 3: AMF编码

    2019独角兽企业重金招聘Python工程师标准>>> 注:此前写了一些列的分析RTMPdump(libRTMP)源代码的文章,在此列一个列表: RTMPdump 源代码分析 1: ...

  9. Android系统默认Home应用程序(Launcher)的启动过程源代码分析(3)

    Step 13.  ActivityStack.startActivityLocked 这个函数定义在frameworks/base/services/java/com/android/server/ ...

  10. 区块链教程Fabric1.0源代码分析scc(系统链码)

    区块链教程Fabric1.0源代码分析scc(系统链码),2018年下半年,区块链行业正逐渐褪去发展之初的浮躁.回归理性,表面上看相关人才需求与身价似乎正在回落.但事实上,正是初期泡沫的渐退,让人们更 ...

最新文章

  1. Android_(消息提示)多种使用Toast的消息提示
  2. 08. 函数和流程控制
  3. stateful function用法记录(还没弄完)
  4. 协方差公式性质证明过程_论文推荐 | 刘志平:等价条件平差模型的方差-协方差分量最小二乘估计方法...
  5. alexeyab darknet 编译_【目标检测实战】Darknet—yolov3模型训练(VOC数据集)
  6. python的json中方法以及jsonpath模块
  7. Java静态代理、动态代理以及CGLIB动态代理
  8. 用new/delete动态创建数组| 一维/二维/三维
  9. 2952 细胞分裂 2
  10. python动态演示数据gdp_荐爬取世界各国历年的GDP数据
  11. c语言程序从源代码到二进制机器码,C语言源代码转变为可执行程序的过程
  12. 军用软件开发周期和文档
  13. Java实现QQ、微信等自动无限发送消息(炸屏)
  14. c15语言中不支持十六进制的数据,从十六进制字符串中提取数据
  15. Java利用mpxj解析mpp格式文件
  16. 医疗行业能否成功入场直播带货?
  17. 电信号码呼叫转移设置和取消
  18. TCP可靠传输-拥塞控制
  19. 发一个随机红包 100块钱给10个人 每个人最多12块钱 最少6块钱 怎么分
  20. pytorch——冻结某层参数

热门文章

  1. 九大背包问题专题--完全背包问题(详解,最优解)
  2. 微信小程序之CSS实现图片遮罩
  3. openwrt mt7620a添加wifi指示灯(wled)的方法
  4. mysql事件探查器_SQL2005事件探查器中的Reads数据很大是怎么回事?
  5. 进程系列(一)-进程基本概念
  6. parse error, unexpected TOKBEGIN, expecting AFFECT or SEMICOLON
  7. TinyOS数据帧与CC2420 Radio Stack解读
  8. 【一文读懂】python 中的 numpy.reshape(a, newshape, order=‘C‘) 详细说明及实例讲解
  9. 计算机工程好发论文么,计算机工程期刊发表论文有什么要求
  10. 番茄ToDo帮助文档