@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);代码;
@手动创建活动的步骤:
@Intent是Android程序中各组件之间进行交互的一种重要方式,它不仅可以指明当前组件想要执行的动作,还可以在不同组件之间传递数据。Intent一般可被用于启动活动、启动服务、以及发送广播等场景;
@Intent的用法大致可以分为两种,显式Intent和隐式Intent:
@在向上一个活动返回数据时,其原理是使用startActivityForResult()方法来启动SecondActivity的,在SecondActivity被销毁之后会回调上一个活动的onActivityResult()方法,因此我们需要在FirstActivity中重写这个方法来得到返回的数据;一般销毁逻辑写在按钮点击的事件中,或者写在点击返回键时重写的onBackPressed()方法中;
@Android使用任务Task来管理活动,一个任务就是一组栈里的活动的集合;
@活动在其生命周期中有4种状态:
@活动有3个生存期:
@活动生命周期示意图:
@Android停止状态的活动如果被系统回收了,当返回该活动时还是会显示该活动,但其不再是通过执行onRestart()方法,而是执行onCreate()方法,活动被重新创建一次,但是被回收活动中的临时数据也一并丢失了,Activity提供了一个onSaveInstanceState()方法,它在系统回收之前被调用,并携带一个Bundle类型的参数,它可以使用一系列重载方法来保存数据,然后从onCreate()方法中通过判断Bundle参数是否为null来取出之前保存的数据;
@Bundle可以用来保存数据,而Intent可以传递Bundle;
@Activity的启动模式有四种,可以通过在Manifest文件中给<activity/>标签中指定android:launchMode属性来指定:
@Android常用控件有TextView、Button、EditText、ImageView、ProgressBar、AlertDialog、ProgressDialog等;注意其中Dialog有一个setCancelable(flase)方法表示对话框不能通过返回键取消掉;
其他控件还有:
ScrollView:用于可滚动查看大量内容
@Android最基本的4种布局:
@AndroidUI元素相关属性整理如下:
@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下单位有如下几点:
@在AndroidSDK的tools文件夹下使用draw9path.bat来制作Nine-Patch图片,注意绘制后图片会有4条黑边,上左边框的矩形表示图片自动拉伸的部分,下右边框的矩形表示内容放置的区域;
@使用碎片的一般步骤:
@动态添加碎片的步骤:
// 获取待加载碎片实例 *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即可
@碎片与活动,碎片与碎片之间互相调用方法:
@碎片生命周期与活动类似,除包含活动的中的6个方法之外,还包含以下回调方法:
其完整生命周期示意图如下:
@和活动一样,碎片也可以通过onSaveInstanceState()方法来保存数据,保存的数据在onCreate()、onCreateView()、onActivityCreated()这三个方法中都可以重新得到;
@动态加载布局时需要使用Android限定符,具体包括大小(small/normal/large/xlarge)、分辨率(ldpi/mdpi/hdpi/xhdpi)、方向(land/port);
@最小宽度限定符运行对屏幕的宽度指定一个最小值(单位dp),然后大于或小于这个值会加载不同布局,一般在res文件夹中创建sw*dp的文件夹并放置布局文件
@Android广播分为标准广播和有序广播两种类型:
@注册广播的方式有两种,一种在代码中注册即动态注册,一种在Manifest中注册及静态注册,创建广播接收器只需将类继承自BroadcastReceiver,重写父类的onReceive()方法即可;
@发送自定义广播的步骤:
@本地广播即只在程序内部传递的广播,其使用步骤如下:
注意本地广播无法使用静态注册,使用时安全性可以保证,效率比系统全局广播高效;
@Android系统提供三种简单数据持久化方式:文件存储、SharedPreference存储以及数据库存储;
@文件存储适合于存储简单的文本数据或二进制数据,一般用于缓存部分临时数据,如果需要存储复杂文本数据则需要自定义格式规范;
@将数据存储到SharedPreferences中,一般用于存储应用程序首选项,获取SharedPreferences对象有三种方法:
得到SharedPreferences对象后,使用如下步骤来存储数据:
获取数据则使用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对象:
@访问内容提供器中的数据需要借助ContentResolver类,它提供了类似于SQLiteDatabase的CRUD操作只是参数稍有区别,一般这些方法不再接收表名而是使用Uri对象作参数,它相当于数据的唯一标识符,包含权限authority和路径path两部分,例如Uri uri=Uri.parse(content://packagename.provider/table),体访问代码例如:Cursor cursor=getContentResolver().query(uri,null...);
@创建自定义的内容提供器需要继承ContentProvider类并实现其6个方法:
@一般自定义的ContentProvider中包含一个UriMatcher和DatabaseHelper对象;UriMatcher用于过滤Uri,使用其addURI()方法添加希望匹配的uri格式,使用match()方法对Uri对象进行匹配返回对应添加时的代码;DatabaseHelper用于提供数据;其中getType()方法获取对应MIME类型,Android规定一个内容URI所对应的MIME字符串主要由三部分组分,Android对这三个部分做了如下格式规定:
@无论是在活动中,或者在广播接收器或者服务中创建通知,步骤都类似:
@获取PendingIntent对象使用其静态方法包括getActivity(),getBroadcast(),getService(Context,0,Intent,flag),它们接收4个参数,第三个参数指定其意图,第四个参数确定其行为,其值有四种:
@发送通知时可以同时指定播放的音频,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类来发送异步消息,具体步骤如下:
异步消息处理机制就是每个线程中有一个Looper管理一个MessageQueue,Handler对象使用sendMessage()方法发送Message对象到消息队列中,Message对象允许携带少量消息包括携带what/arg1/arg2/obj等,最终消息传递到handlerMessage()方法中;
@Android为了方便在子线程中进行UI操作还提供了AsyncTask,其原理同样基于异步消息机制,利用它可以方便地从子线程切换到主线程;使用时需要继承它并指定三个泛型参数:
最简单的AsyncTask的定义例如:class MyTask extends AsyncTask<Void,Integer,Boolean>{...};
定义后需要重写其几个方法才能定制自己的任务:
最后使用MyTask的execute()方法启动任务即可
@定义服务时只需继承Service类并重写几个方法,另外需要在manifest文件的<application/>元素中使用<service android:name=""/>元素进行注册:
启动和停止服务都是使用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类用于解决程序员的"大意",它将自动开启线程并运行完自动停止,具体创建步骤如下:
@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的工作类型,有四种值可选:
第二个参数是定时任务触发的时间,单位毫秒,使用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对象有如下方法来执行解析工作:
Pull解析方式的实质是不断循环获取EventType的值,对节点名称进行判断后取节点内容值即可;
B.使用SAX方式解析XML数据:这种方式在语义上比Pull方式容易理解,但用法复杂一些;
在获取了服务器端的XML文件内容字符串之后,使用SAXParserFactory.newInstance().newSAXParser().getXMLReader()来获取XMLReader对象,然后为其设置Handler对象,XMLReader.setContentHandler(DefaultHandler),最后调用XMLReader.parse(new InputSource(new StringReader(xmlDataString)))来开始执行解析;
DefaultHandler有如下方法来执行解析工作:
注意解析出的数据会以参数形式传入上述某些方法中,另外在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使用的知识,以及大量的开发实践的代码干货,等有时间再一并来整理;