Java For Android


原文地址
很多语言都能用来开发安卓app,谷歌提倡开发者使用Java语言,但它和其他平台上使用的Java不完全相同,有一些差异和特点,但是作为开发者去了解这些是非常重要的。
本教程中,你可以在安卓的世界快速浏览一遍Java并且了解有关他提供的一些功能,通过这些你还可以知道:

  • 安卓app和PC平台上的Java程序有何不同
  • 如何使用安卓的面向对象编程
  • 什么是Java接口,如何通过它来与app的其他部分进行通信
  • 什么是Java注释,他们是怎么提供有关app部分的额外信息的
    本教程假设你至少熟悉一种面向对象的编程语言,如果不是,那也无关紧要,不过熟悉的话更容易理解本文中讲到的一些概念。

注意:本文与标准的raywenderlich.com教程有所不同,因为其中描述了许多高级主题而不是示例程序。在你开始搭建安卓app之前,建议你先阅读本文,因为这里能给一个很好的基础。


带上一杯你最爱的“Java”(咖啡),开始一段本语言的神奇之旅吧——安卓style!

Java和安卓

一个有趣的事实:安卓不使用“纯粹”的Java!这听起来很奇怪,因为当你将传统的Java程序和安卓app的代码进行比较时,很难发现有什么不同。


image

虽然熟练的Java开发者在编写和开发安卓app时会感觉有一些熟悉,但在编译和运行的时候这种熟悉感就戛然而止了,原因是你会发现安卓在编译期间对app的处理方式,对你来说是个未知领域。
Java的主要吸引力在于“一次编写(编译?),到处运行”的能力,这个语言在软件从一个平台到其他平台的昂贵的移植过程中作为银弹销售。(啥意思?懵逼ing)
得益于Java程序的编译,使得真正的软件工程奇迹成为可能。
大部分其他语言的编译期间,编译器会链接和优化程序,然后将它翻译成机器码,这是程序运行时计算机能够理解和执行的指令集。
尽管机器码的执行很迅速,但会被它运行的平台限制。你是否曾经好奇为啥iOS平台的程序无法在Windows上运行,这是其中一个原因。
相比之下,Java做的有些不同,Java编译器不是将程序翻译成机器码,而是翻译成叫做字节码的中间形式,它生成类似机器码的指令集,但它运行在虚拟机上,而不是特定的架构上。
使用虚拟机意味着,只要它能够读取和解析字节码的指令,程序就能愉快的在主机上运行,这确保了跨平台的兼容性。
现在你能明白为什么当你没有Java Runtime Environment(JRE)时大多数Java程序会提示你下载了——这是大多数平台的默认虚拟机。

Java for Android是...不一样的

编译安卓app和把Java文件转换成字节码是一样的,除此以外都不一样。当app(由字节码组成)安装到一个设备时,期间会进入第二个步骤,这个app的字节码会转换成为安卓设备优化过的机器码,提高app的运行性能,这个过程被称为预编译(AoT),安卓运行时(ART)虚拟机使之成为可能。


注意:预编译是一个许多编程语言都使用的概念,你可以在Wikipedia了解有关详情


预编译只发生在安卓KitKat(4.4)和更高版本中,但也提供了向后的兼容性。安卓早期版本依赖另一个虚拟机叫Dalvik。比如在进行时(ART)Dalvik改变了Java的字节码,转为特定的字节码,进行了许多效率上的更改,为最初设计的低功耗设备安卓优化app。
但与ART不同的事,Dalvik直到运行时才把字节码编译成机器码——使用一种叫即时编译的方式,这个过程跟在PC上Java虚拟环境使用的过程很接近。
当Dalvik有能力在运行时分析Dalvik字节码的常用部分时,安卓Froyo(2.2)看到了一项改进,这些指令会Dalvik被永久翻译成机器码以提高app的速度。


注意:基于跟踪的即时编译(JIT)不是Java独有的,再次说明,Wikipedia里的解释做的不错。


在安卓app编译的任一情况下,转成字节码都让为安卓编写的Java不那么“纯粹”。
字节码的改变限制了app的可移植性,从而否定了Java语言的承诺:“一次编写,随处运行”。
安卓和Java的另一个不同点是标准库的可用性,Java的可移植是因为他依赖于标准化的库集,让他能够在多个平台上使用,比如网络和UI库。
安卓提供了Java提供的一个子集,而这些只能用于安卓。

Walking Through Java on Android

安卓广泛使用了Java采用面向对象编程的范例,他旨在解决装、继承和多态的概念,当你构建app的时候会用到这些。
安卓中的所有对象都以某种形式从Object类继承,在此之上创建函数以提供特定的行为和功能。看一下通过Android API提供的一些可用对象,你能看到每个对象继承的层次结构,所有的对象最终都是继承Object类

Not-So-Paranormal Activity

(以下“屏幕”应该是指界面)
Activity类是app专用于特定屏幕的一个对象,可以看作他就是一些处理和展示所有驱动该屏幕功能的相关工作的东西。
由于app至少有一两个功能,可以把他们分散到几个屏幕中,这样你的界面看起来不会杂乱无章。要创建一个屏幕,你可以新建一个Java类然后扩展Activity类,像这样:

public class MainMenuActivity extends Activity {

}

就像那样,因某个特定目的给app的某个区域存根,Activity类还不知道他的目的因为你没告诉他要做什么或者如何展示。
你可以创建活动(activity)的外观,即布局,有两种方式:

  • 在一个XML文件中声明
  • 以编码方式在Java类中创建
    常见的方式是在一个XML文件中创建,如果需要定制布局的某些方面,你可以使用编码方式。
    本文不会介绍任何创建布局的过程,但如果你打算编写安卓app,理解布局是如何做的非常有用,因为这是常规任务。可以从Android's documentation学习更多有关布局的知识。
    以下代码段演示了你应该如何使用res/layout/activity_main_menu.xml中的XML布局来指定一个activity确定他的外观。
public class MainMenuActivity extends Activity {
  // 1
  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState); // 2

    setContentView(R.layout.activity_main_menu); // 3
  }
}

一点一点的看:

  1. 首先,重写onCreate()方法,这是activity中的其中一个可用方法,他会在activity被创建的时候执行,给你足够的时间来设置你需要的东西,这通常是你创建一个activity的第一个方法。
  2. 然后调用父类的onCreate()方法确保activity中必要的设置都能执行,这是必须的一步,如果没有app就会崩溃发出异常警告。
  3. 最后,使用setContentView()方法传入从R引用的布局来让activity展示界面。

注意:R是安卓为你的app各种资源生成的一个特殊类,他可以是你上面看到的界面布局,或者是一些像本地字符串,图片或动画的东西。安卓在构建的时候生成R,所以不应该去修改他。更多相关信息developer.android.com


XML布局很有可能包含构建UI的元素,在activity中通过R来访问这些元素,像这样:

public class MainMenuActivity extends Activity {

  TextView mTextView;
  Button mButton;

  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    setContentView(R.layout.activity_main_menu); 

    mTextView = (TextView) findViewById(R.id.textview_main_menu);
    mButton = (Button) findViewById(R.id.button_main_menu);
  }
}

关键点是findViewById()方法,在XML布局中搜索由他们的id指定的view对象(视图),此函数允许你从Java中操作他们。注意每次调用你都要强制转换成合适的view子类,因为findViewById()只返回一个view对象——所有UI组件继承的根类。
只要用Java代码获取了某个view元素,你就能与他们进行交互并且让他们执行你认为合适的动作。
以下代码段演示的是当用户点击按钮时添加一个动作:

public class MainMenuActivity extends Activity {

  TextView mTextView;
  Button mButton;

  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    setContentView(R.layout.activity_main_menu); 

    mTextView = (TextView) findViewById(R.id.textview_main_menu);
    mButton = (Button) findViewById(R.id.button_main_menu);

    mButton.setOnClickListener(new View.OnClickListener() {
      @Override
      public void onClick(View v) {
        mTextView.setText("Hello World");
      }
    });
  }
}

以上代码在每次点击按钮时执行onClick()方法。在这个方法中,让最初是空的textView显示文字“Hello World”。
使用这个简单的方式连接views到某个类并且提供一些执行某些确定事件的动作,你就能创建高度复杂的活动。记住这些基本步骤,你就可以掌握Java和安卓是如何融合到一起的。

Lets Go Modeling(我们来建模)

模型是编写安卓app的必要条件,别跟T台走秀的那种模型(模特)混淆了,这些模型允许你创建包含数据结构和功能的对象,你可以使用这些对象实现非常棒的效果。
模型存在于你分离的UI类中,这种分离不仅有助于保持app的组织性,也符合面向对象编程中封装的思想。
典型的模型看起来像这样:

public class ReminderList {
    
  private ArrayList mReminderArrayList;

  public ReminderList() {
    mReminderArrayList = new ArrayList<>();
  }

  public void setReminderArrayList(ArrayList reminderArrayList) {
    this.mReminderArrayList = reminderArrayList;
  }
    
  public ArrayList getReminderArrayList() {
    return mReminderArrayList;
  }
    
  public void removeLastItemFromList() {
    if (!mReminderArrayList.isEmpty()) {
        mReminderArrayList.remove(mReminderArrayList.size() - 1);
    }
  }
}

模型没有view或者activity视图!只有数据结构、原始数据类型和各种功能,实际上他就是一个普通的旧Java对象(POJO)——根本不是秘密。这个模型被完美的封装,意味着你能够放到任何一个安卓app中并立刻开始运用他。
使用其他基于OOP(面向对象编程)语言创建对象时,常规做法是在这个对象的上下文中创建全局定义的实例变量,在安卓中,将这些实例变量用m前缀也是常规,所以容易分辨什么是非公有,非静态的实例变量,什么不是。一开始会感到奇怪但是这是个入门的好习惯,特别是当安卓源代码指定了这个框架协议,你需要这样做才能直接为安卓源码做贡献。
成员变量的取值赋值方法(getter&setter)也是安卓开发的基本内容,这些简短的方法在需要的时候可以给你的app其余部分提供一个方式去改变成员变量,在获取和设置成员变量时也允许你提供额外的行为。

Access Modifiers(访问修饰符)

帮助setter和getter工作的最后一个难题就是访问修饰符,看一下上面的模型中的以下代码段:

private ArrayList mReminderArrayList;

public void setReminderArrayList(ArrayList reminderArrayList) {
  this.mReminderArrayList = reminderArrayList;
}

是否注意到private和public关键词?他们就是访问修饰符,负责指定其他类可以访问模型中的哪些元素,这有助于封装你的对象。

  • Public——所有其他对象都可以访问,这些公有的方法和变量组成了类的API。
  • Private——只有本对象可以访问,甚至子类也不行。
  • Protected——本对象和子类可以访问,其他类不行。
    成员变量应该总是设置为private或protected,从外部访问他们只能通过公开的getter和setter方法,就像上面代码段中演示的一样,这可以避免难以跟踪的影响和代码的紧耦合。
    如果你建立一个上面模型的子类,想要访问mReminderArrayList,你应该把他的修饰符改为protected,这允许你在子类中访问这个变量,同时也允许你更进一步的定制或者使用这个变量。

Fragments(片段)

在解决下一个安卓Java的主题之前,你需要更多的了解一些关于安卓app的UI是如何构建的。activity非常适合管理一个界面的整体内容,但他不能分享,幸运的是有一个很好的把UI分解成更小的组件的方式,叫做fragments。
fragments和activity类似,但有个好处,能够像view一样嵌入activity中,他们有类似activity的onCreate()类似的生命周期方法,能够像activity一样接受输入。
fragment的布局看起来和activity的完全一样,他包含了一些view的声明,通过将view声明为变量并且根据你在布局中提供的标识符查找view,你可以使用同样的方法把他们连接到代码。
以下代码从res/layout/fragment_embedded.xml的layout文件中创建了一个fragment:

public class EmbeddedFragment extends Fragment {
  @Override
  public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
    // Inflate the layout for this fragment
    return inflater.inflate(R.layout.fragment_embedded, container, false);
  }
}

首先扩展你的类继承fragment的行为,然后使用其生命周期方法之一,即onCreateView()来设置fragment,然后返回一个layout来显示这个特定的fragment。
当你的activity和嵌入的fragment单独工作时这一切都是很简单的,但要是你需要让他们进行通信呢?这里有个activity内尝试与fragment通信的示例方法:

private void updateFragment() {
  EmbeddedFragment fragment = (EmbeddedFragment) getFragmentManager().findFragmentById(R.id.fragment_embedded);
  fragment.setTextViewText("Hello Little Fragment");
}

因为这个fragment在activity内部,你要用findFragmentById和一个在XML定义的标识符来访问,然后你就能轻松调用fragment的公共方法,如上setTextViewText()所示。
相反地,fragment能通过调用getActivity()方法来访问相关的activity,许多简单情况下能行得通,但是在错综复杂的情形中讨论起来更加有趣。

Interfaces

如果你的activity是三个活跃并做各自工作的fragment的所在,但是需要通知其他的fragment在特定时间做一些事怎么办?
理论上,fragment应该只关心自己要做的事而不需要知道自己在哪里。从某种意义上来说,activity就是主导,他是唯一有权访问所有的fragment并知道他们做什么的。
这个Java功能称为Interface(接口)。
接口和类相似,但没有实现,相反,他定义了一个公开的API。类能够实现这些接口而且你的代码不再依赖具体类的实现,而只需要知道“这是一个未知的对象,我可以在其上调用这些接口方法”。
接口允许你的对象直接与其他对象工作而不用暴露内部的细节。想一想那些构建非常复杂但是使用十分简单的东西:汽车、电视甚至是你用来阅读本教程的设备。
你可能不知道这些东西的内部运作方式,但你肯定知道如何操作,接口做的也是一样的事。
在安卓中,接口可以让fragment和activity、fragment和fragment之间方便沟通,其原理如下:

public class EmbeddedFragment extends Fragment {
  // 1
  OnItemInListSelectedListener mCallback;

  // 2
  public interface OnItemInListSelectedListener {
    public void onItemInListSelected(int position);
  }

  @Override
  public void onAttach(Activity activity) {
    super.onAttach(activity);
    // 3      
    try {
      mCallback = (OnItemInListSelectedListener) activity;
    } 
    catch (ClassCastException e) {
      throw new ClassCastException(activity.toString()  + " must implement OnItemInListSelectedListener");
    }
  }

  @Override
  public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
    return inflater.inflate(R.layout.fragment_embedded, container, false);
  }
}

仔细看看这个:

  1. 在fragment中,你声明了一个成员变量来保存实现了OnItemInListSelectedListener 的自定义对象,并且命名为mCallback。
  2. 接下来,你创建接口声明所需方法——你的接口可以有很多方法。
  3. 在fragment的生命周期方法onAttach()中,检查fragment连接的activity是否符合OnItemInListSelectedListener,如果不符合,那么他就不能实现你的目的,会遇到问题,ClassCastException 会在运行时描述这一点,最好给自己或其他程序员发出信号这样你就能早些发现问题。
    接下来就是让你的activity使用接口:
public class MainMenuActivity extends Activity implements EmbeddedFragment.OnItemInListSelectedListener {

  ...

  public void onItemInListSelected(int position) {
      // Received a message from the Fragment, you'll do something neat here
  }
}

类中定义的implements关键词表明这个类将实现特定的接口,你需要为所有在接口中指定的方法提供实现。在这个自定义接口中只有一个方法,你可以在上面的代码段中看到有个实现框架。
这是最难的部分,你仍然没有让fragment与fragment之间通信。假设你已经定义了第二个fragment叫ListDetailFragment,标识符是fragment_list_detail而且有个方法showListItemDetails(int),要让他们进行通信,简单的跟之前一样操作用fragment的其中一个公有方法就可以建立activity和fragment的通信:

public void onItemInListSelected(int position) {
  ListDetailFragment listDetailFragment = (ListDetailFragment) getFragmentManager.findFragmentById(R.id.fragment_list_detail);
  listDetailFragment.showListItemDetails(position);   
}

就像那样,你已经建立了一个fragment和activity之间通信的方法。对于建立通信但同时又保持组件之间的分离,接口是非常有用的。了解组件如何处理fragment和activity是保持你的app架构灵活性的重要部分。

Annotations

你有没有注意到在本文中的一些方法名上面特殊的代码行?

@Override
protected void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
}

那些前缀带有一个@的就是注解,他们在app的各个阶段提供额外的信息,在运行时给编译器提供额外信息甚至生成额外的代码。
在安卓中最常见的是@Override,告诉编译器某个父类的特定方法应该覆盖。如果你的方法实际上没有覆盖任何东西,那编译器就会编译失败并且从你可能遇到的任何奇怪的东西上面告诉你要提供安全性的东西。
另一个有名的注解是@TargetApi,他允许你的方法表明他们适用于某个特定或者更新版本的安卓。使用@TargetApi设置为比当前app版本更高的方法,编译器会提示你此功能当前安卓版本不可用。
这是提示,也是警告,尽管如此你依然可以允许app,但是很可能导致崩溃。
当你开发一个安卓app时,你几乎就需要靠这两个注解,还有其他的,你可以从Android API文档学习有关知识。你还可以创建自己的注解来自动执行特定任务,生成代码和其他有用的功能。
第三方库AndroidAnnotations是个很好的例子,他提供了各种把多行函数变成单行的自定义注解和其他有用功能,可以看看有哪些可用的注解,他们能给你的app提供什么。

Where To Go From Here?

image

视频教程

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,265评论 6 490
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,078评论 2 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 156,852评论 0 347
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,408评论 1 283
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,445评论 5 384
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,772评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,921评论 3 406
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,688评论 0 266
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,130评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,467评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,617评论 1 340
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,276评论 4 329
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,882评论 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,740评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,967评论 1 265
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,315评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,486评论 2 348

推荐阅读更多精彩内容