某云游戏应用拆解

版权申明:未经允许请勿转载。转载前请先联系作者([email protected]

最近看到某个云游戏应用,资源开销特别低。
2%的CPU消耗,都让我怀疑它是不是云游戏了。

下面是拆解的记录,是个人笔记来着。

拆解

我现在能玩的只有一个免费的游戏(链接双子),我之前的测试也是基于这个游戏(2%的CPU消耗)。

游戏的界面的主Activity是PlayerActivitySW

com.mych.cloudgameclient.player.PlayerActivitySW
-> com.mych.cloudgameclient.player.PlayerActivity
-> com.badlogic.gdx.backends.android.AndroidApplication
-> android.app.Activity

观察到其中有用了开源项目:

https://github.com/libgdx/libgdx
https://github.com/libgdx/libgdx/wiki

这是个游戏开发框架。查了一下,这个框架支持TV、也支持手柄。看起来直接写游戏就可以了。

存疑点是,他们是云游戏,怎么扩展其他游戏呢??!这个假设可能不能成立?

继续看代码:

启动activity的时候,带了一串url给PlayerActivitySW,url的内容大概是这样的:

http://112.124.105.56:7007/mc/play?ac=smart-tcl&bit=4000&id=100&op=1&src=jiangsu4\
&token=dd18b72b3e4f7aa851805447&sign=188535d1be3f52b2e1890b95ed00247f

看起来是获取了地址和端口

{status:0,result:{sessionId:1805447-0605193458-603,ip:223.111.206.238,port:30265}}

获取IP和端口后,这样获取数据包

StringBuilder localStringBuilder = new StringBuilder();
localStringBuilder.append("game host: ");
localStringBuilder.append(paramString);
localStringBuilder.append(":");
localStringBuilder.append(paramInt);
Log.d("xlh*AbstractGamePlayer", localStringBuilder.toString());
this.Y = new Socket(InetAddress.getByName(paramString), paramInt);
this.Y.setTcpNoDelay(true);
this.Z = this.Y.getInputStream();
this.aa = this.Y.getOutputStream();
paramString = new StringBuilder();
paramString.append("connectServer Succ: mInputStream=[");
paramString.append(this.Z);
paramString.append("] mOutputStream=[");
paramString.append(this.aa);
paramString.append("]");
Log.d("xlh*AbstractGamePlayer", paramString.toString());

基于TCP,获取到数据流之后,喂给MediaCodec做H264解码。
键盘事件从主线程post到异步线程,然后在通过tcp通道回传给对应的机器

this.a = new Handler()
{
public void handleMessage(Message paramAnonymousMessage)
{
  if (paramAnonymousMessage.what != 1) {
    return;
  }
  try
  {
    if (a.this.b() != null)
    {
      // a.this.b() 就是上面的 getOutputStream
      a.this.b().write((byte[])paramAnonymousMessage.obj);
      return;
    }
  }
  catch (IOException paramAnonymousMessage)
  {
    StringBuilder localStringBuilder = new StringBuilder();
    localStringBuilder.append("send input fail e=");
    localStringBuilder.append(paramAnonymousMessage.getMessage());
    Log.e("xlh*AbstractGamePlayer", localStringBuilder.toString());
  }
}
};

看起来好像简单得过头了,重新梳理一下顺序;

1, 传递参数进 PlayerActivitySW,在其父类获取了参数

public void onCreate(Bundle paramBundle)
{
    super.onCreate(paramBundle);
    getWindow().setFlags(128, 128);
    paramBundle = getIntent();
    this.f = paramBundle.getStringExtra("url");
    // this.f like : http://112.124.105.56:7007/mc/play?ac=smart-tcl&bit=4000&id=100&op=1&src=jiangsu4&token=dd18b72b3e4f7aa851805447&sign=188535d1be3f52b2e1890b95ed00247f
    ...
}

在PlayerActivitySW 的oncreta 注册了一个surfaceView,在surfaceCreated回调中开启了一条线程。

protected void d()
{
this.ab = new Thread(this);
this.ab.start();
}

这里看到主体的业务流程都是在这条线程里面做了的。

public void run()
{
Object localObject = this.f; // 上文的url
int i1 = 0;
if (!a((String)localObject, false))// 方法a见下一个函数
{ 
  Log.e("xlh*AbstractGamePlayer", "launch game failed.");
  localObject = new StringBuilder();
  ((StringBuilder)localObject).append("������������(");
  ((StringBuilder)localObject).append(this.X);
  ((StringBuilder)localObject).append(":");
  ((StringBuilder)localObject).append(this.W);
  ((StringBuilder)localObject).append(")");
  localObject = ((StringBuilder)localObject).toString();
  org.greenrobot.eventbus.c.a().c(new h((String)localObject));
  l();
  m();
  return;
}
while ((i1 < 3) && (!this.u))
{
  i1 += 1;
  try
  {
    a(this.g, this.h); // 打开TCP链接,开启了游戏,重试三次
  }
  catch (Exception localException)
  {
    try
    {
      Thread.sleep(1000L);
    }
    catch (InterruptedException localInterruptedException)
    {
      localInterruptedException.printStackTrace();
    }
    Log.e("xlh*AbstractGamePlayer", "run connect failed!");
    localException.printStackTrace();
  }
}
if (this.P.b != -1) {
  q();
}
n();
this.v.sendEmptyMessage(2);
}

这条线程做了什么事情呢?

1,处理链接,获取实际的服务器地址和token

protected boolean a(String paramString, boolean paramBoolean)
{
StringBuilder localStringBuilder = new StringBuilder();
localStringBuilder.append("launchGame gameUrl = ");
localStringBuilder.append(paramString);
Log.d("xlh*AbstractGamePlayer", localStringBuilder.toString());
this.j = null;
try
{
  int i1 = b(b(paramString, paramBoolean));
  // b 使用了 HttpURLConnection ,获取了的数据结果是:{status:0,result:{sessionId:1805447-0605193458-603,ip:223.111.206.238,port:30265}}
  if (i1 == 0) {
    return true;
  }
  if (i1 == 1)
  {
    paramBoolean = a(this.j);
    return paramBoolean;
  }
  return false;
}
return false;
}

protected int b(String paramString)
{
    this.g = paramString.getJSONObject("result").getString("ip");
    this.h = paramString.getJSONObject("result").getInt("port");
    this.i = paramString.getJSONObject("result").getString("sessionId");
    // 缓存到全局变量中 g\h\i 中
}

2,打开TCP连接

StringBuilder localStringBuilder = new StringBuilder();
localStringBuilder.append("game host: ");
localStringBuilder.append(paramString);
localStringBuilder.append(":");
localStringBuilder.append(paramInt);
Log.d("xlh*AbstractGamePlayer", localStringBuilder.toString());
this.Y = new Socket(InetAddress.getByName(paramString), paramInt);
this.Y.setTcpNoDelay(true);
this.Z = this.Y.getInputStream();
this.aa = this.Y.getOutputStream();
paramString = new StringBuilder();
paramString.append("connectServer Succ: mInputStream=[");
paramString.append(this.Z);
paramString.append("] mOutputStream=[");
paramString.append(this.aa);
paramString.append("]");
Log.d("xlh*AbstractGamePlayer", paramString.toString());

3, 从TCP中读数据。区分出视屏的包,给到子类 PlayerActivitySW 去处理,区分出音频的包,给AudioPlayer去处理。

// 视屏处理
protected void a(byte[] paramArrayOfByte, int paramInt1, int paramInt2)
{
com.mych.cloudgameclient.decoder.a locala = this.x;
if (locala != null) {
  locala.onVideoFrameReceived(paramArrayOfByte, paramInt1, paramInt2, a, b);
}
}

4, 接收按键事件,传回到上面的流的OutputStream中。

小结下:

  1. 个人猜测是用了libgdx的框架来写游戏,把框架的东西拆成两半,一半在机顶盒上做,一半在远端机器上做。
  2. 主体流程都是用系统&硬件支持,所以app进程中的CPU开销特别低。
  3. 支持软解,但在盒子上没有用到,看到他们带了一个软解的so的包,也有代码一个分支在判断软解。

你可能感兴趣的:(Android)