版权申明:未经允许请勿转载。转载前请先联系作者([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中。