读《50 Android Hacks》笔记整理Hack 31~Hack 34

第七章 实用库

这里只有两个第三方库。

Hack 31 Android面向切面编程

使用面向切面编程(Aspect-Oriented Programming,AOP)可以解决Android中Activity被很多与其自身逻辑无关的代码“污染“的问题。
面向切面编程是一种编程范式,通过分离横切关注点(Crosscutting Concern)提高程序的模块化和组件化。其基本原理是:横切关注点抽离到一个单独的模块(切面,Aspect)中,同时将需要执行的业务逻辑代码(或在横切关注点之前或在横切关注点之后)放在单独或者不同模块中。
在Android中可以使用AspectJ库实现面向切面编程。鉴于Android不支持字节码生成,我们无法使用AspectJ的所有特想,但Android支持AspectJ提供的编译时植入(compile-time weaving)的特性。
要让AOP运行起来,需要修改编译过程。这里我们使用Apache Maven工具,使用该工具,只需在pom.xml文件中添加依赖关系和构建(build)插件。
我们使用的Apache Maven插件是aspectj-maven-plugin,在pom.xml文件配置插件代码如下:

<plugin>
    <groupId>org.codehaus.mojo</groupId>
    <artifactId>aspectj-maven-plugin</artifactId>
    <version>1.4</version>
    <configuration>
        <source>1.5</source>
        <complianceLevel>1.5</complianceLevel>
        <showWeaveInfo>true</showWeaveInfo>
        <verbose>true</verbose>
    </configuration>
    <executions>
        <execution>
            <goals>
                <goal>compile</goal>
            </goals>
        </execution>
    </executions>
</plugin>

goal是Maven中执行任务
main目录和test目录是Maven的标准目录结构
开发切面时,需要将showWeaveInfo和verbose标记设置为开启,这样会打印植入过程的日志信息,有助于理解该过程的执行流程。将goal设置为compile,插件会植入main目录下单类,如果我们还需要植入test目录下的类就需要添加test-compile配置。
创建切面的方式有两种:
1.使用AspectJ语法
2.使用@AspectJ注解(Annotation)
两种方式最大的不同之处在于:
使用AspectJ语法更易编写切面,因为该语法就是为了编写切面而设计的。注解风格遵循常规的Java编译器。
因为这里我们不需要实现什么复杂的功能,切面比较简单,所以本例使用注解风格创建切面。
在aspect目录下,有一个LogAspect.java文件,该文件用于描述切面代码如下:

//1.AspectJ注解
@Aspect
public class LogAspect{
    //2.用于Activity的切入点
    @Pointcut("within(com.manning.androidhacks.hack031.MainActivity)")
    private void mainActivity(){}
    //3.用于onCreate()方法的切入点
    @pointcut("execution(*onCreate(...))")
    private void onCreate(){}
    //4.合用切入点
    @AfterReturning(pointcut = "mainActivity() && onCreate()")
    public void logAfterOnCreateOnMainActivity(){
        //5.执行Advice
        Log.d("TAG","OnCreate() has been called!");
    }
}

连接点(Join Point):是程序执行流程中某个确定的执行点。
切入点(Pointcut):用于辨别确定的连接点以及该连接点的取值。
通知(Advice):是到达一个连接点时要执行的代码。
我们可以把切入点视为特定连接点的表达形式,表示当前切面适用于哪个连接点。
本例展示如何使用AspectJ提供的编译时植入的特性为Activity中的方法添加日志。AOP不仅仅是一种把代码从一个类转移到另一个类的方式。

外链地址1
外链地址2
外链地址3
外链地址4
外链地址5


Hack 32 使用Cocos2d-x美化应用程序

如果我们想为app添加图形视图或3D动画,应该怎么做呢?
一些开发者可能会尝试使用OpenGL开发视图,但这意味着为app增加了一层复杂的代码,而且OpenGL资料并不是很多。
在这里我们会使用Cocos2d-x为app添加新特性。

32.1 Cocos2d-x是什么

Cocos2d原本是PyWeek游戏编程竞赛中使用的Python游戏框架。Cocos2d的名字源于阿根廷科尔多瓦一个叫Los Cocos的城市。后来,Cocos2d创造者之一(Ricardo Quesada)用Objective-C语言开发了iPhone版本的Cocos2d。
Cocos2d-x是iPhone版Cocos2d的C++移植版本。该版本具有跨平台、轻量级、对开发人员友好、免费、开源等优点。而且可以通过Android NDK使用该版本。

32.2 使用Cocos2d-x

我们实现一个下雪效果的示例。
Cocos2d-x使用OpenGL绘图,在Android中,通过OpenGL绘图,开发者需要使用SurfaceView。
SurfaceView的工作原理,开发文档中的描述:
“SurfaceView是View的特殊子类,用于在视图层次结构中提供一个专用的绘图Surface。其目的是将这个绘图的Surface提供给应用程序的另一个线程(非UI线程)这样应用程序界面就不必等待系统视图层次结构做好绘制准备的时候。相反,另一个线程持有SurfaceView的引用,可以根据自己的步调向自身的Canvas中绘制。“
最后一句其实就是我们向应用程序中添加的UI控件或者自定义视图会被加入到视图层次中。完整的视图树(Activity的表现形式)是在UI线程中绘制的。另一方面,SurfaceView在自己线程中绘制,并不会用到UI线程。
如果SurfaceView不通过UI线程绘制自身,那么Android如何将视图层次和SurfaceView整合到一起呢?
我们需要先分析下面这段话:
“Surface是以Z轴排序的(z-order),以至位于SurfaceView宿主窗口的后方。SurfaceView在窗口上开了一个“洞“,这样Surface就可以显示出来了。视图层次会将其与SurfaceView的兄弟节点正确合成,这些兄弟节点会显示在Surface的上方。这个特性可以用于在Surface上方放置覆盖式控件(Overlay),比如显示在Surface上方的按钮。有一点需要注意,如果在Surface上方有全透明控件,那么每当Surface发生变化时,这些全透明控件就会重新合成,这样会影响性能。“
从这段话中我们可以得出一个结论:可以合成两个“世界“的内容,但有一些限制。SurfaceView既可以放置在视图层次的前面也可以放置在后面。
示例:这里我们把视图层放置在后面,而把SurfaceView放置在前面。
Activity的XML布局文件,代码如下:

<RelativeLayout ...>
    <TextView 
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_alignParentTop="true"
        android:layout_marginTop="5dp"
        android:gravity="center"
        android:textSize="30sp"
        android:id="@+id/winter_text"
        android:text="Hello Winter!"/>
    <View 
        android:id="@+id/separator"
        android:layout_width="match_parent"
        android:layout_height="5dp"
        android:layout_below="@id/winter_text"
        android:background="#ffffff"/>
    <TextView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_centerInParent="true"
        android:laout_marginTop="5dp"
        android:gravity="center"
        android:text="It's snowing!"
        android:textSize="30sp"/>
    <FrameLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_below="@id/separator">
        <org.cocos2dx.lib.Cocos2dxEditText
            android:id="@+id/game_edittext"
            android:layout_height="wrap_content"
            android:layout_width="match_parent"
            android:background="@null"/>

        <org.cocos2dx.lib.Cocos2dxGLSurfaceView
            android:id="@+id/game_gl_surfaceview"
            android:layout_width="match_parent"
            android:layout_height-"match_patent"/>
    </FrameLayout>  
</RelativeLayout>

在FrameLayout中,首先可以看到org.cocos2dx.lib.Cocos2dxEditText是如何创建的,当游戏需要用户输入信息的时候,Cocos2d-x通过Cocos2dxEditText显示一个输入界面,这里我们不用这个界面,但还是有必要添加它的。另一个重要组件是SurfaceView,布局中放置的SurfaceView是一种定位Cocos2d-x视图,并提供其宽高尺寸的独特方式,这里其实可以全屏显示,但为了展示如何利用Android资源在屏幕上显示SurfaceView,而不需要考虑设备尺寸、像素密度等信息。
Activity代码如下:

public class MainActivity extpends Cocos2dxActivity{
    private Cocos2dxGLSurfaceView mGLView;
    protected void onCreate(Bundle savedInstanceState){
        super.onCreate(savedInstanceState);
        if(detectOpenGLES20()){
            //1.为Cocos2d-x设置应用程序包名
            String packageName = getApplication().getPackageName();
            super.setPackageName(packageName);
            setContentView(R.layout.game_demo);
            mGLView = (Cocos2dxGLSurfaceView) findViewById(R.id.game_gl_surfaceview);
            //2.为Cocos2d-x提供Cocos2dxEditText控件的位置
            Cocos2dxEditText edittext = (Cocos2dxEditText) findViewById(R.id.game_edittext);
            mGLView.setEGLContextClientVersion(2);
            mGLView.setCocos2dxRenderer(new Cocos2dxRenderer());
            mGLView.setTextField(edittext);
        }else{
            Log.d("activity","do not support gles2.0");
            finish();
        }
    }
}

要在Activity中使用Cocos2d-x的特性,需要继承自Cocos2dxActivity。我们要告诉Cocos2d-x应用程序的包名是什么,Cocos2d-x会使用该包从assets文件夹中读取asset资源。我们还需要为Cocos2d-x提供Cocos2dxEditText控件的位置。如果设备不支持OpenGL 2.0,那么就关闭应用程序。
在这里,我们修改了Cocos2d-x的java代码,令SurfaceView显示在视图层次的上方,并使其背景为半透明状态。要实现这种效果,需要在Cocos2dxGLSurfaceView类的initView()方法中添加如下代码:

setEGLConfigChooser(8,8,8,8,16,0);
getHolder().setFormat(PixelFormat.TRANSLUCENT);
setZOrderOnTop(true);

此外,还需要在Cocos2dxRenderer类的onSurfaceCreated()方法中添加如下代码:

gl.glClearColor(0,0,0,0);

到这里java代码都准备就绪了。接下来,我们只需要编写c++代码处理下雪的效果。因为这里我们只是演示一些特性,所以我们直接使用Cocos2d-x提供的一个与下雪效果有关的粒子系统的测试例子。该代码在HelloWorldScene.cpp文件中,可以在外链地址中找到。
如果之前从未在Android中使用C++语言,但你应该知道需要使用Android NDK。
使用Cocos2d-x不但可以美化app,还可以避免直接操作OpenGL。其不足的地方就是需要处理一些限制和复杂性,开发者需要编写C++代码,并使用NDK,此外还需要创建视图并正确处理SurfaceView。

外链地址1
外链地址2
外链地址3
外链地址4
外链地址5
外链地址6

第八章 与其它编程语言交互

Android应用程序主要用java语言编写。此外,官方提供了Android NDK(Native Development Kit,原生开发套件)支持C/C++语言。除此之外,其实还可以使用其他编程语言开发应用程序。

Hack 33 在Android上运行Objective-C

cocos2d-iphone库使用的是Objective-C语言实现的。但在很多分支项目用其他编程语言也实现了相同的API,其中最活跃的分支是cocos2d-x。
cocos2d-x并非用Objective-C语言实现,而是用C++语言实现。
cocos2d-iphone与cocos2d-x代码如下:

//cocos2d-iphone版本
[[SimpleAudioEngine sharedEngine] playEffect:@"sfx.file"];
//cocos2d-x版本
SimpleAudioEngine::sharedEngine()->playEffect("sfx.file");

我们可以看出这两个API几乎是相同的,但是要想把代码从cocos2d-iphone移植到cocos2d-x,需要将所有Objective-C代码移植为C++代码,这是一个很无聊的工作。
所以我们找到了一个替代的方法——Dmitry Skiba创建的名为Itoa的库。开发文档说明如下:
“[Itoa]是一组托管于GitHub的开源项目,该项目实现了编译器、构建脚本(build script)和各种库,用于将Objective-C源代码构建为Android APK文件。”
Itoa的主要目的不仅限于在Android平台运行Objective-C代码,它可以将iOS应用程序转换为Android应用程序,让我们可以在Android平台运行Objective-C代码。
这里我们移植一个简单的Objective-C库做为示例,这个库命为TextFormatter。这意味着不必修改该库就可以在Android平台运行Objective-C代码。

33.1 下载并编译Itoa

编译Itoa库的方法很简单,只需要在命令行运行以下命令:

wget https://github.com/downloads/DmitrySkiba/itoa/build-ndk.sh
chmod +X build-ndk.sh
./build-ndk.sh

上述脚本会创建一个名为itoa的文件夹,获取所有子项目,并在itoa/ndk目录中构建NDK。该脚本首先设置工具链(tool chain),然后以此编译所有子项目,最终会在/itoa/ndk/itoa/platform/arch-arm/usr/lib文件夹下生成.so文件。

33.2 划分模块

与普通NDK应用程序一样,我们会把代码划分到不同模块中。
我们创建一个名为textformatter的模块,该模块包含需要移植的代码;此外,我们还会创建另一个名为main的模块,用于负责java代码与TextFormatter类之间的交互。

33.2.1 ItoaApp.mk和ItoaModule.mk文件

与Android NDK使用Application.mk和Android.mk等make文件类似,Itoa使用ItoaApp.mk和ItoaModule.mk文件。
在Android项目中,我们需要创建一个名为jni的文件夹。jni文件夹中包含两个make文件:ItoaApp.mk和ItoaModule.mk。此外,还需要在jni文件夹中创建两个文件夹用于存放不同模块的代码,一个文件夹存放textformatter模块,另一个文件夹存放main模块。在每个模块的文件夹中,还需要创建要给ItoaModule.mk文件。
在ItoaModule.mk文件中需要指定jni文件夹下各个模块的ItoaModule.mk文件, 内容如下:

THIS_PATH := $(call my-dir)
include $(THIS_PATH)/main/ItoaModule.mk
include $(THIS_PATH)/TextFormatter/ItoaModule.mk

ItoaApp.mk文件中内容如下:

//1.打开库模式
APP_IS_LIBRARY := true
//2.设置.so文件路径
APP_LIBRARY_BIN_PATH = ../libs/$(TARGET_ABI)

因为我们并不想以这些Objective-C代码创建Android APK,所以需要打开库模式。

33.2.2 textformatter模块

要移植的库很简单,该库只包含一个返回NSString*的类方法。该库的Objective-C代码由a.h和a.m两个文件组成,代码如下:

//TextFormatter.h文件
#import<Foundation/Foundation.h>
@interface TextFormatter:NSOBject+(NSString *)format:(NSString *)text;
@end
...
//TextFormatter.m 文件
#import "TextFormatter.h"
@implementation TextFormatter+(NSString *)format:(NSString *)text{
    NSString *objc = @"Text from Objective-c";
    NSString *string = [NSString stringWithFormat:@"%@ with %@",objc,text];
    retrun string;
}
@end

我们可以看到,不需要该库做任何改动,该库只包含一个.h和一个.m文件,这个两个文件都是用Objective-C编写应用程序时常用的。
Itoa NDK的构建脚本源自Android NDK,但已经被重构过了,如:ItoaModule.mk会把所有LOCAL_*变量重命名为MODULE_*。
该文件内容如下:

MODULE_PATH := $(call my-dir)
include $(CLEAR_VARS)

MODULE_NAME := textformatter//模块名

MODULE_SRC_FILES := \TextFormatter.m//要编译的源文件

MODULE_C_INCLUDES +=\$(MODULE_PATH)\  //头文件路径

include $(BUILD_SHARED_LIBRARY)
33.2.3 main模块

Main模块中有两个源文件:
JNIOnLoad.cpp在该文件中实现JNI_OnLoad方法
main.mm:将JNI调用和TextFormatter实现代码联系在一起
JNIOnLoad.cpp源码如下:

#include <CoreFoundation/CFRuntime.h>
#include <jni.h>

extern "C"{
    jint JNI_OnLoad(JavaVM *vm,void *reserved){
        _CFInitialize();//初始化CoreFoundation
        extern void call_dyld_handlers();//加载Objective-C类
        call_dyld_handlers();
        return JNI_VERSION_1_6;
    }
}

加载native库时,虚拟机会调用JNI_OnLoad方法,因此该方法是初始化Itoa的好地方。
main.mm代码如下:

#include <jni.h>
#import <Foundation/Foundation.h>
#import <objc/runtime.h>
#import <TextFormatter.h>

extern "C"
{
jstring
//TextFormatterJNI调用
Java_com_manning_androidhacks_hack033_TextFormatter_formatString(JNIEnv* env,jobject thiz,jstring text){
        jstring result = NULL;
        NSAutoreleasePool *pool = [NSAutoreleasePool new];
        //将jstring转换为NSString*
        const char *nativeText = env->GetStringUTFChars(text,0);
        NSString *objcText = [NSString stringWithUTF8String:nativeText];
        env->ReleaseStringUTFChars(text,nativeText);

        NSString *formattedText = [TextFormatter format:objcText];
        [pool drain];
        //返回结果为jstring类型
        return result;
    }
}

main模块的ItoaModule.mk内容如下:

MODULE_PATH := $(call my-dir)
include $(CLEAR_VARS)

MODULE_NAME := main//模块名

MODULE_SRC_FILES := \
JNIOnLoad.cpp\       //要编译的源文件
main.mm\
MODULE_C_INCLUDES+=\  //包含TextFormatter.h的路径
$(MODULE_PATH)/../textformatter\
MODULE_SHARED_LIBRARIES+=textformatter //依赖textformatter库
include $(BUILD_SHARED_LIBRARY)
APP_SHARED_LIBRARIES += $(TARGET_ITOA_LIBRARIES)//添加Itoa.so文件

APP_SHARED_LIBRARIES的作用,我们为该变量指定 (TARGETITOALIBRARIES) ITOA_NDK/itoa/platform/arch-arm/usr/lib目录下的.so文件会被包含进libs目录。如果查看上述目录下的文件,会发现里面的.so文件远多于我们的实际需要。所以我们在构建项目之前需要从$ITOA_NDK/itoa/platform/arch-arm/usr/lib目录中删除或移除以下库:
libcg.so,libcore.so,libjnipp.so,libuikit.so

33.2.4 编译

在jni目录下运行以下命令:

$ITOA_NDK/itoa-build

也可以使用$ITOA_NDK/itoa-build -C/path/to/jni命令,该命令可以避免切换到jni文件夹。
命令执行成功后我们就可以得到所有Android平台运行Objective-C代码所必需的.so文件。
下面我们就是该在java层调用这些库。

33.3 创建Java层代码

java层包含一个Activity类以及一个具有native方法的TextFormatter类。Activity类代码如下:

public class MainActivity extends Activity{
    private TextView mTextView;
    @Override
    public void onCreate(Bundle savedInstanceState){
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        mTextView = (TextView) findViewById(R.id.text);
        //使用TextFormatter的formatString方法为TextView设置文本内容
        String text = TextFormatter.formatString("Text from Java");
    }
}

TextFormatter的Java代码如下:

public class TextFormatter{
    //native调用声明
    public static native String formatString(String text);

    //加载所有必需的库
    static{
        System.loadLibrary("macemu");
        System.loadLibrary("objc");
        System.loadLibrary("cf");
        System.loadLibrary("foundation");
        System.loadLibrary("textformatter");
        System.loadLibrary("main");
    }
}

需要加载的库如下:
Macemu:包含objc4和CoreFoundation库使用的一些API的模拟接口
Objc:objc4运行时
cf:CoreFoundation类库
Foundation:Foundation库
Textformatter:我们实现的Textformatter库
Main:我们实现的main库
运行应用程序后我们会看到TextView会被Java和Objective-C混合显示。
外链地址1
外链地址2
外链地址3


Hack 34 在Android中使用Scala

Scala是一种多泛型(multiparadigm)的编程语言,设计初衷是要集成面向对象编程和函数式编程的各种特性。在Android平台使用Scala代替Java创建项目的优点主要有以下几个方面:
1。比Java更简洁
2。兼容Java代码
3。闭包(closure)
4。比Java更易处理线程
我们知道,构建Android代码时首先会把Java类编译为字节码;然后字节码会被转化为dex文件。要让Scala代码运行于Android平台,我们需要具备能完成下述操作工具:
1。将Scala代码转化为字节码
2。运行Scala标准库以缩减应用程序大小
3。处理Java代码
4。创建APK
完成上述操作可以有很多方式,有一种方式是安装带有Android插件的SBT工具。
什么是SBT?
SBT是Simple Build Tool(简单构建工具)的缩写,是一个开源的Scala构建工具,该工具的优点主要体现以下几个方面:
1。项目结构与Maven类似
2。可以使用已有的Maven或者Ivy仓库(repository)管理依赖关系
3。允许混用Scala和Java代码
该Android插件是一个脚本,用于创建SBT可以编译的Android项目。并且还提供了为Market打包应用程序以及向设备部署应用程序等便捷的功能。
Scala版Activity的代码实现如下:

class ScalaActivity extends Activity{
    override def onCreate(savedInstanceState:Bundle){
        super.onCreate(savedInstanceState)
        setContentView(new TextView(this){
            setText("Activity coded in Scala")
        })
    }
}

这里创建了TextView的一个匿名子类,然后通过初始化代码块调用setText()方法。
要运行这个应用程序,我们可以启动SBT然后执行2下列命令:

android:package-debug
android:start-device

Scala的不足之处是创建APK的过程比较耗时间。这是因为经过了ProGuard过程,该过程遍历Scala库,并且移除任何无用部分。为了解决这个缺陷,一些开发者将Scala库添加到开发设备中,甚至还有一个应用程序可以将Scala安装在已经root过的设备上。

外链地址1
外链地址2
外链地址3
外链地址4
外链地址5

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值