跳到主要内容

3 篇博文 含有标签「Android」

查看所有标签

Android基础

· 阅读需 2 分钟

ThreadLocal

ThreadLocal是如何做到为每一个线程维护变量的副本的呢?其实实现的思路很简单:在ThreadLocal类中有一个Map,用于存储每一个线程的变量副本,Map中元素的键为线程对象,而值对应线程的变量副本。

java.lang.ThreadLocal<T>的具体实现

先来看一下ThreadLocal的set()方法的源码是如何实现的:

/**
* Sets the current thread's copy of this thread-local variable
* to the specified value. Most subclasses will have no need to
* override this method, relying solely on the {@link #initialValue}
* method to set the values of thread-locals.
*
* @param value the value to be stored in the current thread's copy of
* this thread-local.
*/
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}

ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}

通过getMap获取当前线程的ThreadLocalMap,然后将变量设置到这个ThreadLocalMap中,如果ThreadLocalMap为空时,则通过createMap方法创建。

线程隔离的秘密,就在于ThreadLocalMap这个类。ThreadLocalMap是ThreadLocal类的一个静态内部类,它实现了键值对的设置和获取(对比Map对象来理解),每个线程中都有一个独立的ThreadLocalMap副本,它所存储的值,只能被当前线程读取和修改。ThreadLocal类通过操作每一个线程特有的ThreadLocalMap副本,从而实现了变量访问在不同线程中的隔离。因为每个线程的变量都是自己特有的,完全不会有并发错误。还有一点就是,ThreadLocalMap存储的键值对中的键是this对象指向的ThreadLocal对象,而值就是你所设置的对象。

AndroidManifest解析

· 阅读需 6 分钟

关于AndroidManifest.xml

AndroidManifest.xml 是每个android程序中必须的文件。它位于整个项目的根目录,描述了package中暴露的组件(activities, services, 等等),他们各自的实现类,各种能被处理的数据和启动位置。 除了能声明程序中的Activities, ContentProviders, Services, 和Intent Receivers,还能指定permissions和instrumentation。

各个节点的详细介绍

(<manifest>)属性
<manifest  xmlns:android="http://schemas.android.com/apk/res/android"
package="com.woody.test"
android:sharedUserId="string"
android:sharedUserLabel="string resource"
android:versionCode="integer"
android:versionName="string"
android:installLocation=["auto" | "internalOnly | preferExternal"] >
</manifest>
  • sharedUserId,表明数据权限,因为默认情况下,Android给每个APK分配一个唯一的UserID,所以是默认禁止不同APK访问共享数据的。若要共享数据,第一可以采用Share Preference方法,第二种就可以采用sharedUserId了,将不同APK的sharedUserId都设为一样,则这些APK之间就可以互相共享数据了
  • sharedUserLabel,一个共享的用户名,它只有在设置了sharedUserId属性的前提下才会有意义
  • versionCode,给设备程序识别版本(升级)用的必须是一个interger值代表app更新过多少次
  • versionName,这个名称是给用户看的,你可以将你的APP版本号设置为1.1版
  • installLocation,安装参数,是Android2.2中的一个新特性,installLocation有三个值可以选择:internalOnly、auto、preferExternal。
    • preferExternal,系统会优先考虑将APK安装到SD卡上(当然最终用户可以选择为内部ROM存储上,如果SD存储已满,也会安装到内部存储上)
    • auto,系统将会根据存储空间自己去适应
    • internalOnly,是指必须安装到内部才能运行
(< Application >)属性
<application  android:allowClearUserData=["true" | "false"]
android:allowTaskReparenting=["true" | "false"]
android:backupAgent="string"
android:debuggable=["true" | "false"]
android:description="string resource"
android:enabled=["true" | "false"]
android:hasCode=["true" | "false"]
android:icon="drawable resource"
android:killAfterRestore=["true" | "false"]
android:label="string resource"
android:manageSpaceActivity="string"
android:name="string"
android:permission="string"
android:persistent=["true" | "false"]
android:process="string"
android:restoreAnyVersion=["true" | "false"]
android:taskAffinity="string"
android:theme="resource or theme" >
</application>
  • android:allowClearUserData,用户是否能选择自行清除数据,默认为true,程序管理器包含一个选择允许用户清除数据。当为true时,用户可自己清理用户数据,反之亦然
  • android:allowTaskReparentin,是否允许activity更换从属的任务,比如从短信息任务切换到浏览器任务
  • android:debuggable,当设置为true时,表明该APP在手机上可以被调试。默认为false,在false的情况下调试该APP
  • android:description,可以用于具体描述获取该许可的程序可以做哪些事情
  • android:label,应用名称
  • android:enabled,Android系统是否能够实例化该应用程序的组件
  • android:icon,APP的图标
  • android:name,为应用程序所实现的Application子类的全名。当应用程序进程开始时,该类在所有应用程序组件之前被实例化
  • android:permission,这个属性若在<application>上定义的话,是一个给应用程序的所有组件设置许可的便捷方式
  • android:presistent,该应用程序是否应该在任何时候都保持运行状态,默认为false
  • android:process,应用程序运行的进程名,它的默认值为<manifest>元素里设置的包名,当然每个组件都可以通过设置该属性来覆盖默认值。如果你想两个应用程序共用一个进程的话,你可以设置他们的android:process相同,但前提条件是他们共享一个用户ID及被赋予了相同证书的时候
  • android:taskAffinity,拥有相同的affinity的Activity理论上属于相同的Task,应用程序默认的affinity的名字是<manifest>元素中设定的package名
  • android:theme,资源的风格
(< Activity >)属性
<activity android:allowTaskReparenting=["true" | "false"]
android:alwaysRetainTaskState=["true" | "false"]
android:clearTaskOnLaunch=["true" | "false"]
android:configChanges=["mcc", "mnc", "locale",
"touchscreen", "keyboard", "keyboardHidden",
"navigation", "orientation", "screenLayout",
"fontScale", "uiMode"]
android:enabled=["true" | "false"]
android:excludeFromRecents=["true" | "false"]
android:exported=["true" | "false"]
android:finishOnTaskLaunch=["true" | "false"]
android:icon="drawable resource"
android:label="string resource"
android:launchMode=["multiple" | "singleTop" |
"singleTask" | "singleInstance"]
android:multiprocess=["true" | "false"]
android:name="string"
android:noHistory=["true" | "false"]
android:permission="string"
android:process="string"
android:screenOrientation=["unspecified" | "user" | "behind" |
"landscape" | "portrait" |
"sensor" | "nosensor"]
android:stateNotNeeded=["true" | "false"]
android:taskAffinity="string"
android:theme="resource or theme"
android:windowSoftInputMode=["stateUnspecified",
"stateUnchanged", "stateHidden",
"stateAlwaysHidden", "stateVisible",
"stateAlwaysVisible", "adjustUnspecified",
"adjustResize", "adjustPan"] >
</activity>
  • android:alwaysRetainTaskState,是否保留状态不变, 比如切换回home, 再从新打开,activity处于最后的状态。比如一个浏览器拥有很多状态(当打开了多个TAB的时候),用户并不希望丢失这些状态时,此时可将此属性设置为true
  • android:clearTaskOnLaunch,比如 P 是 activity, Q 是被P 触发的 activity, 然后返回Home, 重新启动 P,是否显示 Q
  • android:configChanges,正常情况下. 如果手机旋转了.当前Activity后杀掉,然后根据方向重新加载这个Activity. 就会从onCreate开始重新加载. 如果你设置了 这个选项, 当手机旋转后,当前Activity之后调用onConfigurationChanged() 方法. 而不跑onCreate方法等
  • android:excludeFromRecents,是否可被显示在最近打开的activity列表里,默认是false
  • android:finishOnTaskLaunch,当用户重新启动这个任务的时候,是否关闭已打开的activity,默认是false,如果这个属性和allowTaskReparenting都是true,这个属性就是王牌。Activity的亲和力将被忽略。该Activity已经被摧毁并非re-parented
  • android:launchMode,在多Activity开发中,有可能是自己应用之间的Activity跳转,或者夹带其他应用的可复用Activity。可能会希望跳转到原来某个Activity实例,而不是产生大量重复的Activity,默认为standard
    • standard:就是intent将发送给新的实例,所以每次跳转都会生成新的activity
    • singleTop:也是发送新的实例,但不同standard的一点是,在请求的Activity正好位于栈顶时(配置成singleTop的Activity),不会构造新的实例
    • singleTask:和后面的singleInstance都只创建一个实例,当intent到来,需要创建设置为singleTask的Activity的时候,系统会检查栈里面是否已经有该Activity的实例。如果有直接将intent发送给它
    • 首先说明一下task这个概念,Task可以认为是一个栈,可放入多个Activity,比如启动一个应用,那么Android就创建了一个Task,然后启动这个应用的入口Activity,那在它的界面上调用其他的Activity也只是在这个task里面。singleInstance模式就是将该Activity单独放入一个栈中,这样这个栈中只有这一个Activity,不同应用的intent都由这个Activity接收和展示,这样就做到了共享。当然前提是这些应用都没有被销毁,所以刚才是按下的HOME键,如果按下了返回键,则无效
  • android:multiprocess,是否允许多进程,默认是false

NDK入门

· 阅读需 6 分钟

JNI中C与java类型对应表

字符Java类型C类型
Vvoidvoid
Zjbooleanboolean
Ijintint
Jjlonglong
Djdoubledouble
Fjfloatfloat
Bjbytebyte
Cjcharchar
Sjshortshort
数组则以"["开始,用两个字符表示
[IjintArrayint[]
[FjfloatArrayfloat[]
[BjbyteArraybyte[]
[CjcharArraychar[]
[SjshortArrayshort[]
[DjdoubleArraydouble[]
[JjlongArraylong[]
[ZjbooleanArrayboolean[]

开始编写jni

当java调用,

static {
System.loadLibrary("hello");
}

jni会尝试去寻找JNI_OnLoad方法,所以一些初始化的东西就可以在这里面实现。

方法注入

本人表示网上一堆乱七八糟的方式会让刚入门的同学遇到无限的坑,被坑过的我,现在整理了一下,顺便梳理过程

首先列出几个jni的方法,这些方法都可以在jni.h中找到,在jni.h中都大部分提供了有C与C++两个版本的,使用时请注意。

JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void* reserved);//jni入口

JNIEXPORT void JNICALL JNI_OnUnload(JavaVM* vm, void* reserved);//结束jni之后调用的

jint GetEnv(void** env, jint version)//传入指定的jni版本,返回是否支持此版本,成功返回JNI_OK

jclass FindClass(const char* name)//找到指定的java类,使用全包名+类名

jint RegisterNatives(jclass clazz, const JNINativeMethod* methods,jint nMethods)//注册本地方法

有了以上的方法,简单的NDK开发就可以开始了,列出代码讲解可能来的更加痛快。

//jni入口
JNIEXPORT jint JNI_OnLoad(JavaVM* jvm, void* reserved){
JNIEnv *env = NULL;
//使用reinterpret_cast进行类型转换,我之前没有使用这种方式转换,下面的代码一直有问题,所以强烈建议使用
if(jvm->GetEnv(reinterpret_cast<void **>(&env),JNI_VERSION_1_4) != JNI_OK){
return JNI_ERR;
}
//registerMethod 自己写的方法,用于方法注册
if(registerMethod(env) == JNI_ERR){
return JNI_ERR;
}
return JNI_VERSION_1_4;

}

这里说明一下 JNI_VERSION_1_4 是系统定义的宏,除了这个还有

  • JNI_VERSION_1_1
  • JNI_VERSION_1_2
  • JNI_VERSION_1_6

现在的版本一般使用 JNI_VERSION_1_4。

下面是JNINativeMethod的结构体:

typedef struct {
const char* name;
const char* signature;
void* fnPtr;
} JNINativeMethod;

对于JNINativeMethod结构体说明,网上一大把的,

  • 第一个变量name是Java中函数的名字。
  • 第二个变量signature,用字符串是描述了函数的参数和返回值,就是方法签名,例如:()V,表示void func();()表示方法参数,V表示返回值,对应的类型可以参考上面的表格,如果参数是java的类,使用"L"开头,";"结束,例如:("Ljava/lang/Object;")I,表示int func(String str);多个参数例子:(IZLjava/lang/String;)V,表示void func(int i,boolean flag,String str,),()里面直接写类型就可以了
  • 第三个变量fnPtr是函数指针,指向C函数。格式都是固定的(void*)xxx,xxx就是C++的方法名

static JNINativeMethod gMethods []= {
{"stringFromJni", "()Ljava/lang/String;", (void*)stringFromJni},
};

int registerMethod(JNIEnv *env){
jclass clazz = env->FindClass(CLASS_MODEL);
if(clazz == NULL){
return JNI_ERR;
}
if(env->RegisterNatives(clazz,gMethods,NELEM(gMethods))<0){
return JNI_ERR;
}
return JNI_OK;
}

CLASS_MODEL是对应java中的全包名+类名,可以使用下面的方式,也可以定义 成static const char *CLASS_MODEL

#define CLASS_MODEL  "com/wawov/nativeapp/activity/MainActivity"

#ifndef NELEM
# define NELEM(x) ((int) (sizeof(x) / sizeof((x)[0])))
#endif

NELEM用于计算gMethods的size。

本地的方法

jstring stringFromJni(JNIEnv *env,jobject thiz){
return env->NewStringUTF("hello jni");
}

注意C++是至上而下编译的,所以编写是注意方法的位置,到到简单的jni就写好了


Android.mk文件简介

LOCAL_PATH := $(call my-dir) 每个Android.mk文件必须以定义LOCAL_PATH为开始。它用于在开发tree中查找源文件。 宏my-dir 则由Build System提供。返回包含Android.mk的目录路径。

include $(CLEAR_VARS) CLEAR_VARS 变量由Build System提供。并指向一个指定的GNU Makefile,由它负责清理很多LOCAL_xxx. 例如:LOCAL_MODULE, LOCAL_SRC_FILES, LOCAL_STATIC_LIBRARIES等等。但不清理LOCAL_PATH. 这个清理动作是必须的,因为所有的编译控制文件由同一个GNU Make解析和执行,其变量是全局的。所以清理后才能避免相互影响。

LOCAL_MODULE := hello-jni LOCAL_MODULE模块必须定义,以表示Android.mk中的每一个模块。名字必须唯一且不包含空格。 Build System会自动添加适当的前缀和后缀。例如,foo,要产生动态库,则生成libfoo.so. 但请注意:如果模块名被定为:libfoo.则生成libfoo.so. 不再加前缀。

LOCAL_SRC_FILES := hello-jni.c LOCAL_SRC_FILES变量必须包含将要打包如模块的C/C++ 源码。 不必列出头文件,build System 会自动帮我们找出依赖文件。 缺省的C++源码的扩展名为.cpp. 也可以修改,通过LOCAL_CPP_EXTENSION。

include $(BUILD_SHARED_LIBRARY) BUILD_SHARED_LIBRARY:是Build System提供的一个变量,指向一个GNU Makefile Script。 它负责收集自从上次调用 include $(CLEAR_VARS) 后的所有LOCAL_XXX信息。并决定编译为什么

  • BUILD_STATIC_LIBRARY:编译为静态库。
  • BUILD_SHARED_LIBRARY :编译为动态库
  • BUILD_EXECUTABLE:编译为Native C可执行程序

LOCAL_LDLIBS := -llog LOCAL_LDLIBS可以用它来添加系统库,-lxxx

简单的Android.mk例子:

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

LOCAL_MODULE :=hello #System.loadLibrary("hello")
LOCAL_SRC_FILES :=hello.cpp

LOCAL_LDLIBS := -llog

include $(BUILD_SHARED_LIBRARY)

关于Android.mk文件还有很多的内容,兴趣的同学可以移步到: http://android.mk/ https://developer.android.com/ndk/guides/android_mk.html#mdv


Application.mk

一般简单的只需要这样写:

APP_ABI :=armeabi

这里的是cup的架构除此之外,还有别的一些架构,如:x86、mips。当然在Application.mk中,可写的东西也是挺多的。对于刚入门来说,可以慢慢了解。

有兴趣的同学可以移步到: https://developer.android.com/ndk/guides/application_mk.html

好了,jni开发的重要的几个文件就准备得差不多了,接下来就是这些文件放哪里和怎么调用的问题了。


一般的会在main目录下创建一个jni的文件夹放入hello.cpp、Android.mk和Application.mk文件,同时在建一个jniLibs文件夹放入编译好的.so文件。

接下来我们需要在java类中定义一个native的方法:

public native  String stringFromJni();

还记得我们在hello.cpp文件中定义了一个本地的方法:

jstring stringFromJni(JNIEnv *env,jobject thiz){
return env->NewStringUTF("hello jni");
}

这时候我们cd 到jni文件夹中,使用ndk-build命令,编译成功的话,会在jni目录下生成2个文件夹libs、obj,我们将libs中的armeabi文件夹复制到jniLibs中。接下来,在java类中这样做:


static {
System.loadLibrary("hello");
}

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
tv = (TextView) findViewById(R.id.id_tv);
tv.setText(stringFromJni());
}

如果启动app,看到hello jni,那恭喜你,一个简单ndk就完成了。