Android Studio

Android Studio v0.1

Download

Google I/O大会上,发布了Android Studio的预览版。
看发布会上演示时,着实让我惊艳了一番,迫不及待的下载下来,安装后却发现,新建工程时,一直卡在下载某个文件的对话框上。。。人品不行吗?
Android Studio v0.1

下文来源: 网易科技报道

(Android studio)首先解决的一个问题是多分辨率。Android设备拥有大量不同尺寸的屏幕和分辨率,根据新的Studio,开发者可以很方便的调整在各个分辨率设备上的应用。

同时Studio还解决语言问题,多语言版本、支持翻译都让开发者更适应全球开发环境。Studio还提供收入记录功能。

最大的改变在于Beta测试的功能。Studio提供了Beta Testing,可以让开发者很方便试运行。

想想就很给力,希望快些发布正式版~~

给TextView中的文字加上链接

项目上需要给TextView中特定格式文本加上”超链接“,让用户可以点击链接后,可以开启关心该uri的Activity。

如给”abc12345“加上链接,使其点击后,开启Activity,该Activity意图过滤器关心的uri scheme为”abc://“。

利用正则表达式,匹配出”abc12345“。
利用Linkify类给文字加上链接到”abc://abc12345”:

1
2
3
4
5
public void addLink(TextView text){
String localDesc = description;
text.setText(localDesc);
Linkify.addLinks(text, Pattern.compile("(ac\\d{5,})", Pattern.CASE_INSENSITIVE),"abc://");
}

这样用户点击后,内部会startActivity(intent);
该intent的data为 “abc://abc12345”,这样注册了该scheme IntentFilter的Activity就会被开启了。

当然,也可以给符合http协议的url自动加上超链接:

1
2
Pattern http = Pattern.compile("(http://(?:[a-z0-9.-]+[.][a-z]{2,}+(?::[0-9]+)?)(?:/\\S*)?)",Pattern.CASE_INSENSITIVE);
Linkify.addLinks(text,http,"http://");

用户点击后,开启的Intent 包含data为 “http://…”,这样就会开启浏览器,浏览该网址了。

Android - Parcel & Parcelable

对于Parcel的理解:

在Android系统中,定位为针对内存受限的设备,因此对性能要求更高,另外系统中采用了新的IPC(进程间通信)机制,必然要求使用性能更出色的对象传输方式。显然,JAVA的Serialize利用外部存储设备被认为是低效的,
可能也无法完美匹配Binder机制。在这样的环境下,Parcel被设计出来,其定位就是轻量级的高效的对象序列化和反序列化机制。

为了便于ipc之间传递的数据的操作,binder引入了parcel的概念。parcel可以想成快递公司的包装箱,需要传递的各种类型的数据都被打包进parcel类,binder负责传递parcel对象,接收端则从parcel解出数据。这样的机制即减少了各种数据类型对传递的复杂性,又可以通过增加打包/解包parcel的数据类型,轻易实现扩展。
parcel已经支持容纳基本数据类型和一些复合数据类型。

下面内容转自 dairyman的专栏:http://blog.csdn.net/dairyman000/article/details/7247619

Parcel 在英文中有两个意思,其一是名词,为包裹,小包的意思; 其二为动词,意为打包,扎包。邮寄快递中的包裹也用的是这个词。
Android采用这个词来表示封装消息数据。这个是通过IBinder通信的消息的载体。需要明确的是Parcel用来存放数据的是内存(RAM),而不是永久性介质(Nand等)。

Parcelable

定义了将数据写入Parcel,和从Parcel中读出的接口。
一个实体(用类来表示),如果需要封装到消息中去,就必须实现这一接口,实现了这一接口,该实体就成为“可打包的”了。

接口的定义如下:

Parcelable.java
1
2
3
4
5
6
7
8
9
10
11
12
13
public interface Parcelable {  
//内容描述接口,基本不用管
public int describeContents();
//写入接口函数,打包
public void writeToParcel(Parcel dest, int flags);
//读取接口,目的是要从Parcel中构造一个实现了Parcelable的类的实例处理。
//因为实现类在这里还是不可知的,所以需要用到模板的方式,继承类名通过模板参数传入。
//为了能够实现模板参数的传入,这里定义Creator嵌入接口,内含两个接口函数分别返回单个和多个继承类实例。
public interface Creator<T> {
public T createFromParcel(Parcel source);
public T[] newArray(int size);
}
}

在实现Parcelable的实现中,规定了必须定义一个静态成员, 初始化为嵌入接口的实现类。

1
2
public static Parcel.Creator<DrievedClassName>  CREATOR
= new Parcel.Creator<DrievedClassName>();

让多个Fragment 切换时不重新实例化

在项目中需要进行Fragment的切换,一直都是用replace()方法来替换Fragment:

1
2
3
4
5
6
7
8
9
public void switchContent(Fragment fragment) {
if(mContent != fragment) {
mContent = fragment;
mFragmentMan.beginTransaction()
.setCustomAnimations(android.R.anim.fade_in, R.anim.slide_out)
.replace(R.id.content_frame, fragment) // 替换Fragment,实现切换
.commit();
}
}

但是,这样会有一个问题:
每次切换的时候,Fragment都会重新实例化,重新加载一边数据,这样非常消耗性能和用户的数据流量。

就想,如何让多个Fragment彼此切换时不重新实例化?

翻看了Android官方Doc,和一些组件的源代码,发现,replace()这个方法只是在上一个Fragment不再需要时采用的简便方法。

正确的切换方式是add(),切换时hide()add()另一个Fragment;再次切换时,只需hide()当前,show()另一个。
这样就能做到多个Fragment切换不重新实例化:

1
2
3
4
5
6
7
8
9
10
11
12
public void switchContent(Fragment from, Fragment to) {
if (mContent != to) {
mContent = to;
FragmentTransaction transaction = mFragmentMan.beginTransaction().setCustomAnimations(
android.R.anim.fade_in, R.anim.slide_out);
if (!to.isAdded()) { // 先判断是否被add过
transaction.hide(from).add(R.id.content_frame, to).commit(); // 隐藏当前的fragment,add下一个到Activity中
} else {
transaction.hide(from).show(to).commit(); // 隐藏当前的fragment,显示下一个
}
}
}

————Edited 2015.2.7————-

问题一:保存UI与数据的内存消耗

上面所述为避免重新实例化而带来的“重新加载一边数据”、“消耗数据流量”,其实是这个Fragment不够“纯粹”。

Fragment应该分为UI FragmentHeadless Fragment

前者是指一般的定义了UI的Fragment,后者则是无UI的Fragment,即在onCreateView()中返回的是null。将与UI处理无关的异步任务都可以放到后者中,而且一般地都会在onCreate()中加上setRetainInstance(true),故而可以在横竖屏切换时不被重新创建和重复执行异步任务。

这样做了之后,便可以不用管UI Fragment的重新创建与否了,因为数据和异步任务都在无UI的Fragment中,再通过Activity 的 FragmentManager 交互即可。

只需记得在Headless Fragment销毁时将持有的数据清空、停止异步任务。

怎样保证一个可能失败的任务一定被执行

将可能失败的任务(executeDownload())包在一个循环体中,

循环体有一个finished标记,标记为true时停止
当任务失败时抛出 RetryXXX 异常(RetryXXX 异常为自定义的异常)

此时finished标记仍然为false,
然后到finally中取消原先的任务,那么就会到下次循环体,再次尝试执行任务….
如此循环,直到任务确认执行完毕不再抛出RetryXXX 异常。

如此,便能够确保可能失败的任务一定执行成功。

根据Android系统版本禁用/启用某些Activity

在开发中,需要用到Android新版本的特性,而又要适配旧版本。如何让Activity低版本中不显示,而高版本无影响呢?

在查看APIDemos源码时,看到AndroidManifest.xml中有这样一段

AndroidManifest.xml
1
2
3
4
5
6
<activity android:name=".animation.AnimationLoading"
android:enabled="@bool/atLeastHoneycomb">
<intent-filter>
...
</intent-filter>
</activity>

android:enabled这个属性是控制Activity是否能被实例化的。
@bool/atLeastHoneycomb是指values/bools.xml中的atLeastHoneycomb属性的值。
该文件定义了许多与平台相关的atLeastXxx属性,默认值都是false

在APIDemos/src/res/ 目录下有许多的values-vXX目录,各自下面都有bools.xml
values-v11/bools.xml中可以看到

bools.xml
1
2
3
4
<resources>
<!-- True if running under Honeycomb or later. -->
<bool name="atLeastHoneycomb">true</bool>
</resources>

即,.animation.AnimationLoading这个Activity在v11版本以上系统可以被实例化,以下则不能。
如此便做到了让应用更好的适配各个版本。

延伸:

activity-alias
为某个Activity起别名,重复使用同一个Activity,同样可以做到平台相关的一些处理:
比如有Activity需要在Launcher上创建入口,但又不想在低版本上这样做。

完全退出应用

想要完全退出应用,以前自己干掉自己的方法已经不能再用了。
那么如何做到完全退出所有Activity呢?

方法一、自己维护一个Activity任务栈

用集合保存所有Activity实例,退出应用时,遍历集合,逐个消灭

MyApplication.java
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
import java.util.LinkedList;
import java.util.List;
import android.app.Activity;
import android.app.AlertDialog;
import android.app.Application;
import android.content.DialogInterface;
import android.content.Intent;

public class MyApplication extends Application {
private List<Activity> mList = new LinkedList<Activity>();
private static MyApplication instance;

private MyApplication() {
}

public synchronized static MyApplication getInstance() {
if (instance == null) {
instance = new MyApplication();
}
return instance;
}

// add Activity
public void addActivity(Activity activity) {
if(mList.contains(activity) return;
mList.add(activity);
}
// 遍历集合,逐一finish
// 最好从Activity栈底开始干掉
public void exit() {
try {
for (Activity activity : mList) {
if (activity != null)
activity.finish();
}
} catch (Exception e) {}
}

@Override
public void onLowMemory() {
super.onLowMemory();
System.gc();
}
}

在每个Activity的onCreate方法中添加类似代码:

1
2
3
4
5
6
7
8
9
10
11
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
MyApplication.getInstance().addActivity(this);
}

// ...

public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
MyApplication.getInstance().addActivity(this);
}

在需要退出程序的时候,调用:

1
MyApplication.getInstance().exit();

此方法也可参考链接:http://maosidiaoxian.iteye.com/blog/1404725

方法二、使用广播

编写一个BaseActivity,此为所有Activity的基类,其内部注册一个退出Action的广播接收者,故而其所有的子Activity都会注册这个接收者。
当收到退出广播时,各个子Activity调用自己的finish(),结束一生。

BaseActivity.java
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
public abstract class BaseActivity extends Activity {
protected static final String ACTION_EXIT = "net.yrom.action.EXIT";
// 写一个广播的内部类,当收到动作时,结束activity
private BroadcastReceiver exitReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
((Activity)context)finish(); // 子Activity结束今生
}
};

@Override
public void onResume() {
super.onResume();
// 在基类activity中注册广播
IntentFilter filter = new IntentFilter(ACTION_EXIT);
registerReceiver(exitReceiver, filter);
}
// 子Activity调用这个方法来退出整个应用
public void close() {
Intent intent = new Intent(ACTION_EXIT);
sendBroadcast(intent);// 该函数用于发送广播
finish();
}
@Override
protected void onDestroy() {
super.onDestroy();
unregisterReceiver(exitReceiver);

}
}

OAuth

oauth logo

下面文字摘抄自wiki:http://zh.wikipedia.org/wiki/OAuth

OAuth(开放授权)是一个开放标准,允许用户让第三方应用访问该用户在某一网站上存储的私密的资源(如照片,视频,联系人列表),而无需将用户名和密码提供给第三方应用。
OAuth允许用户提供一个token(令牌),而不是用户名和密码来访问他们存放在特定服务提供者的数据。每一个令牌授权一个特定的网站(例如,视频编辑网站)在特定的时段(例如,接下来的2小时内)内访问特定的资源(例如仅仅是某一相册中的视频)。这样,OAuth允许用户授权第三方网站访问他们存储在另外的服务提供者上的信息,而不需要分享他们的访问许可或他们数据的所有内容。

在认证和授权的过程中涉及的三方包括:

服务提供方,用户使用服务提供方来存储受保护的资源,如照片,视频,联系人列表。
用户,存放在服务提供方的受保护的资源的拥有者。
客户端,要访问服务提供方资源的第三方应用,通常是网站,如提供照片打印服务的网站。在认证过程之前,客户端要向服务提供者申请客户端标识。

使用OAuth进行认证和授权的过程如下所示:

  1. 用户访问客户端的网站,想操作用户存放在服务提供方的资源。
  2. 客户端向服务提供方请求一个临时令牌。
  3. 服务提供方验证客户端的身份后,授予一个临时令牌。oauth_token=49e31aa29ab01f2ddda94fe7d0105d87
  4. 客户端获得临时令牌后,将用户引导至服务提供方的授权页面请求用户授权。在这个过程中将临时令牌和客户端的回调连接发送给服务提供方。
  5. 用户在服务提供方的网页上输入用户名和密码,然后授权该客户端访问所请求的资源。
  6. 授权成功后,服务提供方引导用户返回客户端的网页。
  7. 客户端根据临时令牌从服务提供方那里获取访问令牌。
  8. 服务提供方根据临时令牌和用户的授权情况授予客户端访问令牌。accesstoken,tokensecret
  9. 客户端使用获取的访问令牌访问存放在服务提供方上的受保护的资源。

BuildConfig.DEBUG 的妙用

ADT(R17)会自动生成一个名称为BuildConfig的类,该类包含一个DEBUG 常量,该常量会根据当前项目的Build类型自动设置值。
可以通过(BuildConfig.DEBUG) 常量来编写只在Debug模式下运行的代码。
如果有些代码不想在发布后执行,就可以使用该功能。

Added a feature that allows you to run some code only in debug mode.
Builds now generate a class called BuildConfig containing a DEBUGconstant that is automatically set according to your build type. You can check the (BuildConfig.DEBUG) constant in your code to run debug-only functions.

比如调试日志,不想在软件发布后被其他开发者看到,过去的方式是设置一个全局变量,标记软件为DEBUG模式还是其他模式。

1
2
3
4
5
public static boolean DEBUG = true;  
//...
if(DEBUG==true){
Log.d(TAG,"output something");
}

这样打包发布之前还要修改DEBUG变量的值,有时候不记得或者变量到处都有,重新修改、编译、发布,费时费力。

有了BuildConfig.DEBUG之后,代码变成了

1
2
3
if (BuildConfig.DEBUG) {  
Log.d(TAG, "output something");
}

在编码期,编译发布前,BuildConfig.DEBUG的值自动为true
需要打包时,先禁用 “Build Automatically”, “Clean”工程目录,再通过 “Android Tools -> Export Signed Application Package“ 编译打包,BuildConfig.DEBUG的值会被改为false。
开发者自己不用修改其他东西了。

流程如下[1]

  1. Project -> Build Automatically
  2. Project -> Clean
  3. Project -> Build
  4. Android Tools -> Export Android application

从此,Logcat清爽了许多。

[1]-When does ADT set BuildConfig.DEBUG to false?

Android.mk 的使用姿势

1
2
3
4
5
6
7
8

LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
# Here we give our module name and source file(s)
LOCAL_MODULE := Hello
LOCAL_SRC_FILES := Hello.c

include $(BUILD_SHARED_LIBRARY)

LOCAL_PATH:=$(call my-dir)

LOCAL_PATH是定义源文件在哪个目录
my-dir 是个定义的宏方法,$(call my-dir)就是调用这个宏方法, 返回值就是Android.mk文件所在的目录

include $(CLEAR_VARS)

CLEAR_BARS 变量是build system里面的一个变量
这个变量指向了所有的类似 LOCAL_XXX的变量,执行完这一句话,
编译系统就把所有的类似LOCAL_MODULE,LOCAL_SRC_FILES,LOCAL_STATIC_LIBRARIES,…这样的变量统统清除掉,
但是不会清除掉 LOCAL_PATH

LOCAL_MODULE := Hello

要生成的库的名字,这个名字要是唯一的,不能有空格。
编译后系统会自动在前面加上”lib”前缀, 如Hello 就编译成了libHello.so

还有个特点就是如果起名叫libHello 编译后ndk就不会给module名字前加上”lib”了

LOCAL_SRC_FILES = :Hello.c

这个是指定你要编译哪些文件,多个文件用空格分开
不需要指定头文件 ,引用哪些依赖, 因为编译器会自动找到这些依赖自动编译

include $(BUILD_SHARED_LIBRARY)

指定编译后生成的库的类型,.so。
如果是静态库.a 则是include $(BUILD_STATIC_LIBRARY)

别的参数

LOCAL_CPP_EXTENSION := cc //指定c++文件的扩展名
LOCAL_MODULE := ndkfoo
LOCAL_SRC_FILES := ndkfoo.cc

LOCAL_LDLIBS += -llog -lvmsagent -lmpnet -lmpxml -lH264Android
指定需要加载一些别的什么库.

加载-llog 可以让c代码使用logcat

C代码中增加:

1
2
3
4
5
6
7
8
9
10
11
#include <android/log.h>
#define LOG_TAG "System.out"
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)

/*函数中调用:*/
void foo()
{
LOGI("info\n");
LOGD("debug\n");
}