标签 SDK 下的文章

Cocos2d-x 3.0rc0集成Google AdMob SDK

话说Cocos2d-x 3.0上一周迫不及待地发布了正式版,本是一件值得庆幸的事情。但由于不可解决的技术问题,引擎无奈将Android平台的NativeActivity 实现重新回退到了Cocos2d-x 2.2.x版本的实现方案。由于之前已经将 GameDemo移植到了Cocos2d-x 3.0rc0版,直观感受到了NativeActivity方案带来的游戏操作体验上的提升(触屏事件的响应),因此心里总是“挂念”引擎的 NativeActivity方案。按照Cocos2d-x官方人士的说法,只要NDK的问题修复,NativeActivity还是未来引擎在 Android平台上的第一选择。我的理解,将来Cocos2d-x的某个版本中还会恢复NativeActivity的实现方案。

上次只是将GameDemo的核心逻辑移植到了Cocos2d-x 3.0rc0上,但一些外部SDK集成尚未做完,这两天闲遐时开始着手研究如何做,而首先要移植的就是Google AdMob SDK。关于Cocos2d-x 3.0rc0集成Google AdMob SDK,网上已经有了技术方案原型,最初应该是一个外国开发者提出的方案,后来被CocoaChina cocos2d-x社区版主翻译成了中文教程,这里基本上也是参考的这个方案,只是做了局部细化和进一步说明,希望能帮助大家进一步解惑。

一、功能说明

GameDemo游戏分为三个场景:WelcomeScene、GameScene以及EndScene。集成AdMob SDK的功能要求如下:

    – 当游戏进入到WelcomeScene时,AdMob SDK完成初始化,发出Ad Request,当收到Ad的时候才会在屏幕上方显示带有Ad的窗口;
    – 当点击Start进入到GameScene时,隐藏Ad窗口,以不干扰玩家的游戏操作为优先;
    – 当游戏Over进入到EndScene的时候,恢复显示Ad窗口;
    – 当玩家点击“Retry”回到GameScene时,隐藏Ad窗口。

二、方案原理

这个方案就是通过android.widget.PopupWindow实现广告窗口悬浮在当前窗口之上。按照Android官方Doc中的说 明,PopupWindow用于实现一个弹出框,它可以使用任意布局的View作为其内容,这个弹出框是悬浮在当前activity之上的。

至于PopupWindow为何能显示在当前Activity之上,可以查看PopupWindow的源码,大致思路就是PopupWindow通过 new时传入的context(当前Activity)获得了当前WindowManager,并将AdView作为子View添加到该窗口中。这里简要列出一些关键源码,大家可粗略理解一下:

    public PopupWindow(Context context, AttributeSet attrs,
         int defStyleAttr, int defStyleRes) {
        mContext = context;
        mWindowManager = (WindowManager)context
               .getSystemService(Context.WINDOW_SERVICE);
        …. …
    }

    public void setContentView(View contentView) {
        if (isShowing()) {
            return;
        }

        mContentView = contentView;

        if (mContext == null && mContentView != null) {
            mContext = mContentView.getContext();
        }

        if (mWindowManager == null && mContentView != null) {
            mWindowManager = (WindowManager) mContext
                  .getSystemService(Context.WINDOW_SERVICE);
        }
    }

    public void showAtLocation(View parent, int gravity, int x, int y) {
        showAtLocation(parent.getWindowToken(), gravity, x, y);
    }

    public void showAtLocation(IBinder token, int gravity, int x, int y) {
        if (isShowing() || mContentView == null) {
            return;
        }

        unregisterForScrollChanged();

        mIsShowing = true;
        mIsDropdown = false;

        WindowManager.LayoutParams p = createPopupLayout(token);
        p.windowAnimations = computeAnimationResource();
      
        preparePopup(p);
        if (gravity == Gravity.NO_GRAVITY) {
            gravity = Gravity.TOP | Gravity.START;
        }
        p.gravity = gravity;
        p.x = x;
        p.y = y;
        if (mHeightMode < 0) p.height = mLastHeight = mHeightMode;
        if (mWidthMode < 0) p.width = mLastWidth = mWidthMode;
        invokePopup(p);
    }
   
    private void invokePopup(WindowManager.LayoutParams p) {
        if (mContext != null) {
            p.packageName = mContext.getPackageName();
        }
        mPopupView.setFitsSystemWindows(mLayoutInsetDecor);
        setLayoutDirectionFromAnchor();
        mWindowManager.addView(mPopupView, p);
    }

UI线程负责更新窗口,因此popupWindow的创建与操作都应该通过runOnUiThread传递给UI线程执行。

游戏逻辑的C++层代码通过Jni控制PopupWindow的Show和dismiss。别忘了C++层的代码是在渲染线程执行的哦,这也是为何要用handler和runOnUIThread的原因。

在Cocos2d-x 2.2.2版本集成AdMob时,AdMob只是在收到ad后才显示出广告Banner,但是在cocos2d-x 3.0rc0中,当广告加载未成功时,android.widget.PopupWindow会显示一个小黑框,特难看,因此我们需要自己来控制。

三、集成步骤

【AdMob准备】

  到Google AdMob注册一个帐号,如果你有Google帐号,那直接开通AdMob(需填写更加详细的信息)。
  下载Google AdMob SDK,之前一直用GoogleAdMobAdsSdk-6.4.1.jar,这里还以这个版本为例。但Google官方已经不推荐这个版本了,你可以下 载Google Play Services版本的Mobile Ads API,但代码与这里会稍有不同。将下载的jar包放到GameDemo/proj.android/libs中。
 
【修改AndroidManifest.xml】

  在<Application>标签里添加:

  <activity
     android:name="com.google.ads.AdActivity"
     android:configChanges="keyboard|keyboardHidden|orientation|screenLayout|uiMode|screenSize|smallestScreenSize" />
  <meta-data
     android:name="ADMOB_PUBLISHER_ID"
     android:value="YOUR_PUBLISHER_ID_VALUE" />

  权限方面至少包含如下两个:
   <uses-permission android:name="android.permission.INTERNET"/>
   <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>

【Java层代码集成】

我们只需要修改GameDemoActivity.java这个文件。

import android.app.NativeActivity;
import android.os.Bundle;
import android.util.Log;

import android.os.Handler;
import android.os.Message;
import android.view.Gravity;
import android.view.View;
import android.view.Gravity;
import android.view.ViewGroup.LayoutParams;
import android.view.ViewGroup.MarginLayoutParams;
import android.view.WindowManager;
import android.widget.LinearLayout;

import android.widget.PopupWindow;
import com.google.ads.AdRequest;
import com.google.ads.AdSize;
import com.google.ads.AdView;
import com.google.ads.AdListener;
import com.google.ads.Ad;

public class GameDemoActivity extends NativeActivity{
    private static GameDemoActivity context;

    private AdView adView;
    private PopupWindow popUpWindow;
    private LinearLayout popupWindowLayout;
    private LinearLayout mainActivityLayout;
    private boolean hasAdReceived;

    private static Handler adHandler = new Handler() {
        public void handleMessage(Message msg) {
            if (!context.hasAdReceived)
                return;

            switch (msg.what) {
                case 0:
                    if (View.VISIBLE ==
                        context.adView.getVisibility()) {
                        context.adView.setVisibility(View.GONE);
                        context.popUpWindow.dismiss();
                    }
                    break;
                case 1:
                    if (View.VISIBLE !=
                        context.adView.getVisibility()) {
                        context.adView.setVisibility(View.VISIBLE);
                        context.popUpWindow.showAtLocation(
                           context.mainActivityLayout, Gravity.TOP, 0, 0);
                    }
                    break;
            }
        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
        context = this;
        adView = new AdView(this, AdSize.BANNER, "a1533d6de900e31");
        adView.setAdListener(new AdmobListener());
        //初始时,广告View隐藏起来
        adView.setVisibility(View.GONE);
    }

    public static void setupAds(){
        context.initAdPopupWindow();
    }

    public void initAdPopupWindow() {
        if (adView != null) {
            context.runOnUiThread(new Runnable() {
                 @Override
                 public void run() {
                     MarginLayoutParams params = new MarginLayoutParams(
                                     LayoutParams.WRAP_CONTENT,
                                     LayoutParams.WRAP_CONTENT);
                     params.setMargins(0, 0, 0, 0);

                     popupWindowLayout = new LinearLayout(context);
                     popupWindowLayout.setPadding(-5, -5, -5, -5);
                     popupWindowLayout.setOrientation(LinearLayout.VERTICAL);
                     popupWindowLayout.addView(adView, params);

                     popUpWindow = new PopupWindow(context);
                     popUpWindow.setWidth(320);
                     popUpWindow.setHeight(50);
                     popUpWindow.setWindowLayoutMode(LayoutParams.WRAP_CONTENT,
                                               LayoutParams.WRAP_CONTENT);
                     popUpWindow.setClippingEnabled(false);
                     popUpWindow.setContentView(popupWindowLayout);

                     mainActivityLayout = new LinearLayout(context);
                     context.setContentView(mainActivityLayout, params);

                     context.hasAdReceived = false;

                     AdRequest adRequest = new AdRequest();
                     //测试Admob时使用测试Device,发布版需要去掉这行代码
                     adRequest.addTestDevice("CCE4220B2509A406B515E7C9A205AEE1");
                     context.adView.loadAd(adRequest);

                     popUpWindow.update();
                }
            });
        }
    }

    //GoogleAdMobAdsSdk-6.4.1版本中的AdListener是interface,因此
    //我们需要override所有接口,但只有onReceiveAd是我们关心的。
    private class AdmobListener implements AdListener {
        @Override
        public void onReceiveAd(Ad ad) {
            //只有第一次成功接收Ad后,我们后续才显示广告窗口,否则
            //popupWindow会显示为一个小黑框,特难看。
            Log.d("GameDemo", "onReceiveAd");
            if (!hasAdReceived){
                hasAdReceived = true;               
            }
        }

        @Override
        public void onFailedToReceiveAd(Ad ad,
                            AdRequest.ErrorCode error) {
            Log.d("GameDemo",
              "failed to receive ad (" + error+ ")");
        }

        @Override
        public void onPresentScreen(Ad ad) {
            Log.d("GameDemo", "onPresentScreen");
        }

        @Override
        public void onDismissScreen(Ad ad) {
            Log.d("GameDemo", "onDismisScreen");
        }

        @Override
        public void onLeaveApplication(Ad ad) {
            Log.d("GameDemo", "onLeaveApp");
        }
    }

    public static void setAdVisible(boolean b) {
       Message msg = new Message();
        if (b) {
            msg.what = 1;
        } else {
            msg.what = 0;
        }
        adHandler.sendMessage(msg);
    }
}

【C++层代码集成】

在AppDelegate.cpp中添加setupAds方法:

void AppDelegate::setupAds()
{
#if (CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID)
    JniMethodInfo t;
    if (JniHelper::getStaticMethodInfo(t, "com/tonybai/game/GameDemoActivity", "setupAds", "()V")) {
        t.env->CallStaticVoidMethod(t.classID, t.methodID);
        if (t.env->ExceptionOccurred()) {
            t.env->ExceptionDescribe();
            t.env->ExceptionClear();
            return;
        }
        t.env->DeleteLocalRef(t.classID);
    }
#endif
}

在WelcomeScene的init方法中调用setupAds:

bool WelcomeScene::init()
{
    bool bRet = false;
    do {
       … …
       AppDelegate *app = (AppDelegate*)Application::getInstance();
       app->setupAds();

       bRet=true;
    } while(0);

    return bRet;
}

在点击Start按钮时,隐藏Ad:

void WelcomeScene::menuStartCallback(Ref* pSender)
{
    AppDelegate *app = (AppDelegate*)Application::getInstance();

    app->setAdVisible(false);
    GameScene *gameScene = GameScene::create();
    Director::getInstance()->replaceScene(gameScene);
}

集成步骤到此就结束了,编译ok就可以部署到模拟器上运行测试一番了。

四、使用Cocos2d-x 3.0rc0引擎遇到的两个问题

【问题1】用cocos2d-x 3.0rc0的cocos编译高版本引擎生成的工程遇到的问题

 cocos2d-x 3.0rc0生成的proj.android/build-cfg.json与高版本(rc1~正式版)  cocos生成的工程的build-cfg.json稍有不同,用cocos2d-x 3.0rc0版cocos编译高版本cocos生成的project,会提示build_android组件无法找到“copy_to_assets”。 因此需要手动修改proj.android/build-cfg.json,将其中的:

    "copy_resources": [
        {
            "from": "../Resources",
            "to": ""
        }
    ],

改为:

   "copy_to_assets" :[
            "../Resources/"
            ],

【问题2】 W/dalvikvm(1194): dvmFindClassByName rejecting 'org/cocos2dx/lib/Cocos2dxHelper'

使用cocos2d-x 3.0rc0编译的项目,总是出现如下问题,但似乎这个错误还不影响程序的运行:

04-29 09:44:34.968: D/JniHelper(1194): JniHelper::setJavaVM(0xb8835730), pthread_self() = B897B518
04-29 09:44:34.968: W/dalvikvm(1194): dvmFindClassByName rejecting 'org/cocos2dx/lib/Cocos2dxHelper'

Google了一下,这个问题很多童鞋都遇到了,但给出的solution却不甚令我满意,于是我就求甚解的细致挖掘了一下,终于找到了一个让我还算满意 的答案。我们分析一下这两条日志,rejecting那条日志总是伴随setJavaVM后面出现的。可引擎什么时候setJavaVM了呢?显然是在 native_app_glue创建的子进程中,引擎需要attachCurrentThread,获得JniEnv时才做的。于是我们打开cocos2d-x-3.0rc0/cocos/2d/platform/android/nativeactivity.cpp一看究竟。

在nativeactivity.cpp中有两处"org/cocos2dx/lib/Cocos2dxHelper",我们先看engine_handle_cmd中的那处:

static void engine_handle_cmd(struct android_app* app, int32_t cmd)
{
        …. ….
        case APP_CMD_INIT_WINDOW:
            LOG_RENDER_DEBUG("android_main : APP_CMD_INIT_WINDOW");
            // The window is being shown, get it ready.
            if (engine->app->window != NULL) {
                cocos_dimensions d = engine_init_display(engine);
                if ((d.w > 0) &&
                    (d.h > 0)) {
                    cocos2d::JniHelper::setJavaVM(app->activity->vm);
                    cocos2d::JniHelper::setClassLoaderFrom(app->activity->clazz);

                    // call Cocos2dxHelper.init()
                    cocos2d::JniMethodInfo ccxhelperInit;
                    if (!cocos2d::JniHelper::getStaticMethodInfo(ccxhelperInit,
                                                                 "org/cocos2dx/lib/Cocos2dxHelper",
                                                                 "init",
                                                                 "(Landroid/app/Activity;)V")) {
                        LOGI("cocos2d::JniHelper::getStaticMethodInfo(ccxhelperInit) FAILED");
                    }
                    ccxhelperInit.env->CallStaticVoidMethod(ccxhelperInit.classID,
                                                            ccxhelperInit.methodID,
                                                            app->activity->clazz);

                    cocos_init(d, app);
                }
                engine->animating = 1;
                engine_draw_frame(engine);
            }
            break;
    … …
}

初步可以断定,那两条日志就是执行到这里输出的,但为何dvmFindClassByName方法会rejecting “org/cocos2dx/lib/Cocos2dxHelper”这个类名呢?我们还得翻看dalvik虚拟机源码:

 /dalvik/vm/native/InternalNative.cpp

/*
 * Find a class by name, initializing it if requested.
 */
ClassObject* dvmFindClassByName(StringObject* nameObj, Object* loader,
        bool doInit)
{
    ClassObject* clazz = NULL;
    char* name = NULL;
    char* descriptor = NULL;
    if (nameObj == NULL) {
        dvmThrowNullPointerException("name == null");
        goto bail;
    }
    name = dvmCreateCstrFromString(nameObj);
    /*
     * We need to validate and convert the name (from x.y.z to x/y/z). This
     * is especially handy for array types, since we want to avoid
     * auto-generating bogus array classes.
     */
    if (!dexIsValidClassName(name, true)) {
        ALOGW("dvmFindClassByName rejecting '%s'", name);
        dvmThrowClassNotFoundException(name);
        goto bail;
    }
    … …
}

我们的确找到了输出rejecting日志的地方,通过注释我们可以看到这个方法是用来验证名字对象,并将x.y.z形式的名字转换成x/y/z的。但引 擎中传入的就是“x/y/z”格式,因此这个方法输出了错误日志。我尝试将上面engine_handle_cmd中的"org/cocos2dx/lib/Cocos2dxHelper"改成"org.cocos2dx.lib.Cocos2dxHelper",错误日志果然消失了。

不过目前仍不能解释一点:为何在其他位置,比如在前面的AppDelegate::setupAds中,使用x/y/z格式的jni调用参数却没有输出错误日志!难道两个位置dalvik虚拟机使用的是不同的名字对象查找和加载方法?这个目前尚无定论。

使用命令行方式开发Android应用

这两天参加了一个Android开发入门培训,讲师的水平不敢恭维,课讲的基本上也是一塌糊涂,不过通过这次培训,我算是达到了Android开发快速入门的预期目标。

一般来说Android应用开发的标准工具组合是JDK + Android SDK + ADT (Android Development Tools) + Eclipse,大家基本上是通过IDE GUI进行开发操作的。不过我个人更喜欢命令行,所以这次我也尝试探索了一下使用命令行方式开发Android应用的方法。

入门的第一步就是搭建开发环境。关于Android开发环境搭建的资料早已汗牛充栋,不过我也看了一下这些资料多是关于如何在Windows下使用Eclipse搭建环境的,而在Linux环境下不用Eclipse的手工搭建环境的资料甚少。而我用的是Ubuntu 10.04,所以在这里我想说说Ubuntu下搭建Android开发环境的过程,以及在此过程中遇到的诸多问题的解决。

Android应用主要用Java语言开发,所以JDK是不可缺少的,这是一个前提条件。关于JDK的安装以及环境变量配置,这里就不赘述了。我在Ubuntu下安装的是Oracle(原Sun)提供的JDK 1.6版本。

Android开发环境搭建的核心就是SDK。不过大陆的程序员们真的很悲哀,原因你懂的。为了下载一个SDK,到处翻山越岭,跋山涉水啊,好不痛苦。不过还好,领导们还给我们留下了一线生机。那就是http://dl-ssl.google.com/android/repository,这里可以下载到Android SDK相关组件包。

首先你可以下载这个库的导航文件repository.xml(wget -c http://dl-ssl.google.com/android/repository/repository.xml)。打开这个文件,通过里面的注释你会看到这个文件大约包含了四个部分:
. PLATFORMS
. PLATFORM-TOOLS
. TOOLS
. DOCS

这恰恰是Android SDK包的几个主要组成部分:
. 其中TOOLS对应的就是Android SDK Tools,主要用于SDK自身组件安装、卸载管理,提供模拟器工具以及其他开发所需的第三方工具。
. 其中PLATFORMS对应的是Android SDK Platform,这些包为Android应用开发提供了各个版本的虚拟设备(AVD)。比如Android 2.2、Android 2.3.3等虚拟设备。
. 其中PLATFORM-TOOLS对应的是Android SDK Platform-tools,这些包提供了与虚拟设备管理和调试相关的工具,如ADB。

我们如何通过这些组件包来组装成一个完整的Android SDK包呢?步骤大致如下:
. 下载Android SDK Tools包,也就是Repository中对应的TOOLS部分。我这里找到的是tools_r11-linux.zip(wget -c http://dl-ssl.google.com/android/repository/tools_r11-linux.zip)。
. 在本地建立android-sdk-linux_86目录,将下载的tools_r11-linux.zip放到该目录下,解压,我们得到tools_r11-linux目录。
. 将android-sdk-linux_86目录下的tools_r11-linux目录改名为tools。
. 在android-sdk-linux_86目录下建立两个新目录:add-ons和platforms。(如果没有这两个目录,下一步中的android启动会失败)
. 进入android-sdk-linux_86/tools下,执行./android,启动Android SDK and AVD Manager。
. 在启动的Android SDK and AVD Manager对话框的"Installed Packages"里你会看到我们已经安装了“Android SDK Tools, revision 11”。

到这里,我们算是迈出了坚实的第一步。接下来,我们有两种方式继续我们的安装过程:
一种是通过SDK/AVD Manager在线安装SDK其余组件。在"Installed Packages"里点击"Update All"按钮,等待一会,你会看到可以安装的组件。这里我们至少需要一个Platform包(比如Android 2.3.3 API 10, revision 1)以及Platform-tools包(比如Android SDK Platform-tools, revision 4)。选择你要的组件包后,就可以install了。安装后,一个完整的Android SDK就呈现在你的眼前了。这种方式也是最快捷、最方便的方式了。

另外一种是离线安装方式。如果你和我一样,使用的是公司的代理网络,那么你很可能无法在线安装,即使SDK/AVD Manager支持配置网络代理。这样你就需要进行离线安装了,也就是需要你手工下载各个组件包,然后安装到指定的目录下。我这里就做了如下操作:
. 执行下面命令下载各组件包:
  wget -c http://dl-ssl.google.com/android/repository/android-2.2_r02-linux.zip
  wget -c http://dl-ssl.google.com/android/repository/android-2.3.3_r01-linux.zip
  wget -c http://dl-ssl.google.com/android/repository/platform-tools_r04-linux.zip
. 将android-2.2_r02-linux.zip拷贝到android-sdk-linux_86/platforms目录下,并解压。
. 将android-2.3.3_r01-linux.zip拷贝到android-sdk-linux_86/platforms目录下,并解压。
. 将platform-tools_r04-linux.zip拷贝到android-sdk-linux_86目录下,解压,并改名为platform-tools。

至此,SDK各组件安装完毕。执行tools/android,在"Installed Packages"下,你就会看到上述已经安装的组件包了。(笔者最后又发现了一个可以下载Android SDK的地方:http://dl.google.com/android[/android-sdk_r08-linux_86.tgz],在这里你下载到的SDK包内platforms和add-ons目录都已经建立完毕了,SDK tools在tools目录下,其余组件的安装方法和上面一致。)

为了方便后续使用,我们可将SDK目录下的platform-tools和tools两个路径添加到PATH环境变量中。接下来,我们就可以创建一个虚拟设备了。Android虚拟设备其实是一组配置,tools下的emulator使用这些配置启动一个特定版本的Android模拟程序,用来部署、运行和测试你开发的Android应用。

我们可以通过"android list targets"命令来查看当前系统中可以创建哪些平台的虚拟设备,在我的系统下,这条命令的执行结果如下:

Available Android targets:
id: 1 or "android-8"
     Name: Android 2.2
     Type: Platform
     API level: 8
     Revision: 2
     Skins: WVGA854, QVGA, WVGA800 (default), WQVGA400, WQVGA432, HVGA
id: 2 or "android-10"
     Name: Android 2.3.3
     Type: Platform
     API level: 10
     Revision: 1
     Skins: WVGA854, QVGA, WVGA800 (default), WQVGA400, WQVGA432, HVGA

我们有两个Platform可选,这里我们创建一个Android 2.3.3的虚拟设备。创建的命令如下:

$> android create avd -n helloandroid -t 2
Android 2.3.3 is a basic Android platform.
Do you wish to create a custom hardware profile [no]

Created AVD 'helloandroid' based on Android 2.3.3,
with the following hardware config:
hw.lcd.density=240
vm.heapSize=24
hw.ramSize=256

其中-n 用于指定avd的名字,-t则用于指定platform,也就是target,之前我们已经列出系统中的Targets,我们只需选择一个,并使用target的id即可。

创建后,我们可以通过android list avd来查看系统中都创建了哪些avd:
$> android list avd
Available Android Virtual Devices:
    Name: helloandroid
    Path: /media/winD/tonybai/android-sdk-linux_86/.android/avd/helloandroid.avd
  Target: Android 2.3.3 (API level 10)
    Skin: WVGA800

有了avd,我们就可以启动emulator了。执行emulator -avd helloandroid,我们得到了如下错误信息:
“emulator: ERROR: the user data image is used by another emulator. aborting”

这条错误信息的字面意思是有另外一个emulator使用了这个avd,但是我找了半天,发现我并未启动任务其他emulator,系统进程列表中也没有其他emulator的信息。又到网上找了一些资料,都说是因emulator异常退出,导致没有解锁avd配置目录下的.lock文件导致的。但我到avd配置目录下,根本没有找到什么.lock文件。

我又通过调试模式执行了一遍:emulator -avd helloandroid -verbose -debug-all,这回我得到的信息如下:
… 这里省略了几百行日志….
emulator: found system.img in search dir: /media/winD/tonybai/android-sdk-linux_86/platforms/android-2.3.3_r01-linux/images/
emulator: found userdata-qemu.img in content directory
emulator:     locking user data image at /media/winD/tonybai/android-sdk-linux_86/.android/avd/helloandroid.avd/userdata-qemu.img
emulator: ERROR: the user data image is used by another emulator. aborting

从上面的错误日志来看,似乎emulator在对userdata-qemu.img加锁时出现了问题。这个问题古怪了些。我的SDK部署在FAT32分区,难道是跨分区文件锁有问题。无奈下把SDK搬移到我的HOME路径下,并修改PATH环境变量。重新启动emulator,这回emulator启动成功了。不过第一次启动emulator可真是够慢的,大约有5、6分钟之多,才看到Android的界面。不过还有一个问题,那就是emulator启动的模拟器画面太大,出了屏幕边界(我的本子是12寸屏幕的)。我们来修改一下avd的配置,调整屏幕属性:

在android-sdk-linux_86/.android/avd/helloandroid.avd目录下,我们打开config.ini,将下面三项配置:
hw.lcd.density=240
skin.name=WVGA800
skin.path=platforms/android-2.3.3_r01-linux/skins/WVGA800
修改为:
hw.lcd.density=160
skin.name=HVGA
skin.path=platforms/android-2.3.3_r01-linux/skins/HVGA

重新启动emulator,这回整个模拟器的画面都在屏幕以内了。

万事俱备,只欠东风!下面我们就可以开始创建我们第一个HelloAndroid工程了。在~/proj/android下建立helloandroid目录,进入helloandroid目录,执行下面命令:

$> android create project –name helloandroid –activity HelloAndroid –path ./ –package com.examples.helloandroid –target 2

Created directory /home/tonybai/proj/android/helloandroid/src/com/examples/helloandroid
Added file ./src/com/examples/helloandroid/HelloAndroid.java
Created directory /home/tonybai/proj/android/helloandroid/res
Created directory /home/tonybai/proj/android/helloandroid/bin
Created directory /home/tonybai/proj/android/helloandroid/libs
Created directory /home/tonybai/proj/android/helloandroid/res/values
Added file ./res/values/strings.xml
Created directory /home/tonybai/proj/android/helloandroid/res/layout
Added file ./res/layout/main.xml
Created directory /home/tonybai/proj/android/helloandroid/res/drawable-hdpi
Created directory /home/tonybai/proj/android/helloandroid/res/drawable-mdpi
Created directory /home/tonybai/proj/android/helloandroid/res/drawable-ldpi
Added file ./AndroidManifest.xml
Added file ./build.xml
Added file ./proguard.cfg

Build该工程: ant release(注意对于2.3的SDK,ant要使用1.8以上版本)。一切很顺利,Build成功后,在bin下面出现了"helloandroid-unsigned.apk"文件。

那么如何将apk文件部署到模拟器中运行呢?如果系统内仅有一个device在运行(可通过adb devices命令查看),那么我们可以直接执行ant install,这样我们的apk就会自动被部署到emulator中了(这期间使用的是调试版的数字签名)。

部署后,你就会在emulator的界面上看到一个绿机器人图标且名字为“HelloAndroid”的程序了。点击其执行,我们得到一行文字:Hello World, HelloAndroid。这个文字是工程被创建时默认自带的,你当然也可以修改它了。

另外如果要卸载这个应用也很简单,执行ant uninstall就是了。

如果系统有多个AVD在运行,那么我们同样可以通过adb命令来选择一个device安装我们的应用,如果一个device的名字是emulator-5554(通过adb devices查看),那么我们可以先执行ant debug,生成bin/helloandroid-debug.apk,然后通过"adb -s emulator-5554 install bin/helloandroid-debug.apk"将应用安装到emulator-5554上去。

如发现本站页面被黑,比如:挂载广告、挖矿等恶意代码,请朋友们及时联系我。十分感谢! Go语言第一课 Go语言精进之路1 Go语言精进之路2 商务合作请联系bigwhite.cn AT aliyun.com

欢迎使用邮件订阅我的博客

输入邮箱订阅本站,只要有新文章发布,就会第一时间发送邮件通知你哦!

这里是 Tony Bai的个人Blog,欢迎访问、订阅和留言! 订阅Feed请点击上面图片

如果您觉得这里的文章对您有帮助,请扫描上方二维码进行捐赠 ,加油后的Tony Bai将会为您呈现更多精彩的文章,谢谢!

如果您希望通过微信捐赠,请用微信客户端扫描下方赞赏码:

如果您希望通过比特币或以太币捐赠,可以扫描下方二维码:

比特币:

以太币:

如果您喜欢通过微信浏览本站内容,可以扫描下方二维码,订阅本站官方微信订阅号“iamtonybai”;点击二维码,可直达本人官方微博主页^_^:
本站Powered by Digital Ocean VPS。
选择Digital Ocean VPS主机,即可获得10美元现金充值,可 免费使用两个月哟! 著名主机提供商Linode 10$优惠码:linode10,在 这里注册即可免费获 得。阿里云推荐码: 1WFZ0V立享9折!


View Tony Bai's profile on LinkedIn
DigitalOcean Referral Badge

文章

评论

  • 正在加载...

分类

标签

归档



View My Stats