第一行代码Android笔记精华版

--第一章 开始启程,你的第一行Android代码

@Android系统分为四层架构,五块区域:Linux内核层、系统运行库层(包含Android运行时库,Dalvik虚拟机)、应用框架层和应用层;

@Android系统四大组件:活动Activity、服务Service、广播接收器BroadcastReceiver和内容提供器ContentProvider;

@搭建Android开发环境除需要JavaJDK外,还可能用到AndroidSDK、Eclipse和AndroidDevelopmentTools;

@创建AndroidApplicationProject时,MinimumRequiredSDK是指程序最低兼容版本;TargetSDK是指在该目标版本上做了充分测试,系统不再做向前兼容的操作;CompileWith是指程序将使用哪个版本的SDK进行编译;Theme一般选择None;

--第二章 先从看得到的入手,探究活动

@在XML中引用一个id,使用@id/id_name,在XML中定义一个id,使用@+id/id_name这种语法

@要注意在使用R文件时,有一个R文件时当前程序本身的,还有一个是AndroidSDK自动提供一个android包下的R文件,两者完全不同;

@当给主活动指定label不仅会成为标题栏的内容,还会成为启动器中应用程序显示的名称;

@隐藏标题栏,在onCreate()方法中的setContentView()之前添加requestWindowFeature(Window.FEATURE_NO_TITLE);代码;

@手动创建活动的步骤:

  1. 在src中创建包并新建活动类继承Activity,重写onCreate()方法;
  2. 在res的layout文件夹下创建布局XML文件,在布局文件中添加控件,在onCreate()方法中使用setContentView()方法加载布局文件;
  3. 在Manifest文件的<application/>元素中注册活动;
  4. 在res的menu文件夹下创建菜单XML文件,并添加菜单项,在Activity类中重写onCreateOptionsMenu()方法添加菜单,重写onOptionsItemSelected()方法为菜单项设置动作;
  5. 在任意方法中调用finish()方法可以销毁当前活动;

@Intent是Android程序中各组件之间进行交互的一种重要方式,它不仅可以指明当前组件想要执行的动作,还可以在不同组件之间传递数据。Intent一般可被用于启动活动、启动服务、以及发送广播等场景;

@Intent的用法大致可以分为两种,显式Intent和隐式Intent:

  1. 显示Intent使用startActivity()方法并传入Intent实例对象来实现;
  2. 隐式Intent,指定Intent实例的action和category,每个Intent中只能指定一个action,但能指定多个category,另外Intent还能使用setData()方法指定一个Uri对象,用于指定操作的具体数据,对应的在intent-filter标签内也可以指定<data/>标签,其下可以配置android:scheme协议,如http,tel、host主机、port端口、path路径、mimeType数据类型等信息;

@在向上一个活动返回数据时,其原理是使用startActivityForResult()方法来启动SecondActivity的,在SecondActivity被销毁之后会回调上一个活动的onActivityResult()方法,因此我们需要在FirstActivity中重写这个方法来得到返回的数据;一般销毁逻辑写在按钮点击的事件中,或者写在点击返回键时重写的onBackPressed()方法中;

@Android使用任务Task来管理活动,一个任务就是一组栈里的活动的集合;

@活动在其生命周期中有4种状态:

  1. 运行状态:位于返回栈的栈顶的活动;
  2. 暂定状态:不在栈顶但仍可见的活动;
  3. 停止状态:不在栈顶且不可见的活动;
  4. 销毁状态:从栈移除之后的活动;

@活动有3个生存期:

  1. 完整生存期:在onCreate()方法和onDestroy()方法之间所经历的;
  2. 可见生存期:在onStart()方法和onStop()方法之间所经历的;
  3. 前台生存期:在onResume()方法和onPause()方法之间所经历的;

@活动生命周期示意图:

第一行代码Android笔记精华版

@Android停止状态的活动如果被系统回收了,当返回该活动时还是会显示该活动,但其不再是通过执行onRestart()方法,而是执行onCreate()方法,活动被重新创建一次,但是被回收活动中的临时数据也一并丢失了,Activity提供了一个onSaveInstanceState()方法,它在系统回收之前被调用,并携带一个Bundle类型的参数,它可以使用一系列重载方法来保存数据,然后从onCreate()方法中通过判断Bundle参数是否为null来取出之前保存的数据;

@Bundle可以用来保存数据,而Intent可以传递Bundle;

@Activity的启动模式有四种,可以通过在Manifest文件中给<activity/>标签中指定android:launchMode属性来指定:

  1. standard,默认启动模式,每启动一个活动都会创建该活动的新实例在栈顶;
  2. singleTop,如果当前活动已经位于栈顶,则不创建,否则仍然会创建新实例;
  3. singleTask,会检查返回栈中是否存在其实例,如果有则将其上的所有活动出栈并使用,如果没有则重新创建新实例;
  4. singleInstance,会启用新的返回栈来管理这个活动(在singleTask模式下指定不同的taskAffinity也会启用新的返回栈),其意义是实现不同程序之间共享活动的实例;

--第三章 软件也要拼脸蛋,UI开发的点点滴滴

@Android常用控件有TextView、Button、EditText、ImageView、ProgressBar、AlertDialog、ProgressDialog等;注意其中Dialog有一个setCancelable(flase)方法表示对话框不能通过返回键取消掉;

其他控件还有:

ScrollView:用于可滚动查看大量内容

@Android最基本的4种布局:

  1. LinearLayout线性布局:使用android:orientation属性指定vertical垂直或horizontal水平方向,如果是垂直方向,内部控件宽度就不能指定为match_parent,如果是水平方向,内部控件高度就不能指定为match_parent;其下控件可以使用android:layout_gravity属性指定某个方向上的对齐方式,但是如果布局排列方向为垂直,则只有水平方向的对齐方式才能生效,布局排列方向为水平,则只有垂直方向的对齐方式才能生效;另外其下控件还可以使用android:layout_weight来指定某个方向上的宽度计算比例,在指定了该属性后,layout_width可以指定为0;
  2. RelativeLayout相对布局:其下控件可以指定相对父布局进行定位如android:layout_alignParent*系列和layout_centerInParent,还可以相对其他控件定位如android:layout_above/below和layout_to*Of系列,还可以相对其他控件边缘对齐定位如android:layout_align*系列;
  3. FrameLayout框架布局:所有控件在左上角布局,应用场景不多;
  4. TableLayout表格布局:使用<TableRow/>表示行,行不能指定控件宽度但可以指定android:stretchColumns属性来对某列进行拉伸(0代表拉伸第一列),其下一个控件代表一列,列可以指定android:layout_span来合并单元格;

@AndroidUI元素相关属性整理如下:

  1. android:id 用于给控件定义唯一标识符
  2. android:layout_width/height 用于指定控件的宽度高度,其值包括:match_parent/fill_parent/warp_content
  3. android:text 指定文本内容
  4. android:textSize 指定文本大小
  5. android:textColor 指定文本颜色
  6. android:gravity 控件内文本的对齐方式,其值包括:top/bottom/left/right/center
  7. android:hint 指定编辑框提示性文本
  8. android:ellipsize 设定当文本内容超出控件宽度时的缩略方式,其值包括:end
  9. android:inputType 指定编辑框的输入类型,其值包括:textPassword
  10. android:maxLines 指定编辑框最大行数
  11. android:singleLine 指定只能单行显示,其值包括:true
  12. android:src 指定资源路径,用于图片等
  13. android:visibility 指定控件可见属性,其值包括:visible/invisible/gone
  14. android:scaleType 指定图片的填充类型,其值包括:fitXY
  15. android:max 给控件指定最大值,用于进度条等
  16. android:orientation 指定布局排列方向,其值包括:vertical/horizontal
  17. android:layout_gravity 控件在布局中的对齐方式,其值包括:top/bottom/left/right/center/center_vertical/center_horizontal
  18. android:layout_weight 以比例方式指定控件的大小
  19. android:layout_alignParent* 一系列用于相对父布局的对齐属性,其值一般为true
  20. android:layout_centerInParent 控件相对父布局居中
  21. android:layout_above/below 指定控件位于某控件上/下方,其值为另一控件的id
  22. android:layout_to*Of 一系列指定控件位于某控件的*方向,其值为另一控件的id
  23. android:layout_align* 一系列指定控件相对于另一控件的*边缘对齐
  24. android:layout_span 表格布局时指定控件合并单元格数
  25. android:stretchColumns 表格布局时指定某列自动拉伸适应
  26. android:layout_margin 指定控件外部在四个方向的偏移
  27. android:layout_margin* 指定控件外部在*方向的偏移
  28. android:layout_padding 指定控件内部在四个方向的补白
  29. android:layout_padding* 指定控件内部在*方向的补白
  30. android:background 指定布局或控件的背景,其值包括:#***/@drawable/pic_name

@Android复用布局时,可以使用<include layout="@layout/layout_name" />标签,如果希望复用布局中的按钮共用代码则需要重新编写自定义控件,需要以下几步:

第1步:新建*Layout类继承LinearLayout,覆盖构造函数并重新加载布局,构造函数中还可增加共用代码,示例代码如下:

public class *Layout extends LinearLayout {
	public *Layout(Context context, AttributeSet attrs) {
		super(context, attrs);
		LayoutInflater.from(context).inflate(R.layout.layout_name, this);
	}
}

第2步:在布局文件中引用<packagepath.*Layout />标签;

@ListView注册点击事件,使用setOnItemClickListener(OnItemClickListener())方法;

@Android下单位有如下几点:

  1. 密度无关像素dp/dip,它与像素px(屏幕显示的最小单元)不同,它在不同密度的屏幕上显示将保持比例一致;
  2. 磅数pt,1pt=1/72英寸,一般作为字体的单位使用
  3. 可伸缩像素sp,用于解决文字大小适配问题,这里的密度是指屏幕的dpi,即每英寸所包含的像素数;
  4. Android规定在160dpi的屏幕上,1dp等于1px;

@在AndroidSDK的tools文件夹下使用draw9path.bat来制作Nine-Patch图片,注意绘制后图片会有4条黑边,上左边框的矩形表示图片自动拉伸的部分,下右边框的矩形表示内容放置的区域;

--第四章 手机平板要兼顾,探究碎片

@使用碎片的一般步骤:

  1. 创建多个碎片布局文件,一般为左右fragment两个xml文件;
  2. 新建碎片对应类,继承自android.app.Fragment(4.0版本)并覆盖onCreateView()方法,在该方法中动态加载对应的布局文件;
  3. 修改主布局文件,添加两个<fragment/>标签,指定android:name属性值为上一步创建的碎片类的全限定类名(如com.exp.fragment.MyFragment)即可;

@动态添加碎片的步骤:

  1. 创建碎片布局文件及对应类
  2. 在主布局文件中添加<FragmentLayout/>标签,并在其中添加待替换碎片的<fragment/>标签
  3. 在主活动类中使用如下代码:
    // 获取待加载碎片实例
    *Fragment fragment = new *Fragment();
    // 获取FragmentManager
    FragmentManager fragmentManager = getFragmentManager();
    // 开启事务
    FragmentTransaction transaction = fragmentManager.beginTransaction();
    // 向容器内加入碎片,replace()方法中传入容器id和待添加碎片实例
    transaction.replace(R.id.right_layout, fragment);
    // 将事务添加到返回栈中,这样点击返回键时不会直接退出程序,它传入一个返回栈状态描述,一般为null即可
    transaction.addToBackStack(null);
    // 提交事务
    transaction.commit();

@FragmentTransaction提供了addToBackStack()方法用于将事务添加到返回栈,它接收一个返回栈状态描述的参数,一般指定为null即可

@碎片与活动,碎片与碎片之间互相调用方法:

  1. FragmentManager提供了findFragmentById(R.id.fragment_name)来获取碎片实例,这样活动就可以调用碎片中的方法了
  2. 每个碎片可以调用getActivity()方法来获得当前碎片关联的活动实例,这样碎片就可以调用活动中的方法了;
  3. 在碎片中获得与其关联的活动,该活动又获取另一个碎片的实例,这样碎片可以和另外的碎片通信了;

@碎片生命周期与活动类似,除包含活动的中的6个方法之外,还包含以下回调方法:

  1. onAttach()当碎片和活动建立关联的时候调用;
  2. onCreateView()为碎片创建视图(加载布局)时调用;
  3. onActivityCreated()确保与碎片相关联的活动一定已经创建完毕的时候调用;
  4. onDestroyView()当与碎片关联的视图被移除的时候调用;
  5. onDetach()当碎片和活动解除关联的时候调用;

其完整生命周期示意图如下:

第一行代码Android笔记精华版

@和活动一样,碎片也可以通过onSaveInstanceState()方法来保存数据,保存的数据在onCreate()、onCreateView()、onActivityCreated()这三个方法中都可以重新得到;

@动态加载布局时需要使用Android限定符,具体包括大小(small/normal/large/xlarge)、分辨率(ldpi/mdpi/hdpi/xhdpi)、方向(land/port);

@最小宽度限定符运行对屏幕的宽度指定一个最小值(单位dp),然后大于或小于这个值会加载不同布局,一般在res文件夹中创建sw*dp的文件夹并放置布局文件

--第五章 全局大喇叭,详解广播机制

@Android广播分为标准广播和有序广播两种类型:

  1. 标准广播是一种异步执行的广播,所有广播接收器几乎同时收到消息,无法被截断,效率较高;
  2. 有序广播是一种同步执行的广播,其广播接收器是按优先级顺序接收的,而且可以被截断;

@注册广播的方式有两种,一种在代码中注册即动态注册,一种在Manifest中注册及静态注册,创建广播接收器只需将类继承自BroadcastReceiver,重写父类的onReceive()方法即可;

  1. 动态注册即使用registReceiver(*Receiver,intentFilter)方法,缺点是程序启动之后才能接收到广播;
  2. 静态注册即在menifest文件的<application/>元素中新增<receiver/>标签即可,其下在创建<intent-filter><action android:name=""/></intent-filter>元素;
  3. 要注意使用某些广播接收器时需要声明权限,声明权限在<menifest/>元素中添加<user-permission android:name=""/>标签;

@发送自定义广播的步骤:

  1. 创建*BroadcastReceiver类;
  2. 在Menifest文件中注册,在给<action/>元素指定name属性时定义自己的名称,如果需要定义接收先后,给<intent-filter/>标签的priority属性设置优先级值;
  3. 代码中使用sendBroadcast(intent)方法发送标准广播,使用sendOrderBroadcast(intent,null)发送有序广播,该intent的action为该名称;由于此时使用的是intent,所以还可以附带传递其他数据;
  4. 如果是有序广播在onReceive()方法中可以使用abortBroadcast()方法截断广播;

@本地广播即只在程序内部传递的广播,其使用步骤如下:

  1. 获取本地广播过滤器实例:LocalBroadcastManager localBroadcastManager=LocalBroadcastManager.getInstance(this);
  2. 注册本地广播监听:localBroadcastManager.registerReceiver(localReceiver,intentFilter);
  3. 发送本地广播:localBroadcastManager.sendBroadcast(intent);

注意本地广播无法使用静态注册,使用时安全性可以保证,效率比系统全局广播高效;

--第六章 数据存储全方案,详解持久化技术

@Android系统提供三种简单数据持久化方式:文件存储、SharedPreference存储以及数据库存储;

@文件存储适合于存储简单的文本数据或二进制数据,一般用于缓存部分临时数据,如果需要存储复杂文本数据则需要自定义格式规范;

  1. Context类中提供了一个openFileOutput(filename,Context.MODE_*)方法返回一个FileOutputStream对象,文件将存储到data/data/packagename/files目录下,注意MODE_*参数值有两种,一是MODE_PRIVATE,表示会同名文件会覆盖,另一个是MODE_APPEND,表示同名文件会追加;
  2. Context类提供了一个openFileInput(filename)方法返回一个FileInputStream对象,它会到data/data/package name/files目录下加载指定文件;

@将数据存储到SharedPreferences中,一般用于存储应用程序首选项,获取SharedPreferences对象有三种方法:

  1. Context类的getSharedPreferences(filename,MODE_*),生存的xml文件会存放在data/data/package name/shared_prefs/目录下,操作模式有MODE_PRIVATE表示只有当前程序可以对该文件进行读写,MODE_MULTI_PROCESS用于多个进程对同一个文件进行读写
  2. Activity类的getPreferences(MODE_*)方法,它自动使用活动的类名作为文件名
  3. PreferenceManager类中的getDefaultSharedPreferences()方法,他接受一个Context参数,并自动使用当前程序的包名做前缀来命名文件

得到SharedPreferences对象后,使用如下步骤来存储数据:

  1. 调用SharedPreferences对象的edit()方法获取SharedPreferences.Editor对象
  2. 向SharedPreferences.Editor对象中添加数据,使用put*()系列方法
  3. 调用Editor对象的commit()方法提交数据

获取数据则使用SharedPreferences对象的get*()系列方法;

@使用SQLite数据库存储结构复杂的数据,需要创建自己的SQLiteOpenHelper帮助类,继承并重写onCreate()和onUpgrade()方法,创建的数据库文件将存储在data/data/package name/databases/路径下,另外还需要实现其至少一个构造函数SQLiteOpenHelper(Context,db_name.db,null,veision_no),如果升级数据库只需将版本号参数增加将自动执行onUpgrade()方法;

@SQLiteOpenHelper帮助类实例有getReadableDatabase()和getWritableDatabase()两个方法用于创建和打开一个数据库并返回可读写数据库的SQLiteDatabase对象,当调用该方法时,如果数据库已经创建则不会再次创建;升级数据库一般是在onUpgrade()方法中执行新的SQL语句;

@SQLite的SQL语法稍有不同,integer整型,real浮点型,bolb二进制类型,text文本类型;

查看数据库具体内容也需要使用adb shell工具,将SDK的platform-tools目录添加至系统Path变量,在命令行使用adb shell命令进入设备控制台(如果有多台设备需要同时指定设备序列号的参数),cd到数据库文件的目录后,使用sqlite3后面加上数据库名即可打开数据库了,之后可以使用SQL语句进行操作了,其他常用SQLite命令有.table查看表.schema查看数据库创建语句.exit/.quit退出数据库,exit退出adb控制台返回系统命令行;

@数据库的CRUD操作将用到SQLiteDatabase对象:

  1. 添加数据时使用insert(table_name,null,ContentValues)方法,ContentValues对象提供put(column_name,value)方法用于指定列的数据,使用clear()方法清空数据后可以重新使用;
  2. 更新数据时使用update(table_name,ContentValues,sql_part,sql_parm)方法,其中sql_part对应wher语句后的部分,其中的参数值使用?占位,sql_parm是字符串数组用于指定对应值,例如update("table1",values,"colume=?",new String[]{"abc"}),如果没有指定后两个参数则更新所有行;
  3. 删除数据时使用delete(table_name,ContentValues,sql_part,sql_parm)方法,与更新方法类似;
  4. 查询数据时使用query(table_name,columns,sql_part,sql_parm,sql_groupBy,sql_having,sql_orderBy)方法,其参数最少的重载也有7个参数,除了表名其他参数均可指定为null,该方法返回Cursor对象,使用其moveToFirst()方法打开游标并移动至首行兼可判断是否有行,moveToNext()方法移动至下一行,get*(Cursor.getColumnIndex(column_name))系列方法来取得某列的值,例如getString()/getDouble(),其close()方法关闭游标;
@SQLiteDatabase对象同样提供了基于SQL语句的方法用于执行CRUD操作:
  1. 添加数据execSQL("insert into table_name () values(?...)",new String[]{});
  2. 更新数据execSQL("update table_name set column=? where column=?",new String[]{});
  3. 删除数据execSQL("delete from table_name where column=?",new String[]{});
  4. 查询数据rawQuery("select * from table_name",null);

--第七章 跨程序共享数据,探究内容提供器

@访问内容提供器中的数据需要借助ContentResolver类,它提供了类似于SQLiteDatabase的CRUD操作只是参数稍有区别,一般这些方法不再接收表名而是使用Uri对象作参数,它相当于数据的唯一标识符,包含权限authority和路径path两部分,例如Uri uri=Uri.parse(content://packagename.provider/table),体访问代码例如:Cursor cursor=getContentResolver().query(uri,null...);

@创建自定义的内容提供器需要继承ContentProvider类并实现其6个方法:

  1. onCreate()初始化内容提供器的时候调用;通常会在这里完成对数据库的创建和升级等操作,返回true表示内容提供器初始化成功,返回false则表示失败;注意,只有当存在ContentResolver尝试访问我们程序中的数据时,内容提供器才会被初始化;
  2. query()从内容提供器中查询数据;使用uri参数来确定查询哪张表,projection参数用于确定查询哪些列,selection和selectionArgs参数用于约束查询哪些行,sortOrder参数用于对结果进行排序,查询的结果存放在Cursor对象中返回;
  3. insert()向内容提供器中添加一条数据;使用uri参数来确定要添加到的表,待添加的数据保存在values参数中;添加完成后,返回一个用于表示这条新记录的URI;
  4. update()更新内容提供器中已有的数据;使用uri参数来确定更新哪一张表中的数据,新数据保存在values参数中,selection和selectionArgs参数用于约束更新哪些行,受影响的行数将作为返回值返回;
  5. delete()从内容提供器中删除数据;使用uri参数来确定删除哪一张表中的数据,selection和selectionArgs参数用于约束删除哪些行,被删除的行数将作为返回值返回;
  6. getType()根据传入的内容URI来返回相应的MIME类型;

@一般自定义的ContentProvider中包含一个UriMatcher和DatabaseHelper对象;UriMatcher用于过滤Uri,使用其addURI()方法添加希望匹配的uri格式,使用match()方法对Uri对象进行匹配返回对应添加时的代码;DatabaseHelper用于提供数据;其中getType()方法获取对应MIME类型,Android规定一个内容URI所对应的MIME字符串主要由三部分组分,Android对这三个部分做了如下格式规定:

  1. 必须以vnd开头;
  2. 如果内容URI以路径结尾,则后接android.cursor.dir/,如果内容URI以id结尾,则后接android.cursor.item/;
  3. 最后接上vnd.<authority>.<path>;

--第八章 丰富你的程序,运用手机多媒体

@无论是在活动中,或者在广播接收器或者服务中创建通知,步骤都类似:

  1. 使用Context的getSystemService(Context.NOTIFICATION_SERVICE)方法获取NotificationManager实例;
  2. 创建Notification对象,new Notification(icon_id,ticker,time)第一个参数指定通知栏图标的id,第二个参数指定通知栏提示信息,第三个参数为通知创建的时间;
  3. 为通知对象设定布局,调用其setLatestEventInfo(Context,title,content,PendingIntent)方法,第二三个参数分别指定通知的标题和内容,第四个参数指定点击通知后的跳转目的延时Intent;
  4. 调用NotificationManager对象的notify(id,notification)显示通知,第一个参数指定通知的唯一id,取消状态栏的通知则调用cancel(id)即可;

@获取PendingIntent对象使用其静态方法包括getActivity(),getBroadcast(),getService(Context,0,Intent,flag),它们接收4个参数,第三个参数指定其意图,第四个参数确定其行为,其值有四种:

  1. FLAG_CANCEL_CURRENT:如果当前系统中已经存在一个相同的PendingIntent对象,那么就将先将已有的PendingIntent取消,然后重新生成一个PendingIntent对象;
  2. FLAG_NO_CREATE:如果当前系统中不存在相同的PendingIntent对象,系统将不会创建该PendingIntent对象而是直接返回null;
  3. FLAG_ONE_SHOT:该PendingIntent只作用一次;在该PendingIntent对象通过send()方法触发过后,PendingIntent将自动调用cancel()进行销毁,那么如果你再调用send()方法的话,系统将会返回一个SendIntentException;
  4. FLAG_UPDATE_CURRENT:如果系统中有一个和你描述的PendingIntent对等的PendingInent,那么系统将使用该PendingIntent对象,但是会使用新的Intent来更新之前PendingIntent中的Intent对象数据,例如更新Intent中的Extras;

@发送通知时可以同时指定播放的音频,notification.sound=Uri.fromFile(new File("/system/media/audio/ringtones/Basic_tone.ogg"));还可以指定振动,notification.vibrate={0,1000,1000,1000},长整型数组设置手机静止和振动的时长,以毫秒为单位,下标为0的值表示手机静止的时长,下标为1的值表示手机振动的时长,下标为2的值又表示手机静止的时长,以此类推;注意控制手机振动需要声明android.permisson.VIBRATE权限;另外还能设置发送通知的同时让LED灯闪烁,ledARGB控制灯颜色,有Color.RED/GREEN/BLUE三色可选,ledOnMS指定亮起时长,单位毫秒,ledOffMS指定暗去时长,单位毫秒,设置这三样属性后,使用notification.flags=Notification.FLAG_SHOW_LIGHTS;来设置LED灯;最后如果不希望这么复杂可以使用默认的设置:notification.defaults=Notification.DEFAULT_ALL;

@接收短信时只需接收系统受到短信后发出的android.provider.Telephony.SMS_RECEIVED广播,然后在广播接收器中处理短信内容更新至UI即可,另外需要声明接收短信android.permission.RECEIVE_SMS的权限;

发送短信使用系统的SmsManager对象,SmsMagager.getDefault().sendTextMessage(phone_no,null,sms_text,PendingIntent,null);;另外需要声明发送短信android.permission.SEND_SMS的权限;第四个参数使用了一个PendingIntent,用于发送成功后发出一个广播,然后利用广播接收器就能判断发送状态了;

@调用摄像头拍摄一张照片时只需发送android.media.action.IMAGE_CAPTURE的Action即可,注意添加读写SD卡android.permission.WRITE_EXTERNAL_STORAGE的权限:

ImageView picture = (ImageView) findViewById(id);
File outputImage = new File(Environment.getExternalStorageDirectory(),
						"image_name.jpg");
outputImage.createNewFile();
Uri imageUri = Uri.fromFile(outputImage);
Intent intent = new Intent("android.media.action.IMAGE_CAPTURE");
intent.putExtra(MediaStore.EXTRA_OUTPUT, imageUri);
startActivityForResult(intent, 1);
裁剪照片同样只需发送com.android.camera.action.CROP的Action即可,
Intent intent = new Intent("com.android.camera.action.CROP");
intent.setDataAndType(imageUri, "image/*");
intent.putExtra("scale", true);
intent.putExtra(MediaStore.EXTRA_OUTPUT, imageUri);
startActivityForResult(intent, 2);

获取完成显示照片Bitmap bitmap=BitmapFactory.decodeStream(getContentResolver().openInputStream(imageUri));picture.setImageBitmap(bitmap);

从相册中选择一张照片只需发送android.intent.action.GET_CONTENT的Action即可:

Intent intent = new Intent("android.intent.action. GET_CONTENT");
intent.setType("image/*");
intent.putExtra("crop", true);
intent.putExtra("scale", true);
intent.putExtra(MediaStore.EXTRA_OUTPUT, imageUri);
startActivityForResult(intent, 3);
注意使用ImageView控件显示图片时要注意裁剪图片的体积,否则容易引起程序崩溃或者无法显示图片的情况;

@简单地播放音频时使用MediaPlayer类,其常用控制方法有:

setDataSource(new File().getPath())设置要播放的音频文件的位置;
prepare()在开始播放之前调用这个方法完成准备工作;
start()开始或继续播放音频;
pause()暂停播放音频;
reset()将MediaPlayer对象重置到刚刚创建的状态;
seekTo()从指定的位置开始播放音频;
stop()停止播放音频。调用这个方法后的MediaPlayer对象无法再播放音频;
release()释放掉与MediaPlayer对象相关的资源;
isPlaying()判断当前MediaPlayer是否正在播放音频;
getDuration()获取载入的音频文件的时长;

一般的步骤是设置文件路径,完成准备,开始暂停或停止播放,释放资源;

@简单地播放视频时使用VideoView类,其将显示和控制于一身;

setVideoPath()设置要播放的视频文件的位置;
start()开始或继续播放视频;
pause()暂停播放视频;
resume()将视频重头开始播放;
seekTo()从指定的位置开始播放视频;
isPlaying()判断当前是否正在播放视频;
getDuration()获取载入的视频文件的时长;

一般的步骤是设置文件路径,开始暂停或停止播放,调用VideoView的suspend()方法释放资源;

--第九章 后台默默的劳动者,探究服务

@服务适合于不需要用户交互且要求长期运行的任务,服务并非运行在独立的进程中,而是依赖创建该服务的应用程序进程,如果应用程序进程被杀掉,该进程的服务也停止运行,服务并不会自动开启线程,因此需要手动创建子线程,否则同样会阻塞主线程;

@Android的UI也是线程不安全的,因此必须在主线程中进行UI的更新操作,如果在onClick(View v)方法中开启了子线程程序并更新UI将导致程序崩溃,Android提供了一个异步消息处理机制将解决这个问题,实际上就是使用Handler类来发送异步消息,具体步骤如下:

  1. 在主线程中创建Handler实例,Handler handler=new Handler(){public void handleMessage(Message msg){//更新UI}};
  2. 在子线程中发送消息,new Thread(new Runnable(){public void run(){Message messag=new Message();handler.sendMessage(message);}}).start();

异步消息处理机制就是每个线程中有一个Looper管理一个MessageQueue,Handler对象使用sendMessage()方法发送Message对象到消息队列中,Message对象允许携带少量消息包括携带what/arg1/arg2/obj等,最终消息传递到handlerMessage()方法中;

@Android为了方便在子线程中进行UI操作还提供了AsyncTask,其原理同样基于异步消息机制,利用它可以方便地从子线程切换到主线程;使用时需要继承它并指定三个泛型参数:

  1. Params在执行AsyncTask时需要传入的参数,可用于在后台任务中使用;
  2. Progress后台任务执行时,如果需要在界面上显示当前的进度,则使用这里指定的泛型作为进度单位;
  3. Result当任务执行完毕后,如果需要对结果进行返回,则使用这里指定的泛型作为返回值类型;

最简单的AsyncTask的定义例如:class MyTask extends AsyncTask<Void,Integer,Boolean>{...};

定义后需要重写其几个方法才能定制自己的任务:

  1. onPreExecute()在后台任务开始执行前调用,用于进行界面的初始化操作,比如显示一个进度条对话框等;
  2. doInBackground(Params...)该方法会在子线程中运行,这里处理所有的耗时任务;任务完成将结果返回,该方法中不可进行UI操作的,如果需要更新UI元素,例如反馈当前任务的进度,可以调用publishProgress(Progress...)方法来完成;
  3. onProgressUpdate(Progress...)当在后台任务中调用了publishProgress(Progress...)方法后,该方法很快会被调用,方法中携带的参数就是在后台任务中传递过来的;该方法中可以对UI进行操作,利用参数中的数值就可以对界面元素进行相应地更新;
  4. onPostExecute(Result)当后台任务执行完毕并返回时,该方法就很快会被调用;返回的数据会作为参数传递到此方法中,可以利用返回的数据来进行一些UI操作,例如提醒任务执行的结果,以及关闭掉进度条对话框;

最后使用MyTask的execute()方法启动任务即可

@定义服务时只需继承Service类并重写几个方法,另外需要在manifest文件的<application/>元素中使用<service android:name=""/>元素进行注册:

  1. onCreate()服务创建时调用一次,
  2. onStartCommand()每次服务启动时都会调用一次,
  3. onDestroy()服务销毁时调用,用于回收资源
  4. onBind()借助Binder对象实现与服务进行通信的方法

启动和停止服务都是使用Intent,Intent intent=new Intent(this,MyService.class);startService(intent);stopService(intent);另外还可以在MyService中任意位置调用stopSelf()方法来自己停止服务;

@实现与服务进行通信时需要借助Binder对象:在Service对象中自定义一个Binder对象,并定义多个public方法:

public class MyService extends Service {
	private MyBinder myBinder =new MyBinder ();
	class MyBinder extends Binder {
		public void myMethod1() {
			Log.d("MyLog", "myMethod1");
		}
		public int myMethod2() {
			Log.d("MyLog", "myMethod2");
			return 0;
		}
	}
	public IBinder onBind(Intent intent) {
		Log.d("MyLog", "onBind");
		return myBinder ;
	}
}
在主类中需要借助ServiceConnection类来实现绑定和解绑服务,需要覆写如下两个方法分别在服务绑定和解绑时调用:
private ServiceConnection connection = new ServiceConnection() {
	public void onServiceDisconnected(ComponentName name) {
	}
	public void onServiceConnected(ComponentName name, IBinder service) {
		myBinder = (MyService.MyBinder) service;
		myBinder.myMethod1();
		myBinder.myMethod2();
	}
};

绑定服务使用bindService(new Intent(this,MyService.class),connection,BIND_AUTO_CREATE)方法,解绑使用unbindService(connection)方法;绑定服务时的第三个参数是标志位,表示活动和服务绑定后自动创建服务,即绑定时自动执行服务的onCreate()方法;

@使用前台服务,其与普通服务最大的区别是,它会一直有一个运行的图标在系统状态栏显示,类似通知,它不会因系统内存不足而被回收;具体用法是在Service的onCreate方法中编写如下代码示例:

Notification notification = new Notification(icon_id, "", System.currentTimeMillis());
Intent notificationIntent = new Intent(this, MainActivity.class);
PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, notificationIntent, 0);
notification.setLatestEventInfo(this, title, content, pendingIntent);
startForeground(id, notification);

与构建通知类似,只是使用startForeground(id,notification)方法来开启前台服务;

@一个标准的服务需要开启子线程来处理耗时的逻辑,且希望能在服务执行完毕后自动停止,具体示例如下:

public class MyService extends Service {
	public void onCreate() {
		super.onCreate();
	}
	public int onStartCommand(Intent intent, int flags, int startId) {
		new Thread(new Runnable() {
			public void run() {
				//耗时逻辑
				stopSelf();
			}
		}).start();
		return super.onStartCommand(intent, flags, startId);
	}
	public void onDestroy() {
		super.onDestroy();
	}
	public IBinder onBind(Intent intent) {
		return null;
	}
}
这种标准写法需要注意不能忘记开启线程或调用stopSelf()方法,因此Android提供了一个IntentService类用于解决程序员的"大意",它将自动开启线程并运行完自动停止,具体创建步骤如下:
  1. 新建类继承IntentService类,并提供一个无参构造函数:public MyIntentService(){super("MyIntentService");},内部调用父类的有参构造函数;
  2. 实现protected void onHandleIntent(Intent intent)抽象方法,该方法用于处理耗时逻辑且它已经运行在子线程中了;
  3. 有需要还可以重写onDestroy()方法,其启动方式与普通服务一样,同样需要注册;

@Android的定时任务有两种实现方式,一种是借助Java的Timer类,但是其劣势是不适用于需要长期后台运行的任务,因为AndroidCPU有休眠机制,这会导致Timer运行异常,另一种是Alarm机制,它由唤醒CPU的功能,可以保证每次需要执行任务时能保证CPU正常工作;具体使用需用到AlarmManager类,使用Context的getSystemService(Context.ALARM_SERVICE))方法获取其实例,接着调用其set()方法就可以设置一个定时任务了,例如设定一个任务在10秒后执行,示例代码如:

alarmmanager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP,SystemClock.elapsedRealtime()+10000,pendingIntent);
set()方法第一个参数指定AlarmManager的工作类型,有四种值可选:
  1. ELAPSED_REALTIME表示让定时任务的触发时间从系统开机开始算起,但不会唤醒CPU;
  2. ELAPSED_REALTIME_WAKEUP同样表示让定时任务的触发时间从系统开机开始算起,但会唤醒CPU;
  3. RTC表示让定时任务的触发时间从1970年1月1日0点开始算起,但不会唤醒CPU;
  4. RTC_WAKEUP同样表示让定时任务的触发时间从1970年1月1日0点开始算起,但会唤醒CPU;

第二个参数是定时任务触发的时间,单位毫秒,使用SystemClock.elapsedRealtime()方法可以获取到系统开机至今所经历时间的毫秒数,使用System.currentTimeMillis()方法可以获取到1970年1月1日0点至今所经历时间的毫秒数;

第三个参数是PendingIntent,一般会使用getBroadcast()方法获取一个能后执行广播的PendingIntent,当定时任务触发时能执行onReceive()方法;

定时任务的逻辑放在服务的onStartCommand()方法中,然后使用广播接收器循环启动服务调用该方法;

需要注意的是从Android4.4版本后,set()方法设置的定时任务将不准确,系统会检测当前有多少个Alarm任务存在,并将时间较为接近的任务放在一起执行减少CPU的唤醒次数以延长电池使用时间;如果要求任务执行时间精确无误可以使用setExact()方法替代set()方法;

--第十章 看看精彩的世界,使用网络技术

@使用WebView展示网页,具体示例代码:

WebView webView = (WebView) findViewById(R.id.web_view);
webView.getSettings().setJavaScriptEnabled(true);
webView.setWebViewClient(new WebViewClient() {
	public boolean shouldOverrideUrlLoading(WebView view, String url) {
		view.loadUrl(url);
		return true;
	}
});
webView.loadUrl("http://www.baidu.com");
获取到WebView实例后设置其支持JS脚本,并设置WebViewClient使得点击一个链接后目标网页仍然在当前WebView中显示而不是打开系统浏览器了,加载网址使用其loadUrl()方法,注意需要声明android.permission.INTERNET访问网络权限;

@Android发送HTTP请求的方式有两种,使用HttpURLConnection和HttpClient:

获取HttpURLConnection实例,URL url=new URL("http://www.baidu.com");HttpURLConnection connection=(HttpURLConnection)url.openConnection();发送请求有两种方式GET/POST:

URL url = new URL("");
HttpURLConnection connection = (HttpURLConnection)url.openConnection();
//GET方式
connection.setRequestMethod("GET");
//设置连接超时
connection.setConnectTimeout(10000);
//设置读取超时
connection.setReadTimeout(10000);
//获取服务器的返回输入流
InputStream in = connection.getInputStream();
BufferedReader reader = new BufferedReader(new InputStreamReader(in));
//POST方式
connection.setRequestMethod("POST");
//获取输出流
DataOutputStream out = new DataOutputStream(connection.getOutputStream());
//提交的数据以键值对形式存在中间用&符隔开
out.writeBytes("username=admin&password=123456");
//关闭连接
connection.disconnect();
使用HttpClient更加简单:
//获取HttpClient接口实例
HttpClient httpClient = new DefaultHttpClient();
//发起GET请求
HttpResponse responseGet = httpClient.execute(new HttpGet("http://www.baidu.com"));
//发起POST请求
HttpPost httpPost = new HttpPost("http://www.baidu.com");
List<NameValuePair> params = new ArrayList<NameValuePair>();
params.add(new BasicNameValuePair("username","admin"));
params.add(new BasicNameValuePair("password","123456"));
UrlEncodedFormEntity entity = new UrlEncodedFormEntity(params, "utf-8");
httpPost.setEntity(entity);
HttpResponse responsePost = httpClient.execute(httpPost);
//读取响应
if(response.getStatusLine().getStatusCode() == 200) {}

如果返回的状态码是200表示请求和响应都成功,接下来获取响应内容:String responseText=EntityUtils.toString(response.getEntity(),"utf-8");

需要注意的是,也应该为请求设置超时时间,否则线程会阻塞;

@Android中有三种解析XML数据的方式,其中除了原始的DOM解析方式外,还有如下两种:

由于DOM解析方式需要全部加载整个XML文件到内存,因此DOM方式仅适合于解析较小的XML文件;

A.使用Pull方式解析XML数据:

在获取了服务器端的XML文件内容字符串之后,使用XmlPullParserFactory.newInstance().newPullparser()来获取XmlPullParser对象,然后调用其setInput(new StringReader(xmlDataString))方法开始解析,XmlPullParser对象有如下方法来执行解析工作:

  1. getEventType():返回int类型,其值有XmlPullParser.END_DOCUMENT/START_TAG/END_TAG;
  2. getName():返回XML元素的节点名称,如<name>abc</name>返回name;
  3. nextText():返回元素节点内容,如<name>abc</name>返回abc;
  4. next():返回下一个解析事件,其值同getEventType()方法;

Pull解析方式的实质是不断循环获取EventType的值,对节点名称进行判断后取节点内容值即可;

B.使用SAX方式解析XML数据:这种方式在语义上比Pull方式容易理解,但用法复杂一些;

在获取了服务器端的XML文件内容字符串之后,使用SAXParserFactory.newInstance().newSAXParser().getXMLReader()来获取XMLReader对象,然后为其设置Handler对象,XMLReader.setContentHandler(DefaultHandler),最后调用XMLReader.parse(new InputSource(new StringReader(xmlDataString)))来开始执行解析;

DefaultHandler有如下方法来执行解析工作:

  1. startDocument():开始XML解析时调用;
  2. stopDocument():完成XML解析时调用;
  3. startElement():开始解析某个结点时调用;
  4. stopElement():完成解析某个结点时调用;
  5. characters():获取结点中内容时调用;

注意解析出的数据会以参数形式传入上述某些方法中,另外在character()方法会被调用多次,还需处理换行符等;

@相比于XML外强中干臃肿,JSON体积更小适合网络传输,但语义性远不如XML直观;Android中解析JSON可以使用官方的JSONObject,还可以使用第三方的GSON/Jackson/FastJSON等:

A.使用JSONObject:较为简单,使用new JSONArray(jsonDataString)创建数组,使用JSONObject=jsonArray.getJSONObject(index)遍历数组即可,JSONObject对象提供getString(key)获取value值;

B.使用GSON库:其便利性在于可以直接映射成POJO对象,POJO pojo=new Gson().formJson(jsonDataString,POJO.class)或者List<POJO> pojo=new Gson().fromJson(jsonDataString,new TypeToken<List<POJO>>(){}.getType());

--其他章节笔记略过

接下来的章节是有关于安卓定位及传感器相关的知识,以及一个应用开发实例,另外书中还穿插介绍了Git使用的知识,以及大量的开发实践的代码干货,等有时间再一并来整理;

你可能感兴趣的:(android,笔记,第一行代码)