Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> Android開發實例 >> 探秘騰訊Android手機游戲平台之不安裝游戲APK直接啟動法

探秘騰訊Android手機游戲平台之不安裝游戲APK直接啟動法

編輯:Android開發實例

前言

相信這樣一個問題,大家都不會陌生,

“有什麼的方法可以使Android的程序APK不用安裝,而能夠直接啟動”。

發現最後的結局都是不能實現這個美好的願望,而騰訊Android手機游戲平台卻又能實現這個功能,下載的連連看,五子棋都沒有安裝過程,但是都能直接運行,這其中到底有什麼“玄機”呢,也有熱心童鞋問過我這個問題,本文就為大家來揭開這個謎團。

實踐

我實現了一個小小的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卡上)的截圖,細心的同學可以停下來觀察一下他們之間的不同
  4. 後兩幅圖片的不同,也即Title的不同,則解釋出了我們將要分析的後台實現原理的機制

實現原理

最能講明白道理的莫過於源碼了,下面我們就來分析一下A和B的實現機制,首先來分析TestA.apk的主要代碼實現:

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

代碼解析:這就是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.  
  9.         if ((plocalObject.activities != null)  
  10.                 && (plocalObject.activities.length > 0)) {  
  11.             String activityname = plocalObject.activities[0].name;  
  12.             Log.d(TAG, "activityname = " + activityname);  
  13.  
  14.             Class localClass = localDexClassLoader.loadClass(activityname);  
  15.             Constructor localConstructor = localClass  
  16.                     .getConstructor(new Class[] {});  
  17.             Object instance = localConstructor.newInstance(new Object[] {});  
  18.             Log.d(TAG, "instance = " + instance);  
  19.  
  20.             Method localMethodSetActivity = localClass.getDeclaredMethod(  
  21.                     "setActivity", new Class[] { Activity.class });  
  22.             localMethodSetActivity.setAccessible(true);  
  23.             localMethodSetActivity.invoke(instance, new Object[] { this });  
  24.  
  25.             Method methodonCreate = localClass.getDeclaredMethod(  
  26.                     "onCreate", new Class[] { Bundle.class });  
  27.             methodonCreate.setAccessible(true);  
  28.             methodonCreate.invoke(instance, new Object[] { paramBundle });  
  29.         }  
  30.         return;  
  31.     } catch (Exception ex) {  
  32.         ex.printStackTrace();  
  33.     }  
  34. }  

代碼解析:這個函數要做的工作如下:加載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.  
  5.     @Override 
  6.     public void onCreate(Bundle savedInstanceState) {  
  7.         boolean b = false;  
  8.         if (savedInstanceState != null) {  
  9.             b = savedInstanceState.getBoolean("KEY_START_FROM_OTHER_ACTIVITY", false);  
  10.             if (b) {  
  11.                 this.otherActivity.setContentView(new TBSurfaceView(  
  12.                         this.otherActivity));  
  13.             }  
  14.         }  
  15.         if (!b) {  
  16.             super.onCreate(savedInstanceState);  
  17.             // setContentView(R.layout.main);  
  18.             setContentView(new TBSurfaceView(this));  
  19.         }  
  20.     }  
  21.  
  22.     public void setActivity(Activity paramActivity) {  
  23.         Log.d(TAG, "setActivity..." + paramActivity);  
  24.         this.otherActivity = paramActivity;  
  25.     }  
  26. }  

代碼解析:看完程序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.    
  7.     public TBSurfaceView(Context context) {  
  8.         super(context);  
  9.         th = new Thread(this);  
  10.         sfh = this.getHolder();  
  11.         sfh.addCallback(this);  
  12.         paint = new Paint();  
  13.         paint.setAntiAlias(true);  
  14.         paint.setColor(Color.RED);  
  15.         this.setKeepScreenOn(true);  
  16.     }  
  17.    
  18.     public void surfaceCreated(SurfaceHolder holder) {  
  19.         th.start();  
  20.     }  
  21.    
  22.     private void draw() {  
  23.         try {  
  24.             canvas = sfh.lockCanvas();  
  25.             if (canvas != null) {  
  26.                 canvas.drawColor(Color.WHITE);  
  27.                 canvas.drawText("Time: " + System.currentTimeMillis(), 100,  
  28.                         100, paint);  
  29.             }  
  30.         } catch (Exception ex) {  
  31.             ex.printStackTrace();  
  32.         } finally {  
  33.             if (canvas != null) {  
  34.                 sfh.unlockCanvasAndPost(canvas);  
  35.             }  
  36.         }  
  37.     }  
  38.    
  39.     public void run() {  
  40.         while (true) {  
  41.             draw();  
  42.             try {  
  43.                 Thread.sleep(100);  
  44.             } catch (InterruptedException e) {  
  45.                 e.printStackTrace();  
  46.             }  
  47.         }  
  48.     }  
  49.    
  50.     public void surfaceChanged(SurfaceHolder holder, int format, int width,  
  51.             int height) {  
  52.     }  
  53.    
  54.     public void surfaceDestroyed(SurfaceHolder holder) {  
  55.     }  
  56. }  
 

騰訊游戲平台解析

說了這麼多,都是背景,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/

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved