編輯:Android開發實例
相信這樣一個問題,大家都不會陌生,
“有什麼的方法可以使Android的程序APK不用安裝,而能夠直接啟動”。
發現最後的結局都是不能實現這個美好的願望,而騰訊Android手機游戲平台卻又能實現這個功能,下載的連連看,五子棋都沒有安裝過程,但是都能直接運行,這其中到底有什麼“玄機”呢,也有熱心童鞋問過我這個問題,本文就為大家來揭開這個謎團。
我實現了一個小小的Demo,麻雀雖小五髒俱全,為了突出原理,我就盡量簡化了程序,通過這個實例來讓大家明白後台的工作原理。
最能講明白道理的莫過於源碼了,下面我們就來分析一下A和B的實現機制,首先來分析TestA.apk的主要代碼實現:
- @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程序。
- 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程序的主代碼:
- 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畫面,動態畫當前的時間
- 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
原文: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/
登錄應用程序的屏幕,詢問憑據登錄到一些特定的應用。可能需要登錄到Facebook,微博等本章介紹了,如何創建一個登錄界面,以及如何管理安全問題和錯誤嘗試。首先,必須定義兩
本文實例講述了Android編程實現的重力感應效果。分享給大家供大家參考,具體如下: android中的很多游戲的游戲都使用了重力感應的技術,就研究了一下重力感應
JSON代表JavaScript對象符號。它是一個獨立的數據交換格式,是XML的最佳替代品。本章介紹了如何解析JSON文件,並從中提取所需的信息。Android提供了四個
可以顯示在的Android任務,通過加載進度條的進展。進度條有兩種形狀。加載欄和加載微調(spinner)。在本章中,我們將討論微調(spinner)。Spinner 用