<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>润物无声 &#187; Game</title>
	<atom:link href="http://blog.zhourunsheng.com/tag/game/feed/" rel="self" type="application/rss+xml" />
	<link>http://blog.zhourunsheng.com</link>
	<description>天空一朵雨做的云</description>
	<lastBuildDate>Sat, 08 May 2021 05:17:21 +0000</lastBuildDate>
	<language>zh-CN</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>https://wordpress.org/?v=4.1.41</generator>
	<item>
		<title>探秘腾讯Android手机游戏平台之不安装游戏APK直接启动法</title>
		<link>http://blog.zhourunsheng.com/2011/09/%e6%8e%a2%e7%a7%98%e8%85%be%e8%ae%afandroid%e6%89%8b%e6%9c%ba%e6%b8%b8%e6%88%8f%e5%b9%b3%e5%8f%b0%e4%b9%8b%e4%b8%8d%e5%ae%89%e8%a3%85%e6%b8%b8%e6%88%8fapk%e7%9b%b4%e6%8e%a5%e5%90%af%e5%8a%a8%e6%b3%95/</link>
		<comments>http://blog.zhourunsheng.com/2011/09/%e6%8e%a2%e7%a7%98%e8%85%be%e8%ae%afandroid%e6%89%8b%e6%9c%ba%e6%b8%b8%e6%88%8f%e5%b9%b3%e5%8f%b0%e4%b9%8b%e4%b8%8d%e5%ae%89%e8%a3%85%e6%b8%b8%e6%88%8fapk%e7%9b%b4%e6%8e%a5%e5%90%af%e5%8a%a8%e6%b3%95/#comments</comments>
		<pubDate>Sun, 18 Sep 2011 08:36:18 +0000</pubDate>
		<dc:creator><![CDATA[润物无声]]></dc:creator>
				<category><![CDATA[移动开发]]></category>
		<category><![CDATA[Android]]></category>
		<category><![CDATA[Game]]></category>

		<guid isPermaLink="false">http://blog.zhourunsheng.com/?p=920</guid>
		<description><![CDATA[<p>前言 相信这样一个问题，大家都不会陌生， “有什么的方法可以使Android的程序APK不用安装，而能够直接启 [&#8230;]</p>
<p><a rel="nofollow" href="http://blog.zhourunsheng.com/2011/09/%e6%8e%a2%e7%a7%98%e8%85%be%e8%ae%afandroid%e6%89%8b%e6%9c%ba%e6%b8%b8%e6%88%8f%e5%b9%b3%e5%8f%b0%e4%b9%8b%e4%b8%8d%e5%ae%89%e8%a3%85%e6%b8%b8%e6%88%8fapk%e7%9b%b4%e6%8e%a5%e5%90%af%e5%8a%a8%e6%b3%95/">探秘腾讯Android手机游戏平台之不安装游戏APK直接启动法</a>，首发于<a rel="nofollow" href="http://blog.zhourunsheng.com">润物无声</a>。</p>
]]></description>
				<content:encoded><![CDATA[<h3>前言</h3>
<p>相信这样一个问题，大家都不会陌生，</p>
<blockquote><p>“有什么的方法可以使Android的程序APK不用安装，而能够直接启动”。</p></blockquote>
<p>发现最后的结局都是不能实现这个美好的愿望，而腾讯Android手机游戏平台却又能实现这个功能，下载的连连看，五子棋都没有安装过程，但是都能直接运行，这其中到底有什么“玄机”呢，也有热心童鞋问过我这个问题，本文就为大家来揭开这个谜团。<span id="more-920"></span></p>
<blockquote>
<h2>重要说明</h2>
<p>在实践的过程中大家都会发现资源引用的问题，这里重点声明两点：<br />
1. 资源文件是不能直接inflate的，如果简单的话直接在程序中用代码书写。<br />
2. 资源文件是不能用R来引用的，因为上下文已经不同了，腾讯的做法是将资源文件打包(*.pak文件和APK打包在一起)，虽然APK是没有进行安装，但是资源文件是另外解压到指定文件夹下面的，然后将文件夹的地址传给了第三方应用程序，这样第三方应用程序通过File的inputstream流还是可以读取和使用这些资源的。</p></blockquote>
<h3>实践</h3>
<p>我实现了一个小小的Demo，麻雀虽小五脏俱全，为了突出原理，我就尽量简化了程序，通过这个实例来让大家明白后台的工作原理。</p>
<ol>
<li>下载demo的apk程序<a href="http://blog.zhourunsheng.com/wp-content/uploads/2011/09/apks.zip">apks</a>，其中包括了两个apk，分别是A和B</li>
<li>这两个APK可分别安装和运行，A程序界面只显示一个Button，B程序界面会动态显示当前的时间</li>
<li>下面的三幅图片分别为直接启动运行A程序（安装TestA.apk），直接启动运行B程序（安装TestB.apk）和由A程序动态启动B程序（安装TestA.apk，TestB.apk不用安装，而是放在/mnt/sdcard/目录中，即 SD卡上）的截图，细心的同学可以停下来观察一下他们之间的不同<br />
<img class="alignnone size-medium wp-image-924" title="device-2011-09-18-A" src="http://blog.zhourunsheng.com/wp-content/uploads/2011/09/device-2011-09-18-A-200x300.png" alt="" width="200" height="300" /><img class="alignnone size-medium wp-image-925" title="device-2011-09-18-B" src="http://blog.zhourunsheng.com/wp-content/uploads/2011/09/device-2011-09-18-B-200x300.png" alt="" width="200" height="300" /><img class="alignnone size-medium wp-image-926" title="device-2011-09-18-AB" src="http://blog.zhourunsheng.com/wp-content/uploads/2011/09/device-2011-09-18-AB-200x300.png" alt="" width="200" height="300" /></li>
<li>后两幅图片的不同，也即Title的不同，则解释出了我们将要分析的后台实现原理的机制</li>
</ol>
<h3>实现原理</h3>
<p>最能讲明白道理的莫过于源码了，下面我们就来分析一下A和B的实现机制，首先来分析TestA.apk的主要代码实现：</p>
<pre>	@Override
	public void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.main);

		Button btn = (Button) findViewById(R.id.btn);
		btn.setOnClickListener(new OnClickListener() {

			@Override
			public void onClick(View v) {
				Bundle paramBundle = new Bundle();
				paramBundle.putBoolean("KEY_START_FROM_OTHER_ACTIVITY", true);
				String dexpath = "/mnt/sdcard/TestB.apk";
				String dexoutputpath = "/mnt/sdcard/";
				LoadAPK(paramBundle, dexpath, dexoutputpath);
			}
		});
	}</pre>
<p><strong>代码解析</strong>：这就是OnCreate函数要做的事情，装载view界面，绑定button事件，大家都熟悉了，还有就是设置程序B的放置路径，因为我程序中代码是从/mnt/sdcard/TestB.apk中动态加载，这也就是为什么要让大家把TestB.apk放在SD卡上面的原因了。关键的函数就是最后一个了LoadAPK，它来实现动态加载B程序。</p>
<pre>	public void LoadAPK(Bundle paramBundle, String dexpath, String dexoutputpath) {
		ClassLoader localClassLoader = ClassLoader.getSystemClassLoader();
		DexClassLoader localDexClassLoader = new DexClassLoader(dexpath,
				dexoutputpath, null, localClassLoader);
		try {
			PackageInfo plocalObject = getPackageManager()
					.getPackageArchiveInfo(dexpath, 1);

			if ((plocalObject.activities != null)
					&amp;&amp; (plocalObject.activities.length &gt; 0)) {
				String activityname = plocalObject.activities[0].name;
				Log.d(TAG, "activityname = " + activityname);

				Class localClass = localDexClassLoader.loadClass(activityname);
				Constructor localConstructor = localClass
						.getConstructor(new Class[] {});
				Object instance = localConstructor.newInstance(new Object[] {});
				Log.d(TAG, "instance = " + instance);

				Method localMethodSetActivity = localClass.getDeclaredMethod(
						"setActivity", new Class[] { Activity.class });
				localMethodSetActivity.setAccessible(true);
				localMethodSetActivity.invoke(instance, new Object[] { this });

				Method methodonCreate = localClass.getDeclaredMethod(
						"onCreate", new Class[] { Bundle.class });
				methodonCreate.setAccessible(true);
				methodonCreate.invoke(instance, new Object[] { paramBundle });
			}
			return;
		} catch (Exception ex) {
			ex.printStackTrace();
		}
	}</pre>
<p><strong>代码解析</strong>：这个函数要做的工作如下：加载B程序的APK文件，通过类加载器DexClassLoader来解析APK文件，这样会在SD卡上面生成一个同名的后缀为dex的文件，例如/mnt/sdcard/TestB.apk==&gt;/mnt/sdcard/TestB.dex,接下来就是通过java反射机制，动态实例化B中的Activity对象，并依次调用了其中的两个函数，分别为setActivity和onCreate.看到这里，大家是不是觉得有点奇怪，Activity的启动函数是onCreate，为什么要先调用setActivity，而更奇怪的是setActivity并不是系统的函数，确实，那是我们自定义的，这也就是核心的地方。</p>
<p>好了带着这些疑问，我们再来分析B程序的主代码：</p>
<pre>public class TestBActivity extends Activity {
	private static final String TAG = "TestBActivity";
	private Activity otherActivity;

	@Override
	public void onCreate(Bundle savedInstanceState) {
		boolean b = false;
		if (savedInstanceState != null) {
			b = savedInstanceState.getBoolean("KEY_START_FROM_OTHER_ACTIVITY", false);
			if (b) {
				this.otherActivity.setContentView(new TBSurfaceView(
						this.otherActivity));
			}
		}
		if (!b) {
			super.onCreate(savedInstanceState);
			// setContentView(R.layout.main);
			setContentView(new TBSurfaceView(this));
		}
	}

	public void setActivity(Activity paramActivity) {
		Log.d(TAG, "setActivity..." + paramActivity);
		this.otherActivity = paramActivity;
	}
}</pre>
<p><strong>代码解析</strong>：看完程序B的实现机制，大家是不是有种恍然大悟的感觉，这根本就是“偷梁换柱”嘛，是滴，程序B动态借用了程序A的上下文执行环境，这也就是上面后两幅图的差异，最后一幅图运行的是B的程序，但是title表示的却是A的信息，而没有重新初始化自己的，实际上这也是不可能的，所以有些童鞋虽然通过java的反射机制，正确呼叫了被调程序的onCreate函数，但是期望的结果还是没有出现，原因就是这个上下文环境没有正确建立起来，但是若通过startActivity的方式来启动APK的话，android系统会替你建立正确的执行时环境，所以就没问题。至于那个TBSurfaceView，那就是自定义的一个view画面，动态画当前的时间</p>
<pre>public class TBSurfaceView extends SurfaceView implements Callback, Runnable {
	private SurfaceHolder sfh;
	private Thread th;
	private Canvas canvas;
	private Paint paint;

	public TBSurfaceView(Context context) {
		super(context);
		th = new Thread(this);
		sfh = this.getHolder();
		sfh.addCallback(this);
		paint = new Paint();
		paint.setAntiAlias(true);
		paint.setColor(Color.RED);
		this.setKeepScreenOn(true);
	}

	public void surfaceCreated(SurfaceHolder holder) {
		th.start();
	}

	private void draw() {
		try {
			canvas = sfh.lockCanvas();
			if (canvas != null) {
				canvas.drawColor(Color.WHITE);
				canvas.drawText("Time: " + System.currentTimeMillis(), 100,
						100, paint);
			}
		} catch (Exception ex) {
			ex.printStackTrace();
		} finally {
			if (canvas != null) {
				sfh.unlockCanvasAndPost(canvas);
			}
		}
	}

	public void run() {
		while (true) {
			draw();
			try {
				Thread.sleep(100);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	}

	public void surfaceChanged(SurfaceHolder holder, int format, int width,
			int height) {
	}

	public void surfaceDestroyed(SurfaceHolder holder) {
	}
}</pre>
<h3>腾讯游戏平台解析</h3>
<p>说了这么多，都是背景，O(∩_∩)O哈哈~</p>
<p>其实腾讯游戏平台就是这么个实现原理，我也是通过它才学习到这种方式的，还得好好感谢感谢呢。</p>
<p>腾讯Android游戏平台的游戏分成两类，第一类是腾讯自主研发的，像斗地主，五子棋，连连看什么的，所以实现机制就如上面的所示，A代表游戏大厅，B代表斗地主类的小游戏。第二类是第三方软件公司开发的，可就不能已这种方式来运作了，毕竟腾讯不能限制别人开发代码的方式啊，所以腾讯就开放了一个sdk包出来，让第三方应用可以和游戏大厅相结合，具体可参见<a href="http://dev.g.qq.com/res_sdk.html">QQ游戏中心开发者平台</a>，但这同时就损失了一个优点，那就是第三方开发的游戏要通过安装的方式才能运行。</p>
<h3>结论</h3>
<p>看到这里，相信大家都比较熟悉这个背后的原理了吧，也希望大家能提供更好的反馈信息！</p>
<p>程序源码下载<a href="http://blog.zhourunsheng.com/wp-content/uploads/2011/09/source.zip">source</a></p>
<p><a rel="nofollow" href="http://blog.zhourunsheng.com/2011/09/%e6%8e%a2%e7%a7%98%e8%85%be%e8%ae%afandroid%e6%89%8b%e6%9c%ba%e6%b8%b8%e6%88%8f%e5%b9%b3%e5%8f%b0%e4%b9%8b%e4%b8%8d%e5%ae%89%e8%a3%85%e6%b8%b8%e6%88%8fapk%e7%9b%b4%e6%8e%a5%e5%90%af%e5%8a%a8%e6%b3%95/">探秘腾讯Android手机游戏平台之不安装游戏APK直接启动法</a>，首发于<a rel="nofollow" href="http://blog.zhourunsheng.com">润物无声</a>。</p>
]]></content:encoded>
			<wfw:commentRss>http://blog.zhourunsheng.com/2011/09/%e6%8e%a2%e7%a7%98%e8%85%be%e8%ae%afandroid%e6%89%8b%e6%9c%ba%e6%b8%b8%e6%88%8f%e5%b9%b3%e5%8f%b0%e4%b9%8b%e4%b8%8d%e5%ae%89%e8%a3%85%e6%b8%b8%e6%88%8fapk%e7%9b%b4%e6%8e%a5%e5%90%af%e5%8a%a8%e6%b3%95/feed/</wfw:commentRss>
		<slash:comments>55</slash:comments>
		</item>
	</channel>
</rss>
