探秘腾讯Android手机游戏平台之不安装游戏APK直接启动法

重要说明

在实践的过程中大家都会发现资源引用的问题,这里重点声明两点:
1. 资源文件是不能直接inflate的,如果简单的话直接在程序中用代码书写。
2.
资源文件是不能用R来引用的,因为上下文已经不同了,腾讯的做法是将资源文件打包(*.pak文件和APK打包在一起),虽然APK是没有进行安装,但是

资源文件是另外解压到指定文件夹下面的,然后将文件夹的地址传给了第三方应用程序,这样第三方应用程序通过File的inputstream流还是可以读
取和使用这些资源的。

实践

我实现了一个小小的Demo,麻雀虽小五脏俱全,为了突出原理,我就尽量简化了程序,通过这个实例来让大家明白后台的工作原理。

1、下载demo的apk程序apks ,其中包括了两个apk,分别是A和B

2、这两个APK可分别安装和运行,A程序界面只显示一个Button,B程序界面会动态显示当前的时间

3、下面的三幅图片分别为直接启动运行A程序(安装TestA.apk),直接启动运行B程序(安装TestB.apk)和由A程序动态启动B程序
(安装TestA.apk,TestB.apk不用安装,而是放在/mnt/sdcard/目录中,即
SD卡上)的截图,细心的同学可以停下来观察一下他们之间的不同

后两幅图片的不同,也即Title的不同,则解释出了我们将要分析的后台实现原理的机制

实现原理

  1. @Override
  2. public void onCreate(Bundle savedInstanceState) {
  3. super.onCreate(savedInstanceState);
  4. setContentView(R.layout.main);
  5. Button btn = (Button) findViewById(R.id.btn);
  6. btn.setOnClickListener(new OnClickListener() {
  7. @Override
  8. public void onClick(View v) {
  9. Bundle paramBundle = new Bundle();
  10. paramBundle.putBoolean("KEY_START_FROM_OTHER_ACTIVITY", true);
  11. String dexpath = "/mnt/sdcard/TestB.apk";
  12. String dexoutputpath = "/mnt/sdcard/";
  13. LoadAPK(paramBundle, dexpath, dexoutputpath);
  14. }
  15. });
  16. }

代码解析:这就是OnCreate函数要做的事情,装载view界面,绑定button事件,大家都熟悉了,还有就是设置程序B的放置路径,因为我程序中
代码是从
/mnt/sdcard/TestB.apk中动态加载,这也就是为什么要让大家把TestB.apk放在SD卡上面的原因了。关键的函数就是最后一个了
LoadAPK,它来实现动态加载B程序。

  1. public void LoadAPK(Bundle paramBundle, String dexpath, String dexoutputpath) {
  2. ClassLoader localClassLoader = ClassLoader.getSystemClassLoader();
  3. DexClassLoader localDexClassLoader = new DexClassLoader(dexpath,
  4. dexoutputpath, null, localClassLoader);
  5. try {
  6. PackageInfo plocalObject = getPackageManager()
  7. .getPackageArchiveInfo(dexpath, 1);
  8. if ((plocalObject.activities != null)
  9. && (plocalObject.activities.length > 0)) {
  10. String activityname = plocalObject.activities[0].name;
  11. Log.d(TAG, "activityname = " + activityname);
  12. Class localClass = localDexClassLoader.loadClass(activityname);
  13. Constructor localConstructor = localClass
  14. .getConstructor(new Class[] {});
  15. Object instance = localConstructor.newInstance(new Object[] {});
  16. Log.d(TAG, "instance = " + instance);
  17. Method localMethodSetActivity = localClass.getDeclaredMethod(
  18. "setActivity", new Class[] { Activity.class });
  19. localMethodSetActivity.setAccessible(true);
  20. localMethodSetActivity.invoke(instance, new Object[] { this });
  21. Method methodonCreate = localClass.getDeclaredMethod(
  22. "onCreate", new Class[] { Bundle.class });
  23. methodonCreate.setAccessible(true);
  24. methodonCreate.invoke(instance, new Object[] { paramBundle });
  25. }
  26. return;
  27. } catch (Exception ex) {
  28. ex.printStackTrace();
  29. }
  30. }

代码解析:这个函数要做的工作如下:加载B程序的APK文件,通过类加载器DexClassLoader来解析APK文件,这样会在SD卡上面生成一个同
名的
后缀为dex的文件,例如/mnt/sdcard/TestB.apk==>/mnt/sdcard/TestB.dex,接下来就是通过java

反射机制,动态实例化B中的Activity对象,并依次调用了其中的两个函数,分别为setActivity和onCreate.看到这里,大家是不是

觉得有点奇怪,Activity的启动函数是onCreate,为什么要先调用setActivity,而更奇怪的是setActivity并不是系统的
函数,确实,那是我们自定义的,这也就是核心的地方。

好了带着这些疑问,我们再来分析B程序的主代码:

  1. public class TestBActivity extends Activity {
  2. private static final String TAG = "TestBActivity";
  3. private Activity otherActivity;
  4. @Override
  5. public void onCreate(Bundle savedInstanceState) {
  6. boolean b = false;
  7. if (savedInstanceState != null) {
  8. b = savedInstanceState.getBoolean("KEY_START_FROM_OTHER_ACTIVITY", false);
  9. if (b) {
  10. this.otherActivity.setContentView(new TBSurfaceView(
  11. this.otherActivity));
  12. }
  13. }
  14. if (!b) {
  15. super.onCreate(savedInstanceState);
  16. // setContentView(R.layout.main);
  17. setContentView(new TBSurfaceView(this));
  18. }
  19. }
  20. public void setActivity(Activity paramActivity) {
  21. Log.d(TAG, "setActivity..." + paramActivity);
  22. this.otherActivity = paramActivity;
  23. }
  24. }

代码解析:看完程序B的实现机制,大家是不是有种恍然大悟的感觉,这根本就是“偷梁换柱”嘛,是滴,程序B动态借用了程序A的上下文执行环境,这也就是上
面后两幅图
的差异,最后一幅图运行的是B的程序,但是title表示的却是A的信息,而没有重新初始化自己的,实际上这也是不可能的,所以有些童鞋虽然通过java
的反射机制,正确呼叫了被调程序的onCreate函数,但是期望的结果还是没有出现,原因就是这个上下文环境没有正确建立起来,但是若通过
startActivity的方式来启动APK的话,android系统会替你建立正确的执行时环境,所以就没问题。至于那个
TBSurfaceView,那就是自定义的一个view画面,动态画当前的时间

  1. public class TBSurfaceView extends SurfaceView implements Callback, Runnable {
  2. private SurfaceHolder sfh;
  3. private Thread th;
  4. private Canvas canvas;
  5. private Paint paint;
  6. public TBSurfaceView(Context context) {
  7. super(context);
  8. th = new Thread(this);
  9. sfh = this.getHolder();
  10. sfh.addCallback(this);
  11. paint = new Paint();
  12. paint.setAntiAlias(true);
  13. paint.setColor(Color.RED);
  14. this.setKeepScreenOn(true);
  15. }
  16. public void surfaceCreated(SurfaceHolder holder) {
  17. th.start();
  18. }
  19. private void draw() {
  20. try {
  21. canvas = sfh.lockCanvas();
  22. if (canvas != null) {
  23. canvas.drawColor(Color.WHITE);
  24. canvas.drawText("Time: " + System.currentTimeMillis(), 100,
  25. 100, paint);
  26. }
  27. } catch (Exception ex) {
  28. ex.printStackTrace();
  29. } finally {
  30. if (canvas != null) {
  31. sfh.unlockCanvasAndPost(canvas);
  32. }
  33. }
  34. }
  35. public void run() {
  36. while (true) {
  37. draw();
  38. try {
  39. Thread.sleep(100);
  40. } catch (InterruptedException e) {
  41. e.printStackTrace();
  42. }
  43. }
  44. }
  45. public void surfaceChanged(SurfaceHolder holder, int format, int width,
  46. int height) {
  47. }
  48. public void surfaceDestroyed(SurfaceHolder holder) {
  49. }
  50. }

平台解析:

说了这么多,都是背景,O(∩_∩)O哈哈~

其实腾讯游戏平台就是这么个实现原理,我也是通过它才学习到这种方式的,还得好好感谢感谢呢。

腾讯Android游戏平台的游戏分成两类,第一类是腾讯自主研发的,像斗地主,五子棋,连连看什么的,所以实现机制就如上面的所示,A代表游戏大
厅,B代表斗地主类的小游戏。第二类是第三方软件公司开发的,可就不能已这种方式来运作了,毕竟腾讯不能限制别人开发代码的方式啊,所以腾讯就开放了一个
sdk包出来,让第三方应用可以和游戏大厅相结合,具体可参见QQ游戏中心开发者平台 ,但这同时就损失了一个优点,那就是第三方开发的游戏要通过安装的方式才能运行。

结论:

看到这里,相信大家都比较熟悉这个背后的原理了吧,也希望大家能提供更好的反馈信息!

程序源码下载source

来源: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--代码安装、获取资源及Intent调用已安装apk

Android应用开发提高系列(4)——Android动态加载(上)——加载未安装APK中的类

Android应用开发提高系列(5)——Android动态加载(下)——加载已安装APK中的类和资源

前言

  近期做换肤功能,由于换肤程度较高,受限于平台本身,实现起来较复杂,暂时搁置了该功能,但也积累了一些经验,将分两篇文章来写这部分的内容,欢迎交流!

  关键字:Android动态加载

声明

  欢迎转载,但请保留文章原始出处:)

    博客园:http://www.cnblogs.com

    农民伯伯: http://over140.cnblogs.com

    Android中文Wiki:http://wikidroid.sinaapp.com

正文

  一、前提

    目的:动态加载SD卡中Apk的类。

    注意:被加载的APK是未安装的。

    相关:本文是本博另外一篇文章:Android动态加载jar/dex的升级版。

    截图: 成功截图:

      

  二、准备

    准备调用Android工程:TestB

    ITest

public interface ITest {
    String getMoney();
}

    TestBActivity

public class TestBActivity extends Activity implements ITest {
    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
    }

@Override
    public String getMoney() {
        return "1";
    }

}

    代码说明:很简单的代码。将生成后的TestB.apk拷贝到SD卡的根目录下。

  三、调用

    调用工程TestA

public class TestAActivity extends Activity {
    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);

String path = Environment.getExternalStorageDirectory() + "/";
        String filename = "TestB.apk";
        DexClassLoader classLoader = new DexClassLoader(path + filename, path,
                null, getClassLoader());

try {
            Class mLoadClass = classLoader.loadClass("com.nmbb.TestBActivity");
            Constructor constructor = mLoadClass.getConstructor(new Class[] {});
            Object TestBActivity = constructor.newInstance(new Object[] {});
            
            Method getMoney = mLoadClass.getMethod("getMoney", null);
            getMoney.setAccessible(true);
            Object money = getMoney.invoke(TestBActivity, null);
            Toast.makeText(this, money.toString(), Toast.LENGTH_LONG).show();
            
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (SecurityException e) {
            e.printStackTrace();
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (IllegalArgumentException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }
    }
}

    执行的时候可以发现会自动生成TestB.dex文件。动态加载方面还可以搜索一下"Java动态加载"方面的资料,很有参考价值。可以发现比Android动态加载jar/dex使用起来方便得多。

  四、下载

    TestA.zip

    TestB.zip    

  五、注意

    6.1  别忘了加上SDCARD的写权限:

      android.permission.WRITE_EXTERNAL_STORAGE

    6.2  同样注意,不要再两个工程包含package和名称相同的接口,否则报错。(参见Android动态加载jar/dex的后期维护)

  六、扩展阅读

    探秘腾讯Android手机游戏平台之不安装游戏APK直接启动法

    (强烈推荐:QQ游戏动态调用Activity的方法:通过ClassLoader,loadClass
Activity类,然后分别在主工程的onDestroy、onKeyDown、onPause、onRestart、onResume等生命周期方法
中反射调用(Method、invoke)子工程的类方法来模拟实现整个生命周期。此外巧妙的通过解压缩APK文件来获取游戏的资源)

    Android中文Wiki:DexFile

  七、缺点

    6.1  由于是使用反射,无法取得Context,也就是TestBActivity与普通的类毫无区别,没有生命周期。

  八、推荐

    Android版 程序员专用搜索

Android 动态加载 (二) 态加载机制 案例二的更多相关文章

  1. Android 动态加载 (一) 态加载机制 案例一

    在目前的软硬件环境下,Native App与Web App在用户体验上有着明显的优势,但在实际项目中有些会因为业务的频繁变更而频繁的升级客户端,造成较差的用户体验,而这也恰恰是Web App的优势.本 ...

  2. [转载] Android动态加载Dex机制解析

    本文转载自: http://blog.csdn.net/wy353208214/article/details/50859422 1.什么是类加载器? 类加载器(class loader)是 Java ...

  3. Android动态加载技术初探

    一.前言: 现在,已经有实力强大的公司用这个技术开发应用了,比如淘宝,大众点评,百度地图等,之所以采用这个技术,实际上,就是方便更新功能,当然,前提是新旧功能的接口一致,不然会报Not Found等错 ...

  4. Android应用开发提高系列(4)——Android动态加载(上)——加载未安装APK中的类

    前言 近期做换肤功能,由于换肤程度较高,受限于平台本身,实现起来较复杂,暂时搁置了该功能,但也积累了一些经验,将分两篇文章来写这部分的内容,欢迎交流! 关键字:Android动态加载 声明 欢迎转载, ...

  5. Android动态加载jar/dex

    前言 在目前的软硬件环境下,Native App与Web App在用户体验上有着明显的优势,但在实际项目中有些会因为业务的频繁变更而频繁的升级客户端,造成较差的用户体验,而这也恰恰是Web App的优 ...

  6. android动态加载

    转载自: http://www.cnblogs.com/over140/archive/2012/03/29/2423116.html http://www.cnblogs.com/over140/a ...

  7. Android动态加载so文件

    在Android中调用动态库文件(*.so)都是通过jni的方式,而且往往在apk或jar包中调用so文件时,都要将对应so文件打包进apk或jar包,工程目录下图: 以上方式的存在的问题: 1.缺少 ...

  8. Android动态加载代码技术

    Android动态加载代码技术 在开发Android App的过程当中,可能希望实现插件式软件架构,将一部分代码以另外一个APK的形式单独发布,而在主程序中加载并执行这个APK中的代码. 实现这个任务 ...

  9. Android 动态加载(防止逆向编译) jar混淆加密

    最近工作中接到了一个研究防止逆向编译的任务.研究了几天资料,最后基本实现了防破解技术,在这个工程中,也略有一些心得体会,现整理下来分享,供大家探讨参考研究.文中如有纰漏.失实之处,请大家及时给与指正. ...

随机推荐

  1. jQuery Flickerplate 幻灯片

    Flickerplate 是个轻量级 jQuery 插件,大小仅为 12 kb.它允许用户点击鼠标然后转换内容,非常容易使用,响应式,支持触摸设备 在线实例 默认 圆点导航位置 动画方式 深色主题 H ...

  2. html5 大幅度地增加和改良input元素的种类

    增加和改良input元素 url类型.email类型.date类型.time类型.datetime类型.datetime-local类型.month类型.week类型.number类型.range类型 ...

  3. Js中的this指向问题

    函数中的this指向和当前函数在哪定义的或者在哪执行的都没有任何的关系分析this指向的规律如下: [非严格模式]1.自执行函数中的this永远是window [案例1] var obj={ fn:( ...

  4. javascript --- 词法分析

    JavaScript代码自上而下执行,但是在js代码执行前,会首先进行词法分析,所以事实上,js运行要分为词法分析和执行两个阶段. 词法分析主要分为三步: 第一步: 分析形参: 第二步: 分析变量声明 ...

  5. Flexbox实现垂直水平居中

    Flexbox(伸缩盒)是CSS3中新增的特性,利用这个属性可以解决页面中的居中问题.只需要3行代码就可以实现,不需要设置元素的尺寸,能够自适应页面. 这个方法只能在现代浏览器上有效,IE10+.ch ...

  6. JS框架的一些小总结

    闭包结构 为了防止和别的库的冲突,用闭包把整个框架安全地保护好. 我们待会的代码都写在里面.这里创建一个全局变量"window.O",就是在window对象里加个O,它等价于 &q ...

  7. android 事件

    package com.example.yanlei.my2; import android.app.Activity; import android.content.Context; import ...

  8. “Stamping” PDF Files Downloaded from SharePoint 2010

    http://blog.falchionconsulting.com/index.php/2012/03/stamping-pdf-files-downloaded-from-sharepoint-2 ...

  9. CocoaPod出现-bash: pod: command not found 解决办法

    从过年来到公司  就不用自己电脑了    之前一直自己带电脑   昨天随便建了一个demo   使用cocoapods  发现     -bash: pod: command not found 刚开 ...

  10. 如何让 UITableViewCell 中的 imageView 大小固定

    UITableView可以算是使用频率最高的组件之一的,在开发过程中经常需要展示一些简单的信息列表常见列表布局 如图,很多页面其实就是这种展示结果,通常需要imageView,textLabel,de ...