探秘腾讯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. java微信开发

    所谓的微信开发就是在微信开发模式之下,对微信进行公众号和企业号的扩展开发.     如果要让你的微信公众号有更多的功能,比如菜单支持,自动的信息服务,查询,消息推送等,就必须开启微信的开发模式.进入微 ...

  2. Android-将RGB彩色图转换为灰度图

    package com.example.yanlei.wifi; import android.graphics.Bitmap; import android.graphics.BitmapFacto ...

  3. 【转】提高C#编程水平的50个要点

    1.总是用属性 (Property) 来代替可访问的数据成员2.在 readonly 和 const 之间,优先使用 readonly3.在 as 和 强制类型转换之间,优先使用 as 操作符4.使用 ...

  4. Sharepoint学习笔记—习题系列--70-573习题解析 -(Q63-Q65)

    Question 63You have a SharePoint farm that has more than 100 custom Features.You upgrade several Fea ...

  5. Fresco 使用笔记(一):加载gif图片并播放

    项目总结 --------------------------------------------------------------------- 前言: 项目中图文混合使用的太多太多了,但是绝大部 ...

  6. C语言柔性数组

    结构中最后一个元素允许是未知大小的数组,这个数组就是柔性数组.但结构中的柔性数组前面必须至少一个其他成员,柔性数组成员允许结构中包含一个大小可变的数组,sizeof返回的这种结构大小不包括柔性数组的内 ...

  7. Hibernate之Criteria的完整用法

    Criteria的完整用法 QBE (Query By Example) Criteria cri = session.createCriteria(Student.class); cri.add(E ...

  8. Effective Java 14 In public classes, use accessor methods, not public fields

    Principle To offer the benefits of encapsulation you should always expose private field with public ...

  9. SQLServer中登录名的用户名配置

    其实这个问题困扰我很久了. 今夏(13.7)实习的时候第一次接触sqlserver 当时是统一安排,按部就班的做就行. 那时候链接数据库用的id是sa. 后来自己做小程序时候举得不管什么都用sa登录好 ...

  10. springMVC 接收数组参数,mybatis 接收数组参数,mybatis批量插入/批量删除案例

    案例是给一个用户赋予多个权限,多个权限用其对应的主键 id 为参数,组成了 一个id数组,传给springMVC,然后springMVC传给mybatis,然后mybatis批量插入.其实类似的场景还 ...