轉:探祕騰訊Android手機遊戲平臺之不安裝遊戲APK直接啓動法

前言

相信這樣一個問題,你們都不會陌生,html

「有什麼的方法可使Android的程序APK不用安裝,而可以直接啓動」。java

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

重要說明

在實踐的過程當中你們都會發現資源引用的問題,這裏重點聲明兩點:
1. 資源文件是不能直接inflate的,若是簡單的話直接在程序中用代碼書寫。
2. 資源文件是不能用R來引用的,由於上下文已經不一樣了,騰訊的作法是將資源文件打包(*.pak文件和APK打包在一塊兒),雖然APK是沒有進行安裝,可是資源文件是另外解壓到指定文件夾下面的,而後將文件夾的地址傳給了第三方應用程序,這樣第三方應用程序經過File的inputstream流仍是能夠讀取和使用這些資源的。canvas

實踐

我實現了一個小小的Demo,麻雀雖小五臟俱全,爲了突出原理,我就儘可能簡化了程序,經過這個實例來讓你們明白後臺的工做原理。app

  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的主要代碼實現:ide

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
    @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);             }         });     }

代碼解析:這就是OnCreate函數要作的事情,裝載view界面,綁定button事件,你們都熟悉了,還有就是設置程序B的放置路徑,由於我程序中代碼是從/mnt/sdcard/TestB.apk中動態加載,這也就是爲何要讓你們把TestB.apk放在SD卡上面的緣由了。關鍵的函數就是最後一個了LoadAPK,它來實現動態加載B程序。函數

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
    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)                     && (plocalObject.activities.length > 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();         }     }

代碼解析:這個函數要作的工做以下:加載B程序的APK文件,經過類加載器DexClassLoader來解析APK文件,這樣會在SD卡上面生成一個同名的後綴爲dex的文件,例如/mnt/sdcard/TestB.apk==>/mnt/sdcard/TestB.dex,接下來就是經過java反射機制,動態實例化B中的Activity對象,並依次調用了其中的兩個函數,分別爲setActivity和onCreate.看到這裏,你們是否是以爲有點奇怪,Activity的啓動函數是onCreate,爲何要先調用setActivity,而更奇怪的是setActivity並非系統的函數,確實,那是咱們自定義的,這也就是核心的地方。學習

好了帶着這些疑問,咱們再來分析B程序的主代碼:this

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
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;     } }

代碼解析:看完程序B的實現機制,你們是否是有種恍然大悟的感受,這根本就是「偷樑換柱」嘛,是滴,程序B動態借用了程序A的上下文執行環境,這也就是上面後兩幅圖的差別,最後一幅圖運行的是B的程序,可是title表示的倒是A的信息,而沒有從新初始化本身的,實際上這也是不可能的,因此有些童鞋雖然經過java的反射機制,正確呼叫了被調程序的onCreate函數,可是指望的結果仍是沒有出現,緣由就是這個上下文環境沒有正確創建起來,可是若經過startActivity的方式來啓動APK的話,android系統會替你創建正確的執行時環境,因此就沒問題。至於那個TBSurfaceView,那就是自定義的一個view畫面,動態畫當前的時間spa

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
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) {     } }

騰訊遊戲平臺解析

說了這麼多,都是背景,O(∩_∩)O哈哈~

其實騰訊遊戲平臺就是這麼個實現原理,我也是經過它才學習到這種方式的,還得好好感謝感謝呢。

騰訊Android遊戲平臺的遊戲分紅兩類,第一類是騰訊自主研發的,像鬥地主,五子棋,連連看什麼的,因此實現機制就如上面的所示,A表明遊戲大廳,B表明鬥地主類的小遊戲。第二類是第三方軟件公司開發的,可就不能已這種方式來運做了,畢竟騰訊不能限制別人開發代碼的方式啊,因此騰訊就開放了一個sdk包出來,讓第三方應用能夠和遊戲大廳相結合,具體可參見QQ遊戲中心開發者平臺,但這同時就損失了一個優勢,那就是第三方開發的遊戲要經過安裝的方式才能運行。

結論

看到這裏,相信你們都比較熟悉這個背後的原理了吧,也但願你們能提供更好的反饋信息!

程序源碼下載source

相關文章
相關標籤/搜索