Android学习笔记

以下是Android基础知识笔记。

添加ActionBar

ActionBar的覆盖叠加

问题:当本人添加

1
2
<item name="android:windowActionBarOverlay">true</item>
<item name="windowActionBarOverlay">true</item>

传入的intent数据不显示。

兼容不同的设备

  • 使用字符串替代资源实现支持多国语言
    在res/中创建一个额外的values目录,以连字符和ISO国家代码结尾命名。

  • 使用字符资源
    在xml文件中,通过元素的name属性来引用字符串资源。

    在源代码中可以通过R.string.语法来引用字符串资源。

  • 适配不同的屏幕

  • 创建不同的layout

  • 创建不同的bitmap

  • 适配不同的系统版本
    确保新版本的app支持90%设备的使用。

    指定最小的目标API级别

  • 在运行时检查系统版本

管理Activity的生命周期

指定你的程序首次启动的Activity

  • 系统会调用你的app里面的被声明为“launcher”(or”main“)activity中的onCreate()方法。

  • 如果你的程序没有一个activity声明了MAIN action或者LAUNCHERcategory,那么在设备的主界面列表不会呈现你的app图标。

创建一个新的实例

问题:示例代码有问题。

  • 无论如何,系统会调用新的activity实例中的onCreate()方法。
  • onCreate里面尽量少做事情,避免程序启动太久都看不到界面。
  • onStart之后开始被用户可见,但是onResume()会迅速执行,使activity停留在Resume()状态。

销毁Activity

  • onDestroy()作为activity要从系统中完全移除的信号。
  • 在onCreate创建了后台线程,以及可能导致内存泄露的资源等,你就应该在onDestroy()时杀死他们。
  • 如果activity只是做简单的逻辑跳转功能,它用来决定跳转到哪的一个activity,那么,在onCreate调用finish方法,会让系统直接跳过其他方法执行onDestroy方法。

暂停与恢复Activity

  • 被其它组件阻塞的情况下,只要activity部分可见,之前的activity一直处在Paused状态。
  • 如果这个activity完全阻塞不可见,它会进入Stop状态。
  • 你应该实现onResume()来初始化那些你在onPause方法中释放掉的组件。

停止与重启Activity初探

  • 出现的一些关键场景:
    • 使用app时,接到一个电话。
    • 启动新的Activity时,当前的Activity会stop,点击back后,第一个会被重启。
    • app在最近的app菜单,通过手机界面图标启动或者最近的程序中启动。
  • 无论什么原因导致Activity停止,系统总是会在onStop()之前调用onPause方法的。
  • 调用onRestart后会迅速调用onStart和onResume方法。

停止你的activity

  • 避免内存泄露,为什么会内存泄露?因为极端情况下,在不需要activity时会摧毁,摧毁的时候不执行onDestroy方法。所以你需要在onStop()方法中释放资源。

启动与重启你的activity

  • onRestart()方法只是在activity从stopped状态恢复时才会被调用。
  • 因为回到activity之前需要过一段时间,所以onStart()方法是一个比较好的来验证某些系统特性是否可用方法。

重新创建Activity

  • 正常destory的场景:
    • 通过返回按钮或者通过调用finish()。
    • 处于stop状态长期不用。
    • 前台需要更多的系统资源所以关闭后台进程。
  • 如果系统不是主动结束的时候(姑且认为是主动,比如按回按钮,调用finish()等),系统在销毁之前会保存记录数据(这些数据可能是描述状态用的),这是一个存放在Bunlde对象中的键值对。
  • 重建的时候,可能需要更多的之前状态的信息,首先,为了恢复View的状态,需要为每个View指定一个ID。
  • 这里有一个很”神秘”的回调函数,需要重写。这个方法叫做:onSaveinstanceState(),只要系统离开activity的时候,就会调用这个函数,把信息传递给Bundle对象,当重建的时候,就会调用onRestoreState()方法。
  • 执行onSaveInstanceState()的场景:
    • 跳转到其他activity
    • 点击Home
    • back不会出现这种情况,因为这属于退栈,不会执行该函数,因为通常认为,不会出现这种操作。
  • 恢复数据可以在onCreate()和onRestoreInstanceState()实现,前者总是在后者前执行。

##使用Fragment建立动态UI

Fragment像一个嵌套的activity,拥有自己的布局以及管理自己的生命周期。在activity中可以与其他的fragments生成不同的组合。

创建一个Fragment

  • 拥有生命周期,接受自己的输入事件,运行过程中添加移除。
  • API版本大于11可以不使用Support Library,也可以使用v7 appcompat library.
  • 和activity的区别之一:你必须重写onCreateView()回调方法,这是唯一一个需要重写的回调方法。
  • 用XML将fragment添加到activity.可以在activity的XML布局文件中定义。
  • API11版本以下的系统需要activity继承FragmentActivity,而如果版本大于等于11,你可以使用普通的Activity.
  • 如果使用的是v7 appcompat library,需要继承ActionBarActivity,后者是FragmentActivity的一个子类。
  • 如果是使用XML布局文件的方式添加进activity,是不可以动态移除的。如果是要交互的时候把fragment切入与切出,必须在activity启动后添加。
  • 当屏幕是large时,按目录中的large字符来区分。

建立灵活动态的UI

  • 支持范围广泛的屏幕尺寸时,可以重用fragment.
  • FragmentManager可以动态的添加移除fragment.
  • 如果你打算在activity的生命周期中替换fragment,可以在activity运行时动态添加fragment.
  • 使用FragmentManager创建FragmentTransaction对象。
  • fragment的layout在view中。
  • 添加fragment的具体步骤:
    • 程序中首先确定activity的布局使用了FragmeLayout.
    • saveInstanceState != null判断是否存了先前的状态。
    • 在activity的onCreate方法初始化fragment实例。
    • 将activity的Intent参数传给生成的Fragment对象。
    • 将Fragment对象添加到布局文件中。
  • fragment替换的具体步骤:
    • 将add()替换成replace()
    • 为了使客户能向后导航和撤销,可以用FragmentTRansaction提交前调用addTbackStack()方法,该方法可以传参给这次更改的事务命名(String类型)。
  • fragments之间的交互:
    • fragment之间不应该直接交互,应该通过关联的activity.
    • 在fragment中定义一个接口,用onAttach()来捕获activity的接口实现。
    • 在fragment中有各种触发事件的类,在这些触发事件的类中调用前面的接口实现。
  • 消息传递给Fragment
    • 使用findFragmentById()获取Fragment的实例。
    • 直接调用Fragment的public方法向Fragment传递消息。(比如一个activity中的另一个Fragment).

数据保存

通过activity那一部分可以了解到,onPause()是可以保存一些信息的。但是Android也有一些专门保存数据的方法。

保存到Preference

  • SharedPreferences可以用来保存键值对量级的需求,他可以是私有的,也可以是共享的状态。
  • 不要与preference混淆,后者帮助设置用户配置的页面。
  • getPreference()方法如果省略了第一个参数(activity的shared preference文件),这个方法会默认检索该activity下的该文件。
  • 如果设置了Context.MODE_PRIVATE,表示这个文件只能这个app访问了。如果输设置为MODE_WORLD_READABLE或者MODE_WORLD_WRITEABLE模式,那么任何app知道文件名就能访问。
  • 写文件,可以通过edit()创建SharedPreferences.Editor来实现。
  • 提交使用apply()方法来替代commit(),可能阻塞UI线程,但是如果不出于线程同步的要求可以使用。
  • 读文件,用getInt()和getString()方法来读取。在读取时,存在一个默认的value作为查找的key不存在时函数的返回值,default.

保存到文件

应用场景:网络中交换的数据,图片文件。

  • 两个存储区域:
    • Internal storage:1.总是可用;2.只能被app访问;3.卸载app后相关文件都清楚干净;4.如果你希望不被其它用户以及其它app访问数据,应该保存在这里。
    • External storage:1.并非总是可用,比如通过USB挂在的时候;2.都可以访问的区域,所以你不能拥有绝对的控制权;3.卸载app时,仅仅会删除external根目录下的相关文件(getExternalFilesDir()).
  • app默认安装在internal storage,但是可以在manifest中声明android:install.ocation属性来安装到external storage中。
  • 获得写External存储权限:在manifest文件中请求权限:<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
  • 尽量声明读权限,尽管在现有版本并非必要。
  • getFileDir()代表app的internal目录,getCacheDir()返回缓存目录,并提供了一些方法对文件进行存储。
  • 保存文件到External Storage,因为可能挂在,所以应该先查询状态,用getExternalStorageState()方法来实现。返回的是MEDIA_MOUNTED的话,表示可以读写。
  • external storage目录对用户和其它app来说有两种:
    • Public files:卸载后应该保留。特定方法getExternalStoragePublicDirectory()方法实现,有两个可指定的参数分别是DIRECTORY_MUSIC以及DIRECTORY_PICTURES.
    • Private files:卸载后应该随之删除。
  • 应该手动删除所有通过getCacheDir()方式创建的缓存文件。

保存到数据库

相较于前两种存储方法,数据库方便存储结构化的数据。大多数都有APIs封装好了。

  • 由于保存的区域默认是私有的,所以android会保存db到你的程序private的空间上。
  • 通过实现BaseColumns接口,可帮助您的数据库与 Android 框架协调工作。
  • 要使用SQLiteOpenHelper,需要创建一个替代onCreate()、onUpgrade()和 onOpen()回调方法的子类。也可以通过版本号结合onDowngrade()来更新数据库。
  • 通过将一个ContentValues对象传递至insert()将数据插入数据库。
  • 要从数据库中读取信息,需使用query()方法,查询使用Cursor对象来返回。

与其它应用交互

Intent的发送

  • 一个Intent可以显示的指明需要启动的模块,也可以隐式的指明处理那种类型的操作。
  • 带有action的intent能使当前app跳转到其他app。有的时候你并不知道对方组件的名字,这个时候可以使用隐式的intent.
  • 例如:Intent callIntent = new Intent(Intent.ACTION_DIAL, number)样式。
  • 如果没有用Uri,则可以使用setType()方法来指定intent附带的数据类型。
  • 在API Level 14以上可以使用intent for Calendar.
  • 设􏰠MIME type 􏱊为􏱒􏲱􏱛哪􏱰activity􏰔􏰙应该􏳝􏳞这􏰩intent.
  • 应该处罚intent之前验证是否有App接受这个intent,如果没有app接收,那么app会crash.
  • 要确认可以安全的使用这个intent,需要使用queryIntentActivities()获得一个能接收的list,然后通过数组的大小来查看是不是为空。
  • 通过前面的步骤后,最后一步,启动app,运行startActivity(intent).

Intent过滤

你的app如果需要在别人分享时出现在待选对话框,那么你的app应该能接收ACTION_SEND的Intent.

  • 必须定义好action和data,如果activity中的intent filter满足以下对象的标准,那么就可以:
    • Action,在intent filt用指定它的值,值为字符串。这些值都是已经定义好了的。
    • Data,可以是多个属性。
    • Category,一般不怎么用得到,都有一个默认值,这主要是和用户的手势以及启动位置有关。
    • 如果出现了能响应ACTION_SEND和ACTION_SENDTO两种“name”,那么你必须用两个filter来分开表示。
  • 获取action,并且读取intent的内容。方法是getIntent().
  • 可以在任何生命周期使用getIntent(),但是最好是在onCreat()和onStart()执行。
  • 返回Result,使用setResult(),并且总是指定result code.通常是RESULT_OK或者RESULT_CANCELED.

探究碎片

动态添加碎片

实现一个例子,用两个fragment对activity进行分屏,左边的fragment上面有一个按钮,通过按下这个按钮,将右边的fragment进行替换。

事实上,这个按钮还是注册在activity上面的,按照书中所说,动态加载fragment需要5步:

  • 创建碎片实例;
  • 获得fragmentManager对象;
  • 用这个对象开启一个事务;
  • 通过这个事务的replace()方法替换实例;
  • 提交事务;

通过这个实验,产生了疑问,fragment到底是替换还是覆盖??因为替换为第三个fragment后,出现了一个现象,那就是前一个fragment的一部分图片仍然显示,背景颜色也是原有颜色。后来将新的fragment的颜色改为了黄色,这个时候才没有出现前一个fragment.如图所示:


图片01.

碎片中模拟返回栈

在前面的例子中,如果按下back键程序会直接退出。添加生成的事务的方法addTobackStack(null);

但是我在我的模拟器上试验没有成功。

碎片的生命周期

  • 运行状态
    碎片可见,相关联的活动处于运行状态,则该碎片处于运行状态。
  • 暂停状态
    当一个活动进入暂停状态,他所关联的所见碎片都会进入暂停状态。
  • 停止状态
    当一个活动进入停止状态时,关联的碎片都进入到了停止状态,或者通过remove()和replace()方法将碎片移除,然后在事务提交之前调用addTobackStack()方法,也会进入停止状态。
  • 销毁状态
    如果关联的activity销毁了,name这些碎片也销毁。如果在事务提交之前没有调用addToBackStack(),也会进入到销毁状态。

详解广播机制

  • 标准广播
  • 有序广播

接受广播

  • 在代码中注册,动态注册
  • 在AndroidManifest.xml中注册,静态注册
  • 动态注册的广播接收器都要取消注册
  • 访问一些关键信息需要在配置文件中声明才可以
  • 静态注册的缺点是程序启动了才能收到广播,这样不能进行一些接受操作,比如要实现开机自启动

内容提供器

内容提供器主要用于在不同的应用程序之间实现数据共享的功能,允许一个程序访问另一个程序中的数据,同时能保证数据的安全性。

  • 一种是现有的内容提供器
  • 一种是自己创建的内容提供器
  • 具体操作有一套现有的APIs,充分利用了ContentResolve类

探究服务

适合于去执行不需要和用户交互而且要长期运行的任务。
服务不是运行在一个独立的进程当中的,而是依赖于创建服务时所在的应用程序进程,所以程序进程被杀掉了,那么该进程的服务也会停止。

多线程

  • 用Runnable()方法启动线程的方式是:new Thread(yourThread).start()
  • Android不允许在子线程对UI进行操作
  • 解析异步消息处理机制的四个方法
    • Message:可以携带少量消息,有what字段,arg1,arg2还有obj…
    • Handler:处理和发送消息
    • MessageQueue:消息队列,每个线程只会有一个MessageQueue对象
    • Looper:进行无限循环,去发现并取出MessageQueue中的消息
  • AsyncTask:

服务基本用法

  • onCreate():在服务创建时调用
  • onStartCommand():在每次服务启动时调用,一旦启动就立即执行操作的话,可以使用这个
  • onDestroy():在服务销毁的时候调用
  • 服务需要在AndroidManifest.xml中注册才能生效,四大组件都需要这样
  • 启动服务
    • 启动服务很简单,只要配合好Intent就行了,类似启动另一个Activity,但是这里使用startService()
      停止服务:
    • 同样的,如果停止服务,使用stopService()。服务也可以自己停下来,只要在自身任何位置调用stopSelf()方法就可以停止这个服务。
  • onCreate和onStartCommand的区别??我觉得类似Activity中的onCreate和onStart.如果没有创讲过服务,那么两者都要执行。而服务创建好后,多次点击启动的那个button,只执行后者了
  • 服务是在活动里启动的,但是启动了服务后,活动与服务基本就没什么关系了。当我把Activity destroy以后,具体看图(服务1):
    服务00
    以及图(服务2):
    服务01
    从图(服务00)和图(服务01)可以看出,后台中还有一个服务在跑,中间其实经过了一次小小的重新创建,process曾经变为了0,为了证实这一点,可以看图(服务3)和图(服务03)的结果,图(服务3)如下:
    服务02
    图(服务4):
    服务03

你会发现,当我关掉Activity后,onCreate()又执行了一次。

网络技术

WebView的用法

首先研究基本的语法,在layout中加入一个WebView的控件,这个很容易,其次是在设置文件中添加一个permission权限,来访问网络功能,接下来主要分析一下activity中的代码:

1
2
3
4
5
6
7
8
9
10
webView = (WebView)findViewById(R.id.web_view);
webView.getSettings().setJavaScriptEnabled(true);
webView.setWebViewClient(new WebViewClient(){
@Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
view.loadUrl(url);
return true;
}
});
webView.loadUrl("http://www.baidu.com");

这这几行代码中,setJavaScriptEnabled()使WebView支持JavaScript脚本。

webView.loadUrl(“http://www.baidu.com");使得立即加载百度首页页面。

这里使用WebViewClient的作用是希望目标页面能在WebView直接显示,而不需要打开其它的浏览器,具体的做法是把方法的返回值设为true.当然,前面会运用view.loadUrl(url)来让函数传入参数后再加载新的网页。

使用HttpURLConnection

这里举一个例子,用一个Button控件来发送请求,然后通过HTTP协议接收目标网站响应的内容,并呈现在界面上。这里主要关注sendRequestWithHttpURLConnection()方法:

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
private void sendRequestWithHttpURLConnection(){
new Thread(new Runnable() {
@Override
public void run() {
HttpURLConnection connection = null;
try {
URL url = new URL("http://www.baidu.com");
connection = (HttpURLConnection) url.openConnection();
 connection.setRequestMethod("GET");
connection.setConnectTimeout(8000);
connection.setReadTimeout(8000);
InputStream in = connection.getInputStream();
//下面对获取到的输入流进行读取
BufferedReader reader = new BufferedReader(new InputStreamReader(in));
StringBuilder response = new StringBuilder();
String line;
while ((line = reader.readLine()) != null) {
response.append(line);
}
Message message = new Message();
message.what = SHOW_RESPONSE;
//将服务器返回的结果存放到Message中
message.obj = response.toString();
handler.sendMessage(message);
} catch (Exception e) {
e.printStackTrace();
} finally {
if (connection != null) {
connection.disconnect();
}
}
}
}).start();
}

现在来分析一下这段代码:

  • openConnection():用来获得connection实例
  • setRequestMethod():设置请求方法,通常有post和get
  • setConnectTimeout()和setReadTimeout():分别设置链接超时和读取超时
  • getInputStream():获取服务器返回的输入流
    后面就是对输入流进行读取了,然后就是把返回的信息保存在Message实例中

使用HttpClient

Android中另一种发送请求的方式是采用HttpClient.但是这种方法主要是在Android api9以下采用,Apache公司维护它的热情也不高。

解析XML格式数据

网络传输时最常用的格式有两种,分别是XML和JSON.

  • Pull解析方式
    在书上,作者采用了Apache服务器,这里我们简单的实用Django自带的服务器进行测试。

在Django工程目录下新建一个static文件夹,然后在该文件夹下新建一个get_data.xml,填写XML格式的内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<apps>
<app>
<id>1</id>
<name>Google Maps</name>
<version>1.0</version>
</app>
<app>
<id>2</id>
<name>Chrome</name>
<version>2.1</version>
</app>
<app>
<id>3</id>
<name>Google Play</name>
<version>2.3</version>
</app>
</apps>

浏览器显示如图所示(Safari呈现):
xml解析

这里我们继续使用HttpURLConnection的那个项目开发,重点关注XML的解析。

在genymotion中指向localhost的地址不是10.0.2.2,而是10.0.3.2…

在原有的代码中,我们获得了一个叫response的String对象,然后将该对象传入一个叫做parseXMLWithPull()的方法中,用该方法来解析XML文件。

我们具体来看parseXMLWithPull()方法是如何来解析的:

  1. XmlPullParserFactory:这个类用于创建XmlPull解析器
  2. XmlPullParser:这是一个定义解析功能的接口,通过多态,和1中的类一起产生一个XmlPullParser的对象
  3. setInput()方法:将获得的Xml数据设置到2中产生的对象,以进行解析。
  4. getEventType():获得解析事件,一些基本的事件如下:
    • START_DOCUMENT:判断事件为文档开始事件
    • END_DOCUMENT:判断事件为文档结束事件
    • START_TAG:标签文件开始事件
    • END_TAG:标签文件结束事件
    • TEXT:文本事件

解析JSON格式数据

JSON的优点是体积小,省流量,但是缺点是语义性差。

这里简单说说使用GSON来解析数据。

首先安装GSON插件,如图所示:
GSON01

  • 如果是单个GSON数据,可以有如下示例:

    1
    2
    Gson gson = new Gson();
    Person person = gson.fromJson(jsonData, Person.class);
  • 如果是一个GSON数组,可以有如下示例:

    1
    List<Person> people = gson.fromJson(jsonData, new TypeToken<List<Person>>() {}.getType());

这样就生成了对象的一个List了。

参考

pull的详细原理:http://www.android100.org/html/201406/07/20575.html
GSON简单示例:http://blog.csdn.net/dakaring/article/details/46300963