传智播客的安卓基础视频-20151228-Android基础视频
PS:本笔记配对传智播客的安卓基础视频-20151228-Android基础视频
对应视频的课件解答:张萍老师:http://bbs.itcast.cn/?197082
数据存储与界面展示
Android工程的目录详解:
Android版本介绍、常用手机的分辨率:
Android测试程序需要添加的代码:
1.在清单文件中的application里面添加下面的代码配置函数库
<uses-library android:name="android.test.runner"/>
2.再在清单文件application外面添加下面的三行代码:
android:name="android.test.InstrumentationTestRunner"android:targetPackage="com.example.simpledail" android:label="TestApplication" />
PS:记得修改targetPackage为当前你需要测试程序的包的路径
SharedPreferences的介绍:
背景:对于软件配置参数的保存,如果是window软件,通常会采用ini文件进行保存;如果是 j2se应用,通常会采用properties属性文件进行保存;如果是Android应用,Android 平台提供了一个SharedPreferences类,它是一个轻量级的存储类,特别适合用于保存软件配置参数。使用SharedPreferences保存数据,其背后是用xml文件存放数据,文件存放在/data/data//shared_prefs目录下。
因为SharedPreferences背后是使用xml文件保存数据,getSharedPreferences(name,mode)方法的第一个参数用于指定该文件的名称,名称不用带后缀,后缀会由Android自动加上。方法的第二个参数指定文件的操作模式,共有四种操作模式,这四种模式分别是:
Context.MODE_PRIVATE:
为默认操作模式,代表该文件是私有数据,只能被应用本身访问,在该模式下,写入的内容会覆盖原文件的内容,如果想把新写入的内容追加到原文件中。可以使用Context.MODE_APPEND
Context.MODE_APPEND:
模式会检查文件是否存在,存在就往文件追加内容,否则就创建新文件。
Context.MODE_WORLD_READABLE和Context.MODE_WORLD_WRITEABLE
用来控制其他应用是否有权限读写该文件。
MODE_WORLD_READABLE:表示当前文件可以被其他应用读取;
MODE_WORLD_WRITEABLE:表示当前文件可以被其他应用写入。
如果希望文件被其他应用读和写,可以传入:
openFileOutput(“123.txt”, Context.MODE_WORLD_READABLE + Context.MODE_WORLD_WRITEABLE);
详情看
Android中使用SharedPreferences进行数据存储及文件操作-txgc_wm-ChinaUnix博客
1.存数据,生成的是xml文件
//1 获取sp的实例 SharedPreferences sp = getSharedPreferences("config", Context.MODE_PRIVATE);//confid为存储文件的姓名 //2 获取编辑器 SharedPreferences.Editor edit = sp.edit(); //3.存数据,第一个为key,第二个为值 edit.putString("username","yueyue"); edit.putString("password","123456"); //4.提交数据 edit.commit();
2.拿数据:
//1 获取sp的实例 SharedPreferences sp = getSharedPreferences("config", Context.MODE_PRIVATE);//confid为存储文件的姓名 //2 取数据 第一个为值的key,第二个为默认返回值(当没有取到值的时候) String username = sp.getString("username", "");
官方google给出的实例:
http://www.android-doc.com/guide/topics/data/data-storage.html#pref
数据库SQLite学习笔记:
1.sqlite3 打开数据库
2.chmod lunix下修改文件的权限
3.改变dos的编码方式 chcp936(GBK) chcp65001(utf-8)
如何创建一个数据库
定义一个类继承SqliteOpenHelper
/** * * @param context 上下文环境 * @param name 数据库名字 * @param factory 游标工厂,一般用不上 * @param version 数据库版本号 */ public MySqliteHelper(Context context, String name, SQLiteDatabase.CursorFactory factory, int version) { super(context, name, factory, version); }
如果感觉参数太多或者有些参数不需要,可以写成下面这种
/** * * @param context 上下文环境 */ public MySqliteHelper(Context context) { super(context, "mysqlite.db", null, 1); }
SQLite增删改查:
打开或者创建数据库
库增加:一般使用系统的继承方法那里增加数据库名字
public MySQLiteHelper(Context context) { super(context, "sqlitehaha.db", null , 1); // TODO Auto-generated constructor stub }
Minactivity.java中:
MySQLiteHelper helper = new MySQLiteHelper(this);// MySQLiteHelper helper = new MySQLiteHelper(getApplicationContext());// 打开或者创建数据库 如果是第一次就是创建SQLiteDatabase sqLiteDatabase = helper.getWritableDatabase();// 打开或者创建数据库 如果是第一次就是创建 如果磁盘满了 返回只读的SQLiteDatabase readableDatabase1 = helper.getReadableDatabase();
表格操作
表增加
db.execSQL("create table info(_id integer primary key autoincrement,name varchar(20),phone varchar(20))");
表修改
db.execSQL("alter table info add money varchar(20)");
数据操作
数据增加:
1.
//执行增加一条的sql语句 db.execSQL("insert into info(name,phone) values(?,?)", new Object[]{"张三","1388888"});
2.
/** * table 表名 ContentValues 内部封装了一个map key: 对应列的名字 value对应的值 **/ ContentValues values = new ContentValues(); values.put("name", "王五"); values.put("phone", "110"); // 返回值代表插入新行的id long insert = db.insert("info", null, values); // 底层就在组拼sql语句 // [3]数据库用完需要关闭 db.close(); if (insert > 0) { Toast.makeText(getApplicationContext(), "添加成功", 1).show(); } else { Toast.makeText(getApplicationContext(), "添加fail", 1).show(); }
数据修改
1.
db.execSQL("update info set phone=? where name=? ", new Object[]{"138888888","张三"});
2.
ContentValues values = new ContentValues();values.put("phone", "114");//代表更新了多少行 int update = db.update("info", values, "name=?", new String[]{"王五"});//int update = db.update("info", values, "name='王五',null");db.close();Toast.makeText(getApplicationContext(), "更新了"+update+"行", 0).show();
数据删除
1.
db.execSQL("delete from info where name=?", new Object[]{"张三"});
2.
//返回值代表影响的行数 int delete = db.delete("info", "name=?", new String[]{"王五"});//int delete = db.delete("info", "name='王五'", null);db.close();Toast.makeText(getApplicationContext(), "删除了"+delete+"行", 0).show();
数据查询
1.
Cursor cursor = db.rawQuery("select * from info", null);if (cursor!= null && cursor.getCount()>0) {while(cursor.moveToNext()){//columnIndex代表列的索引 String name = cursor.getString(1);String phone = cursor.getString(2);System.out.println("name:"+name+"========="+phone); }}
2.
Cursor cursor = db.query("info", new String[]{"name","phone"}, "name=?", new String[]{"王五"}, null, null, null);if (cursor!= null&&cursor.getCount()>0) {while(cursor.moveToNext()){//columnIndex代表列的索引 String name = cursor.getString(1);String phone = cursor.getString(2);System.out.println("name:"+name+"========="+phone);
事物
db.beginTransaction(); try { ... db.setTransactionSuccessful(); } finally { db.endTransaction(); }
PS:Cursor运用
代码模板:
MyOpenHelper myOpenHelper = new MyOpenHelper(getApplicationContext());SQLiteDatabase db = myOpenHelper.getReadableDatabase(); Cursor cursor = db.query("info", null, null, null, null, null, null);// 得到info数据表的数据 if(cursor!=null && cursor.getCount()>0) {// 判断cursor不为空以及数据库的数据行>0 while (cursor.moveToNext()) {// 将cursor向下解析 String name = cursor.getString(1);// 得到数据表格中的名字列数据 String phone = cursor.getString(2);// 得到数据表格中的名字工资列数据 System.out.println("name" + name + "----" + phone); } }
Intent简单学习:
// 点击按钮 实现拨打电话的功能 public void click1(View v) { // [1]创建意图对象 Intent intent = new Intent(); // [2] 设置拨打的动作 intent.setAction(Intent.ACTION_CALL); // [3]设置拨打的数据 intent.setData(Uri.parse("tel:" + 119)); // [4]开启Activity 记得加上权限 startActivity(intent); } // 点击按钮 跳转到TestActivity public void click2(View v) { // [1]创建意图对象 意图就是我要完成一件事 Intent intent = new Intent(); // [2] 设置跳转的动作 intent.setAction("com.itheima.testactivity"); // [3] 设置category intent.addCategory("android.intent.category.DEFAULT"); // [4]设置数据 // intent.setData(Uri.parse("itheima:"+110)); // [5]设置数据类型 // intent.setType("aa/bb"); // [6]注意 小细节☆ 如果setdata 方法和 settype 方法一起使用的时候 应该使用下面这个方法 intent.setDataAndType(Uri.parse("itheima1:" + 110), "aa/bb1"); // [4]开启Activity startActivity(intent); }
Listview :
PS:不管是什么adapter,作用就是把数据展示到listview
Listview出现内存溢出的解决办法:
10-28 03:16:57.267: E/dalvikvm-heap(1618): Out of memory on a 24332-byte allocation. // 内存溢出问题
convertView简介:
The old view to reuse, if possible.Note: You should check that this view is non-null and of an appropriate type before using
Listview的行高设置建议:
android:layout_width=”match_parent”
android:layout_height=”match_parent”
listView优化
1:复用convertView 2:复用ViewHolder让其减少findViewById()次数 3:static ViewHolder 4:分页算法
参考代码:
ViewHolder viewHolder = null; if(convertView == null){ viewHolder = new ViewHolder(); convertView = View.inflate(); viewHolder.text1 = convertView.findViewById(R.id.text1); convertView.setTag(viewHolder); }else{ viewHolder = (ViewHolder)convertView.getTag(); } viewHolder.text1.setText("文本内容"); static class ViewHolder{ TextView text1; }
特点:
1.线性布局、相对布局都继承ViewGroup,ViewGroup可以有自己的孩子
2.通过一个打气筒inflate可以把一个布局文件转换成一个View对象
生成打气筒的常用三种方式:
// [1]想办法把我们自己定义的布局转换成一个view对象 就可以了 View view; if (convertView == null) { // 创建新的view对象 可以通过打气筒把一个布局资源转换成一个view对象 // resource 就是 我们定义的布局文件 // [一☆☆☆☆]获取打气筒服务 // view = View.inflate(getApplicationContext(), R.layout.item, // null); // [二☆☆☆☆]获取打气筒服务 // view=LayoutInflater.from(getApplicationContext()).inflate(R.layout.item, // null); // [三☆☆☆☆]获取打气筒服务 LayoutInflater inflater = (LayoutInflater) getSystemService(LAYOUT_INFLATER_SERVICE); view = inflater.inflate(R.layout.item, null); } else { // 复用历史缓存对象 view = convertView; }
网络编程
主线程一般作为UI线程,更新UI(如view等),子线程不可以直接更新U,需要更新的话可以调动Handler
PS:联网操作的时候记得加上联网权限:
<uses-permission android:name="android.permission.INTERNET"/>
1.HttpURLConnection:用于接受和发送数据
HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection(); //创建实例,用于接受和发送数据
【1】HttpURLConnection接受数据
子线程更新UI:
默认原则:子线程一般不更新UI(Android4.0之后默认的),一般只有主线程才可以更新UI
1.使用Handler更新:
//创建handler 对象,当实例handler调用sendmessage的方法时候自动调用handlemaess方法 private Handler handler = new Handler(){ //处理消息 public void handleMessage(android.os.Message msg) { Bitmap bitmap = (Bitmap) msg.obj; //obj是吸纳任何变量类型的,吸了什么类型取出来就强制转为什么类型 iv.setImageBitmap(bitmap); }; };
2.使用runOnUiThread()进行更新也可以
注意:这句api 不 管你在什么位置上(不管是在主线程还是子线程里),调用 action都运行在UI线程里
runOnUiThread(new Runnable() { public void run() { iv.setImageBitmap(cacheBitmap); //更新图片UI,调用runOnUiThread API函数 } });
Handler对比runOnUiThread
[1]如果仅仅更新ui,那么使用runOnUiThread就可以了
[2]有时候通过Handler发送消息的时候需要携带数据,那么仅能使用Handler了
Handler的api介绍:
[1] postDelayed函数
//5秒钟后 执行run方法 new Handler().postDelayed(new Runnable() { @Override public void run() { tv.setText("哈哈哈哈哈"); } }, 5000);
PS:java中也有类似的方法TimerTask:
Timer timer = new Timer(); TimerTask task=new TimerTask() { @Override public void run() { // TODO Auto-generated method stub System.out.println("我是Java方法实现的"); } }; //五秒后执行task timer.schedule(task, 5000); //3秒后 每隔1秒执行一次run方法 timer.schedule(task, 3000,1000);
当Java中的TimerTask方法使用每个几秒执行一次的方法时候注意一下,如果不销毁,即使你退出了 程序,但他依旧在后台执行活动,记得在销毁活动Activity的时候把TimerTask销毁
//当Activity销毁的时候 会执行这个方法 @Override protected void onDestroy() { timer.cancel(); //销毁timer task.cancel(); //销毁 super.onDestroy(); }
对比:Handler延时api可以更新UI,但是TimerTask不可以更新UI。TimerTask中内置runOnUiThread这个api才可以更新UI
开源项目smartimageview介绍
[1]把com包源码拷贝到当前工程
[2]在使用smartimageview类的时候,在布局里面定义的时候一定是这个类的完整包名+类名
api:应用程序界面(接口)
【2】HttpURLConnection发送数据(向服务器提交数据)
问:基于什么协议? 答:http协议
get方式:组拼url地址把数据到url显示,get方式提交数据大小限制:1kb(浏览器规定),4kb(http协议规定)
post方式:post方式提交安全
get跟post方式的区别:
1.请求路径不同:get方式url直接在后面带数据,post方式通过把请求体的方式把数据发给服务器(以流的形式写给服务器)
2.post方式要自己组拼请求体的内容
3.post方式比get方式多了2个头信息 content-length以及content-type
get方式:
new Thread() { public void run() { try { // [2]获取用户名和密码 String name = et_username.getText().toString().trim(); String pwd = et_password.getText().toString().trim(); // [2.1]定义get方式要提交的路径 小细节 如果提交中文要对name 和 pwd 进行一个urlencode // 编码 String path = "http://192.168.1.235:8080/login/LoginServlet?username=" + URLEncoder.encode(name, "utf-8") + "&password=" + URLEncoder.encode(pwd, "utf-8") + ""; // (1) 创建一个url对象 参数就是网址 URL url = new URL(path); // (2)获取HttpURLConnection 链接对象 HttpURLConnection conn = (HttpURLConnection) url.openConnection(); // (3)设置参数 发送get请求 conn.setRequestMethod("GET"); // 默认请求 就是get 要大写 // (4)设置链接网络的超时时间 conn.setConnectTimeout(5000); // (4.2)设置网络读取的超时时间 conn.setReadTimeout(5000); // (4.3)连接 conn.connect(); // (5)获取服务器返回的状态码 int code = conn.getResponseCode(); // 200 代表获取服务器资源全部成功 // 206请求部分资源 if (code == 200) { // (6)获取服务器返回的数据 以流的形式返回 InputStream inputStream = conn.getInputStream(); // (6.1)把inputstream 转换成 string String content = StreamTools.readStream(inputStream); // (7)把服务器返回的数据展示到Toast上 不能在子线程展示toast showToast(content); } } catch (Exception e) { e.printStackTrace(); } }; }.start();
post方式:
new Thread() { public void run() { try { // [2]获取用户名和密码 String name = et_username.getText().toString().trim(); String pwd = et_password.getText().toString().trim(); // [2.1]定义get方式要提交的路径 String data = "username=" + URLEncoder.encode(name, "utf-8") + "&password=" + URLEncoder.encode(pwd, "utf-8") + ""; // 请求体的内容 // 一 ☆☆☆☆☆☆☆和get方式提交数据 区别 路径不同 String path = "http://192.168.1.235:8080/login/LoginServlet"; // (1) 创建一个url对象 参数就是网址 URL url = new URL(path); // (2)获取HttpURLConnection 链接对象 HttpURLConnection conn = (HttpURLConnection) url.openConnection(); // (3)设置参数 发送post请求 // 二 ☆☆☆☆☆☆☆和get方式提交数据 区别 设置请求方式是post conn.setRequestMethod("POST"); // 默认请求 就是get 要大写 // (4)设置链接网络的超时时间 conn.setConnectTimeout(5000); // 三 ☆☆☆☆☆☆☆和get方式提交数据 区别 要多设置2个请求头信息 // 设置头信息 conn.setRequestProperty("Content-Type", "application/x-www-form-urlencoded"); conn.setRequestProperty("Content-Length", data.length() + ""); // 四 ☆☆☆☆☆☆☆ 把我们组拼好的数据提交给服务器 以流的形式提交 conn.setDoOutput(true);// 设置一个标记 允许输出 conn.getOutputStream().write(data.getBytes()); // (5)获取服务器返回的状态码 int code = conn.getResponseCode(); // 200 代表获取服务器资源全部成功 206请求部分资源 if (code == 200) { // (6)获取服务器返回的数据 以流的形式返回 InputStream inputStream = conn.getInputStream(); // (6.1)把inputstream 转换成 string String content = StreamTools.readStream(inputStream); // (7)把服务器返回的数据展示到Toast上 不能在子线程展示toast showToast(content); } } catch (Exception e) { e.printStackTrace(); } }; }.start();
Httpclient向服务器提交数据:
get方式:
// [2.1]定义get方式要提交的路径 小细节 如果提交中文要对name 和 pwd 进行一个urlencode 编码 String path = "http://192.168.1.235:8080/login/LoginServlet?username=" + URLEncoder.encode(name, "utf-8") + "&password=" + URLEncoder.encode(pwd, "utf-8") + ""; // [3]获取httpclient实例 DefaultHttpClient client = new DefaultHttpClient(); // [3.1]准备get请求 定义 一个httpget实现 HttpGet get = new HttpGet(path); // [3.2]执行一个get请求 HttpResponse response = client.execute(get); // [4]获取服务器返回的状态码 int code = response.getStatusLine().getStatusCode(); if (code == 200) { // [5]获取服务器返回的数据 以流的形式返回 InputStream inputStream = response.getEntity().getContent(); // [6]把流转换成字符串 String content = StreamTools.readStream(inputStream); // [7]展示结果 showToast(content); }
post方式:
// [2]获取用户名和密码 String name = et_username.getText().toString().trim(); String pwd = et_password.getText().toString().trim(); String path = "http://192.168.11.73:8080/login/LoginServlet"; // [3]以httpClient 方式进行post 提交 DefaultHttpClient client = new DefaultHttpClient(); // [3.1]准备post 请求 HttpPost post = new HttpPost(path); // [3.1.0]准备parameters List<NameValuePair> lists = new ArrayList<NameValuePair>(); // [3.1.1]准备 NameValuePair 实际上就是我们要提交的用户名 和密码 key是服务器key :username BasicNameValuePair nameValuePair = new BasicNameValuePair("username", name); BasicNameValuePair pwdValuePair = new BasicNameValuePair("password", pwd); // [3.1.3] 把nameValuePair 和 pwdValuePair 加入到集合中 lists.add(nameValuePair); lists.add(pwdValuePair); // [3.1.3]准备entity UrlEncodedFormEntity entity = new UrlEncodedFormEntity(lists); // [3.2]准备post方式提交的正文 以实体形式准备 (键值对形式 ) post.setEntity(entity); HttpResponse response = client.execute(post); // [4]获取服务器返回的状态码 int code = response.getStatusLine().getStatusCode(); if (code == 200) { // [5]获取服务器返回的数据 以流的形式返回 InputStream inputStream = response.getEntity().getContent(); // [6]把流转换成字符串 String content = StreamTools.readStream(inputStream); // [7]展示结果 showToast(content); }
Async-Httpclient开源项目向服务器提交数据:
快速Android开发系列网络篇之Android-Async-Http - AngelDevil - 博客园
get方式:
// [3]使用开源项目进行get请求 // [3.1]创建asynchttpclient AsyncHttpClient client = new AsyncHttpClient(); // [3.2]进行get 请求 client.get(path, new AsyncHttpResponseHandler() { // 请求成功的回调方法 @Override public void onSuccess(int statusCode, Header[] headers, byte[] responseBody) { try { Toast.makeText(getApplicationContext(), new String(responseBody, "gbk"), 1).show(); } catch (UnsupportedEncodingException e) { e.printStackTrace(); } } // 请求失败 @Override public void onFailure(int statusCode, Header[] headers, byte[] responseBody, Throwable error) { } });
post方式:
// [3.1]创建asynchttpclient AsyncHttpClient client = new AsyncHttpClient(); // [3.1.0]准备请求体的内容 RequestParams params = new RequestParams(); params.put("username", name); params.put("password", pwd); // [3.2]进行post请求 params 请求的参数封装 client.post(path, params, new AsyncHttpResponseHandler() { // 请求成功 登录成功 @Override public void onSuccess(int statusCode, Header[] headers, byte[] responseBody) { try { Toast.makeText(getApplicationContext(), new String(responseBody, "gbk"), 1).show(); } catch (UnsupportedEncodingException e) { e.printStackTrace(); } } @Override public void onFailure(int statusCode, Header[] headers, byte[] responseBody, Throwable error) { } });
PS:请求成功仅仅是服务器有响应,登录成功指的是你输入的账号密码与服务器中的一组账号密码匹配成功
总结:
【1】HttpURLConnection:可以带请求数据,如实现断点下载传输等
【2】Httpclient:很少人用,几乎没有人使用
【3】Async-Httpclient开源项目:仅仅是传一些账号密码用它更方便
多线程下载:
JavaSE多线程下载:
设置请求服务器文件的位置:
//设置一个请求Range,作用就是告诉服务器每个线程下载的开始位置以及结束位置conn.setRequestProperty("Range", "bytes="+startIndex+"-"+endIndex);
多线程加速下载:
【1】不是说线程开的越多下载就快(手机迅雷建议开3-4个线程就好)
【2】还受服务器带宽的影响
【3】更多的cpu资源给了你
实现断点续传:
就是把当前线程下载的位置给存起来,下次再下载的时候就按照上次下载的位置继续下载就可以了
开源项目实现断点续传(xutiles):
Xutils使用方法:http://blog.csdn.net/dj0379/article/details/38356773/
四大组件
PS:
1.四大组件的定义方式都是一样的,都需要在清单文件配置一下
2.意图是四大组件的纽带
Activity 显示界面
[1] 安卓的四大组件都需要在清单文件AndroidManifest.xml配置(xml中注释快捷键Ctrl+shift+/)
[2] 如果你想让你的应用有多个启动图标,你的activity需要这样配置
<intent-filter><action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> intent-filter>
[3] activity的icon以及label可以自定义,没有指定的话默认使用application中的icon和label
[4]一个安卓应用一般配置一个启动图标就可以了,多的话不仅是用户桌面繁琐
意图:
Android学习之Intent使用 - zhouhb - 博客园
意图就是我要完成一件事
隐式意图:
定义:通过一组动作或者数据
// [4]设置数据,This method automatically clears any type that was previously set by setType. // intent.setData(Uri.parse("itheima1:"+110));// [5]设置数据类型,This method automatically clears any data that was previously set by setData. // intent.setType("aa/bb1");// [6]注意 小细节☆ 如果setdata 方法和 settype 方法一起使用的时候 应该使用下面这个方法intent.setDataAndType(Uri.parse("itheima1:" + 110), "aa/bb1");
显式意图:
定义:通过指定具体的包名(在项目的AndroidMainfest.xml里面)和类名
// 点击按钮跳转到 TestActivitypublic void click3(View v) {// [1]创建意图对象 意图就是我要完成一件事Intent intent = new Intent(this, Test3Activity.class);// [2]设置包名和类名 packageName:当前应用的包名// intent.setClassName("com.itheima.newactivity",// "com.itheima.newactivity.Test3Activity");// [3]开启ActivitystartActivity(intent);}
总结:
1.开启自己应用的界面用显式意图
2.开启其他应用(如系统应用)用隐式意图
3.显式意图安全一些
Activity生命图解
两分钟彻底让你明白Android Activity生命周期(图文)! - Android_Tutor的专栏 - 博客频道 - CSDN.NET
横竖屏切换Activity的生命周期:
快捷键:Ctrl+F11
android:screenOrientation=”portrait” 代表竖屏
android:screenOrientation=”landscape” 代表横屏
栈:先进后出
队列:先进先出
task 什么叫任务
[1]打开一个Activity叫进栈 关闭一个Activity叫出栈
[2]我们操作的Activity永远是栈顶的Activity
[3]任务栈是用来维护用户操作体验的
[4]应用程序退出了是任务栈清空了
[5]一般情况一个应用程序对应一个任务栈
Activity的四种启动模式
android:launchMode="standard"
1、对于使用standard 模式的活动,系统不会在乎这个活动是否已经在返回栈中存在,每次启动都会创建该活动的一个新的实例。 例如A启动A,A再接着启动A,A继续启动A,然后再分别出栈,如图所示 2、当活动的启动模式指定为 singleTop,在启动活动时如果发现返回栈的栈顶已经是该活动,则认为可以直接使用它,不会再创建新的活动实例。
3、当活动的启动模式指定为 singleTask,每次启动该活动时系统首先会在返回栈中检查是否存在该活动的实例,如果发现已经存在则直接使用该实例,并把在这个活动之上的所有活动统统出栈,如果没有发现就会创建一个新的活动实例。
4、使用singleInstance 模式就可以解决这个问题,在这种模式下会有一个单独的返回栈来管理这个活动,不管是哪个应用程序来访问这个活动,都共用的同一个返回栈,也就解决了共享活动实例的问题。 假设B启动A,A启动C,其中A的启动模式为singleInstance,则: 返回的页面顺序是C-B-A
转载请注明:Android开发中文站 » Activity的四种启动模式-图文并茂
### Broadcast Receiver 广播 背景:Android系统已经定义好了一些广播事件,比如外拨电话、短信到来、sd状态,电池电量。。。Android开发者使用Broadcast Receiver 广播去接收系统已经定义好的这些事件 #### 不同版本广播的特点: [1]在4.0 谷歌工程师要求 第一次安装应用的时候必须有的界面 这样的广播接受者才生效 [2]在设置页面有一个强行停止的按钮 如果说用户点击了 强行停止按钮那么广播接收者也不生效 [3]在2.3的手机上没有这样的安全设计 广播接收者的一般步骤: [1]定义广播接收者 相当于你买了一个收音机 ![这里写图片描述](https://img-blog.csdn.net/20170303234249956?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvc2ltcGxlYmFt/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast) [2]在清单文件里配置 相当于你装了一块电池 ![这里写图片描述](https://img-blog.csdn.net/20170303234314864?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvc2ltcGxlYmFt/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast) [3]具体 调节到了合适的频道 ![这里写图片描述](https://img-blog.csdn.net/20170303234333035?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvc2ltcGxlYmFt/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast) 注意事项: 1.接收系统电话的广播需要用到权限android.permission.PROCESS_OUTGOING_CALLS 2.监听sd卡状态的时候,需要在清单配置
<data android:scheme="file"/>
系统广播:
短信监听实例:
[1]在清单文件的配置
<receiver android:name="com.itheima.smslistener.SmsListenerReceiver"><intent-filter ><action android:name="android.provider.Telephony.SMS_RECEIVED" />intent-filter>receiver>
2.具体实现代码
//当短信到来的时候执行 @Overridepublic void onReceive(Context context, Intent intent) {//获取发送者的号码 和发送内容 pdus是固定写法来的Object []objects = (Object[]) intent.getExtras().get("pdus");for (Object obj : objects) {//[1]获取smsmessage实例 SmsMessage smsMessage = SmsMessage.createFromPdu((byte[]) obj);//[2]获取发送短信的内容 String messageBody = smsMessage.getMessageBody();//获得发来短信的内容String address = smsMessage.getOriginatingAddress();//获得短信的发送者的手机号码System.out.println("body:"+messageBody+"-----"+address);}
[3]加上权限android.permission.RECEIVE_SMS
应用卸载安装监听案例
[1]在清单文件配置:
<receiver android:name="com.itheima.appstate.AppStateReceiver" > <intent-filter> <action android:name="android.intent.action.PACKAGE_INSTALL" /> <action android:name="android.intent.action.PACKAGE_ADDED" /> <action android:name="android.intent.action.PACKAGE_REMOVED" /> <data android:scheme="package" /> intent-filter> receiver>
[2]实现代码
//当有新的应用被安装 了 或者有应用被卸载 了 这个方法调用 @Override public void onReceive(Context context, Intent intent) { //获取当前广播事件类型 String action = intent.getAction(); if ("android.intent.action.PACKAGE_INSTALL".equals(action)) { System.out.println("应用安装了11111"); }else if ("android.intent.action.PACKAGE_ADDED".equals(action)) { System.out.println("应用安装了22222"); }else if("android.intent.action.PACKAGE_REMOVED".equals(action)){ System.out.println("应用卸载了"+intent.getData()); } }
注意:在Broadcast Receiver 广播中,有两个在配置清单文件中需要配置data数据的,一个是监听发来的短信(需要配置为了file),一个是监听应用卸载安装(需要配置为”package”)
系统重启广播案例:
注意:不能直接在广播接收者中开启Activity
[1]在清单文件配置:
<receiver android:name="com.itheima.bootreceiver.BootReceiver"><intent-filter ><action android:name="android.intent.action.BOOT_COMPLETED"/>intent-filter>receiver>
[2]实现代码
// 当手机重新启动的时候调用@Overridepublic void onReceive(Context context, Intent intent) {// 在这个方法里面开启activityIntent intent2 = new Intent(context, MainActivity.class);// ☆☆☆注意 不能在广播接收者里面直接开启activity 需要添加一个标记 添加一个任务栈的标记intent2.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);// 开启activitycontext.startActivity(intent2);}
[3]加上权限:android.permission.RECEIVE_BOOT_COMPLETED
ps:屏蔽返回键的方法
@Overridepublic void onBackPressed() { //主要实现返回键的功能// TODO Auto-generated method stub// super.onBackPressed();//父类主要实现了finish(),我们想屏蔽它,就不让它调用父类方法就好System.out.println("已经屏蔽了返回键");}
自定义广播:
参考:android有序广播和无序广播的区别 - ljb_blog的专栏 - 博客频道 - CSDN.NET
1.有序广播
是通过Context.sendOrderedBroadcast来发送,所有的receiver依次执行。
BroadcastReceiver可以使用setResult系列函数来结果传给下一个BroadcastReceiver,通过getResult系列函数来取得上个BroadcastReceiver返回的结果,并可以abort系列函数来让系统丢弃该广播,使用该广播不再传送到别的BroadcastReceiver。
可以通过在intent-filter中设置android:priority属性来设置receiver的优先级,优先级相同的receiver其执行顺序不确定。
如果BroadcastReceiver是代码中注册的话,且其intent-filter拥有相同android:priority属性的话,先注册的将先收到广播。
有序广播,即从优先级别最高的广播接收器开始接收,接收完了如果没有丢弃,就下传给下一个次高优先级别的广播接收器进行处理,依次类推,直到最后。如果多个应用程序设置的优先级别相同,则谁先注册的广播,谁就可以优先接收到广播。
这里接收短信的广播是有序广播,因此可以设置你自己的广播接收器的级别高于系统原来的级别,就可以拦截短信,并且不存收件箱,也不会有来信提示音。
类似中央发送红头文件,一级一级往下发,各个单位按照优先级接受,省级->县级->乡镇
[1]发送广播
//点击按钮 发送有序广播 发大米 public void click(View v) { Intent intent = new Intent(); intent.setAction("com.xinwen"); /** * intent 意图 ' * * receiverPermission 接收的权限 * * resultReceiver 最终的receiver * * scheduler handler * * initialCode 初始码 * initialData 初始化数据 */ sendOrderedBroadcast(intent, null, new FinalReceiver(), null, 1, "习大大给每个村民发了1000斤大米", null); }
FinalReceiver广播主要代码:
@Override public void onReceive(Context context, Intent intent) { //[1]获取发送广播携带的数据 String content= getResultData(); //[2]显示结果 Toast.makeText(context, "报告习大大:"+content, 1).show(); }
[2]广播接收者
清单文件配置:priority配置的值越大,越优先进行
<receiver android:name="com.example.receiverxixi.ProviceReceiver" > <intent-filter android:priority="1000" ><action android:name="com.xinwen" />intent-filter>receiver><receiver android:name="com.example.receiverxixi.CityReceiver" ><intent-filter android:priority="100" ><action android:name="com.xinwen" />intent-filter>receiver><receiver android:name="com.example.receiverxixi.CountryReceiver" ><intent-filter android:priority="50" ><action android:name="com.xinwen" />intent-filter>receiver><receiver android:name="com.example.receiverxixi.NongMinReceiver" ><intent-filter android:priority="-100" ><action android:name="com.xinwen" />intent-filter>receiver>
ProviceReceiver主要实现代码:
@Overridepublic void onReceive(Context context, Intent intent) {// [1]获取发送广播携带的数据String content = getResultData();// [2]显示结果Toast.makeText(context, "省:" + content, 1).show();// [2.1]终止广播,省级以下的官员都不可以接收广播了,但FinalReceiver钦差大人可以可以接收abortBroadcast();// [3]修改数据,因为有了abortBroadcast,所以省级以下官员不可以接收广播了,但钦差大人可以接收这个修改的广播setResultData("习大大给每个村民发了500斤大");}
2.无序广播 (普通广播)
通过Context.sendBroadcast()方法来发送,它是完全异步的。
所有的receivers(接收器)的执行顺序不确定,因此所有的receivers(接收器)接收broadcast的顺序不确定。
这种方式效率更高,但是BroadcastReceiver无法使用setResult系列、getResult系列及abort(中止)系列API
比如新闻联播.每天晚上7:00开播(我不管你看还是不看)
[1]按钮发送广播
//点击按钮 发送一条无序广播 public void click(View v) {//创建一个意图 Intent intent = new Intent();//设置发送广播的动作,相当于发送了一条红包口令 intent.setAction("com.itheima.custom");//Intent意图里面携带了名字为"name"的数据 intent.putExtra("name", "新闻联播每天晚上 7点准时开整!!");//发送无序按钮 sendBroadcast(intent);//发送无序广播}
[2]接受无序广播的工程
在清单文件的配置:
<receiver android:name="com.itheima.receivewuxubroadcast.ReceiveCustomReceiver"><intent-filter ><action android:name="com.itheima.custom"/>intent-filter>receiver>
主要实现代码:
//当接收到我们发送的自定义广播@Overridepublic void onReceive(Context context, Intent intent) {//[1]获取发送广播携带的数据 String content = intent.getStringExtra("name");Toast.makeText(context, content, 1).show();}
有序广播对比无序广播:
[1]有序广播可以终止,有序广播数据可以被修改
[2]无序广播不可以被终止以及数据不可以被修改
特殊的广播接收者:
操作特别频繁的广播事件 比如:屏幕的锁屏和解锁,电池电量的变化 这种事件的广播在清单文件里注册无效的
注册广播接收者的两种方式:
You can either dynamically register an instance of this class with Context.registerReceiver() or statically publish an implementation through the tag in your AndroidManifest.xml.
[1]动态注册 通过代码方式注册
[2]在清单文件通过receiver tag节点静态发布的(这个办法对于特殊的广播接收者无效)
锁屏解锁的案例
1.在ScreenReceiver广播里面
//当我们进行屏幕锁屏和解锁 这个方法执行 @Overridepublic void onReceive(Context context, Intent intent) {//获取当前广播的事件类型 String action = intent.getAction();if("android.intent.action.SCREEN_OFF".equals(action)){System.out.println("屏幕锁屏了 ");}else if ("android.intent.action.SCREEN_ON".equals(action)) {System.out.println("屏幕解锁了"); }}
2.在MainActivity中的代码:
public class MainActivity extends Activity {private ScreenReceiver screenReceiver;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);//动态的去注册广播接收者 screenReceiver = new ScreenReceiver();//创建IntentFilter 对象IntentFilter filter = new IntentFilter();//添加要注册的actionfilter.addAction("android.intent.action.SCREEN_OFF");filter.addAction("android.intent.action.SCREEN_ON");//动态注册广播接收者registerReceiver(screenReceiver, filter); }@Overrideprotected void onDestroy() {//当activity 销毁的时候要取消注册广播接收者 unregisterReceiver(screenReceiver);super.onDestroy();}
发送接收数据需要注意的选项:
1.我们定义Intent的方法来发送数据,那么接受它的数据的时候也是通过Intent方法来获取的
2.不是通过Intent意图发数据的,那就不要通过Intent意图来接收数据!!!
Service 服务
进程
Android进程与线程基本知识 - Healtheon - 博客园
http://www.cnblogs.com/hanyonglu/archive/2012/04/12/2443262.html
概念:
[1]Android下四大组件都是运行在主线程中
[2]服务是在后台运行的 是没有界面的Activity(Activity 你大爷-> 服务)
进程优先级:
前台线程(Foreground process)>可见线程(Visible process)>服务线程(Service process)>后台线程(Background process)>空线程(Empty process)
Start方式开启服务的特点:
1.第一次点击服按钮开启服务 服务就会onCreate()方法和onStartCommand(Intent intent, int flags, int startId)
ps:服务中的onStartCommand跟Activity中的onStart方法只是名字不同,作用是一样的
2.第二次点击按钮 再次开启服务 服务执行onStartCommand方法
3.服务一旦被开启 服务就会后台长期运行 直到用户手动停止
StartService开启服务的例子:
// 点击按钮 开启服务 通过startservicepublic void click1(View v) {Intent intent = new Intent(this, DemoService.class);startService(intent); // 开启服务}// 点击按钮 关闭服务 通过stopServicepublic void click2(View v) {Intent intent = new Intent(this, DemoService.class);stopService(intent);}
bindService开启服务的特点:
1.第一次点击按钮 会执行服务的OnCreate方法和OnBind方法
2.当OnBind方法返回为null的时候,OnServiceConnected方法是不执行的
3.第二次点击按钮 服务没响应
4.不求同时生,但求同时死 指的是调用者(比如Activity)与服务之间的关系
5.服务不可以多次解绑,多次连续解绑会报异常
6.通过bind方法开启服务,服务不能在设置页面找到 相当于是一个隐形的服务
bindService开启服务的例子:
// 点击按钮 绑定服务 开启服务的第二种方式
public void click3(View v) {Intent intent = new Intent(this, DemoService.class);// 连接到DemoService 这个服务conn = new MyConn();bindService(intent, conn, BIND_AUTO_CREATE);}// 点击按钮手动解绑服务public void click4(View v) {unbindService(conn);}@Overrideprotected void onDestroy() {// 当Activity销毁的时候 要解绑服务unbindService(conn);super.onDestroy();}// 定义一个类 用来监视服务的状态private class MyConn implements ServiceConnection {// 当服务连接成功调用@Overridepublic void onServiceConnected(ComponentName name, IBinder service) {System.out.println("onServiceConnected"); }// 失去连接调用@Overridepublic void onServiceDisconnected(ComponentName name) {System.out.println("onServiceDisconnected"); }}
为什么要引入bindService?
是为了调用服务里面的方法
通过bindService方式调用服务里面方法的过程:
1.在服务的内部定义一个方法 让Activity去调用
// 办证的方法public void banZheng(int money) { if (money > 1000) { Toast.makeText(getApplicationContext(), "我是领导 把证给你办了", 1).show();} else { Toast.makeText(getApplicationContext(), "这点钱 还想办事....", 1).show(); }}
2.在服务的内容定义一个中间人对象(IBinder)
// [1]定义中间人对象(IBinder)public class MyBinder extends Binder {public void callBanZheng(int money) { // 调用办证的方法 banZheng(money); }}
3.把我定义的中间人对象在onBind()方法里面返回
// 把我定义的中间人对象返回@Overridepublic IBinder onBind(Intent intent) { return new MyBinder();}
4.在mainActivity的onCreate方法里面调用BindService的目的是为了获取我们定义的中间人对象
Intent intent = new Intent(this,BanZhengService.class);//连接服务 conn = new MyConn();//继承ServiceConnection接口重新的连接成功方法里面获取Service返回的IBinderbindService(intent, conn, BIND_AUTO_CREATE);
5.获取中间人对象
//监视服务的状态private class MyConn implements ServiceConnection{ //当服务连接成功调用 @Override public void onServiceConnected(ComponentName name, IBinder service) { //获取中间人对象 myBinder = (MyBinder) service; } //失去连接 @Override public void onServiceDisconnected(ComponentName name) { } }
6.拿到中间人的方法就可以调用服务里面的方法
myBinder.callBanZheng(10000000);//MyBinder myBinder=new MyBinder();
7.当Activity销毁的时候解绑服务
@Overrideprotected void onDestroy() { //当activity 销毁的时候 解绑服务 unbindService(conn);//MyConn conn=new MyConn(); super.onDestroy();}
通过接口方式调用服务里面的方法:
接口可以隐藏代码内部的细节 让程序员暴露自己只想暴露的方法
1.定义一个接口 把想暴露的方法都定义在接口里面
public interface Iservice { //把领导想暴露的方法都定义在接口里 public void callBanZheng(int money); public void callPlayMaJiang();}
2.我们定义的中间人对象实现我们自己定义的接口
// [1]定义中间人对象(IBinder)private class MyBinder extends Binder implements Iservice { public void callBanZheng(int money) { // 调用办证的方法 banZheng(money); } public void callPlayMaJiang() { // 调用playMaJiang 的方法 playMaJiang(); } public void callXiSangNa() { // 调用洗桑拿的方法 洗桑拿(); }}
3.在获取中间人对象的时候,需要把类型变为接口名称类型
将myBinder类型改为Iservice(也可以不修改,因为MyBinder继承了Iservice的接口)
StartService和bindService两者区别:
1.StartService服务一旦被开启 服务就会后台长期运行 直到用户手动停止
2.bindService不求同时生,但求同时死 指的是调用者(比如Activity)与服务之间的关系
补充:混合方式开启服务
Android里Service的bindService()和startService()混合使用深入分析 - 博客频道 - CSDN.NET
需求:既想让服务在后台长期运行又想调用服务里面的方法
1.先调用startService方法开启服务 保证服务能够在后台长期运行
2.调用bindService方法 去获取中间人对象
3.调用unbindService 解绑服务(但服务并没有被销毁)
4.调用stopService
aidl服务
本地服务:运行自己应用的服务
远程服务:运行在其他服务的服务,但跟javaweb中servlet没有半毛钱的关系
实现进程间通信 IPC
使用aidl的步骤:
下面是eclipse教程
1.把Iservice.Java文件变成一个aidl文件(修改后缀名即可)
2.aidl这个语言不认识public 把public去掉
3.自动生成一个Iservice.java文件(gen文件夹下),系统会自动帮我们生成一个Stub类,继承了Binder以及Iservice
4.我们自己定义的中间人对象直接继承Stub可即可
5.保证两个应用的aidl文件是同一个(保证aidl所在包的包名相同即可)
PS:图片中的1代表调用应用的项目,2代表被调用应用的项目,当aidl所在的包名相同的时候,系统就会以为他们共享一个文件
6.获取中间人的对象方式不同了
如何在AndroidStudio中使用AIDL - j275183662的博客 - 博客频道 - CSDN.NET
Content Provider 数据通信
作用:使用内容提供者 把私有的数据库内容给暴露出来
内容提供者把数据进行封装然后提供出来,其他应用都是通过内容解析者来访问
实现内容提供者步骤
1.定义一个类继承contentProvider
2.在清单文件里面配置内容提供者
<provider android:name="com.itheima.db.AccountProvider" android:authorities="com.itheima.provider" >provider>
3.写一个静态代码块 添加匹配规则
//[1]定义一个urimathcher 定义路径匹配器private static final UriMatcher sURIMatcher = new UriMatcher(UriMatcher.NO_MATCH);private static final int QUERYSUCESS = 0; //ctrl + shift + X private static final int INSERTSUCESS = 1;private static final int UPDATESUCESS = 2;private static final int DELETESUCESS = 3;private MyOpenHelper myOpenHelper; //[2] 定义静态代码块 添加匹配规则 static { /** * authority 注意 这个参数和你在清单文件里面定义的要一样 * * Url http://www.baidu.com * * uri content://com.itheima.provider/query * content://com.itheima.provider/insert * content://com.itheima.provider/update * content://com.itheima.provider/query */ sURIMatcher.addURI("com.itheima.provider", "query", QUERYSUCESS); sURIMatcher.addURI("com.itheima.provider", "insert", INSERTSUCESS); sURIMatcher.addURI("com.itheima.provider", "update", UPDATESUCESS); sURIMatcher.addURI("com.itheima.provider", "delete", DELETESUCESS); }
4.暴露你想暴露的方法(增删改查)
5.在日志logcat中出现这样一段日志 说明你的内容提供者 没有问题
- 其他应用就可以通过内容的解析者去操作数据库
// 第二种方式 读取数据库// 由于 第一个应用里面的私有数据库已经通过内容提供者给暴露出来了 所以可以直接通过内容的解析者进行访问// [1]拿到内容的解析者 直接通过上下文获取Uri uri = Uri.parse("content://com.itheima.provider/query"); // 路径和你定义的路径一样Cursor cursor = getContentResolver().query(uri, null, null, null, null);
短信备份小案例:
Android开发短信备份小例子 - ibey0nd的专栏 - 博客频道 - CSDN.NET
短信备份小案列代码:
// 点击按钮 查询短信数据内容 然后进行备份public void click(View v) { try { // [1]获取xml序列化实例 XmlSerializer serializer = Xml.newSerializer(); // [2]设置序列化参数 File file = new File(Environment.getExternalStorageDirectory().getPath(), "smsBackUp.xml"); FileOutputStream fos = new FileOutputStream(file); serializer.setOutput(fos, "utf-8"); // [3]开始写xml文档开头 serializer.startDocument("utf-8", true); // [4]开始写根节点 serializer.startTag(null, "smss"); // [5]由于短信数据库 系统也通过内容提供者给暴露出来了了 所以我们只需要通过内容解析者去操作数据库 Uri uri = Uri.parse("content://sms/"); Cursor cursor = getContentResolver().query(uri, new String[] { "address", "date", "body" }, null, null, null); while (cursor.moveToNext()) { String address = cursor.getString(0); String date = cursor.getString(1); String body = cursor.getString(2); // [6]写sms节点 serializer.startTag(null, "sms"); // [7]写address节点 serializer.startTag(null, "address"); serializer.text(address); serializer.endTag(null, "address"); // [8]写body节点 serializer.startTag(null, "body"); serializer.text(body); serializer.endTag(null, "body"); // [9]写date节点 serializer.startTag(null, "date"); serializer.text(date); serializer.endTag(null, "date"); serializer.endTag(null, "sms"); } serializer.endTag(null, "smss"); serializer.endDocument(); } catch (Exception e) { e.printStackTrace(); }}
读取联系人案例:
张萍老师的帖子 - 传智播客论坛_传智播客旗下社区 - Powered by Discuz!(79-83)
主要在微信 QQ,支付宝 等应用多一点
三张主要的表:
1.data 保存联系人的数据
[1].data data1 里面存的是所有联系人的信息
[2].data表里面的raw_contact_id,实际上是raw_contacts表的contact_id
[3].data表里面的mimetype_id列 实际对应mimetypes表
2.raw_contacts表 保存联系人的Id contact_id
3.mimetypes表 保存联系人数据的类型
实际步骤:
1.先读取raw_contact表 读取contact_id字段 从而我就知道实际里面一共有几条联系人
2.再读取data表 根据raw_contact_id去读取data1和mimetype(不是mimetype_id)
public void inquire(View v) { // 1.查询raw_contacts表,获取联系人的id ContentResolver resolver = getContentResolver(); Uri uri = Uri.parse("content://com.android.contacts/raw_contacts/"); Uri datauri = Uri.parse("content://com.android.contacts/data/"); Cursor cursor = resolver.query(uri, new String[] { "contact_id" }, null, null, null); while (cursor.moveToNext()) { String id = cursor.getString(0); // System.out.println("联系人的id:" + id); // 2.根据联系人的id,查询data表,把这个id的数据取出来 // 系统api 查询data表的时候 不是真正的查询data表 而是查询data表的视图了 // "mimetype_id"只是视图view_data里面的命名,不是mimetypes表里面的名字 Cursor dataCursor = resolver.query(datauri, new String[] { "data1", "mimetype" }, "raw_contact_id=?", new String[] { id }, null); while (dataCursor.moveToNext()) { String data1 = dataCursor.getString(0); // System.out.println("联系人的data1:" + data1); String mimetype = dataCursor.getString(1); if ("vnd.android.cursor.item/name".equals(mimetype)) { System.out.println("联系人的名字:" + data1); } else if ("vnd.android.cursor.item/phone_v2".equals(mimetype)) { System.out.println("联系人的手机号码:" + data1); } else if ("vnd.android.cursor.item/email_v2".equals(mimetype)) { System.out.println("联系人的邮箱:" + data1); } } }}
PS:在查询的过程中,我们应该查询mimetype这一列,而不是mimetype_id这一列,在查询的时候,我们查询的是view_data视图而不是data表
内容观察者:
84. 内容观察者的原理 - 资源分享 - 传智播客论坛_传智播客旗下社区 - Powered by Discuz!
内容观察者实现的步骤:
1.注册内容观察者(在增删改加入如下的代码可以实现)
getContext().getContentResolver().notifyChange(uri, null);//发送一条消息 说明数据库发生了改变
2.定义内容观察者
protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); // content://com.test.provider/ uri // 第二个参数true代表模糊匹配(content://com.test.provider/), // false代表精确匹配(如:content://com.test.provider/query) Uri uri = Uri.parse("content://com.test.provider/");//uri在拥有私有数据库的地方注册的authority里面规定 //Uri uri = Uri.parse("content://com.itheima.provider"); getContentResolver().registerContentObserver(uri, true, new MyObserver(new Handler()));}//定义内容观察者private class MyObserver extends ContentObserver { public MyObserver(Handler handler) { super(handler); // TODO Auto-generated constructor stub } // 当info表中的数据发生变化时则执行该方法 @Override public void onChange(boolean selfChange) { // TODO Auto-generated method stub System.out.println("数据已经发生改变了~~~~"); super.onChange(selfChange); }}
多媒体
PS:Android多媒体指的是:文字、图片、音频、视频
图片:
图片大小的计算公式:图片的总像素 * 每个像素大小
PS:Android中采用的是png格式 Android中采用ARGB Android中一个像素占4byte
缩放需要加载的大图片:
12-03 08:46:01.819: E/AndroidRuntime(3117): Caused by: java.lang.OutOfMemoryError(内存泄漏)
1.获取图片的分辨率
2.获取手机的分辨率 320*480
在onCreate()方法中实现以下方法:
// 1.获得手机的分辨率,偏向于JavaWindowManager wm = (WindowManager) getSystemService(WINDOW_SERVICE);int winHeight = wm.getDefaultDisplay().getHeight();int winWidth = wm.getDefaultDisplay().getWidth();//1.1获得手机分辨率的新式写法,偏向于c语言Point point = new Point();WindowManager windowManager = (WindowManager) getSystemService(WINDOW_SERVICE);wm.getDefaultDisplay().getSize(point);int Width = point.x;int Hight = point.y;
3.计算缩放比
4.按照大的去缩放
// 点击按钮 加载一张大图片@SuppressLint("SdCardPath")public void click(View v) { // 创建一个位图工厂的配置参数 BitmapFactory.Options options = new Options(); // 解码器不去真正的解析位图 但是还能够获取图片的宽和高信息 options.inJustDecodeBounds = true; BitmapFactory.decodeFile("/mnt/sdcard/cc.jpg", options); // [2]获取图片的宽和高信息 int imgWidth = options.outWidth; int imgHeight = options.outHeight; System.out.println("图片的宽和高:" + imgWidth + "----" + imgHeight); // [3]计算缩放缩放比 int scale = 1; int scaleX = imgWidth / width; int scaleY = imgHeight / height; if (scaleX >= scaleY && scaleX > scale) { scale = scaleX; } if (scaleY > scaleX && scaleY > scale) { scale = scaleY; } System.out.println("缩放比==:" + scale); // [4]安装缩放比进行显示 生物学 丁磊 options.inSampleSize = scale; // [5]安装缩放比 进行解析位图 options.inJustDecodeBounds = false; Bitmap bitmap = BitmapFactory.decodeFile("/mnt/sdcard/cc.jpg", options); // [6]把bitmap显示iv上 iv.setImageBitmap(bitmap);}
个人理解:先获取图片的像素大小跟手机分辨率进行对比,接着再加载图片
复制图片的原理:
//显示原图 ImageView iv_src = (ImageView) findViewById(R.id.iv_src); //显示副本 ImageView iv_copy = (ImageView) findViewById(R.id.iv_copy);//[1]先把tomcat.png 图片转换成bitmap 显示到iv_srcBitmap srcBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.tomcat);//[1.1]操作图片 //srcBitmap.setPixel(20, 30, Color.RED);iv_src.setImageBitmap(srcBitmap);//[2]创建原图的副本 //[2.1]创建一个模板 相当于 创建了一个大小和原图一样的 空白的白纸 Bitmap copybiBitmap = Bitmap.createBitmap(srcBitmap.getWidth(), srcBitmap.getHeight(), srcBitmap.getConfig());//[2.2]想作画需要一个画笔 Paint paint = new Paint();//[2.3]创建一个画布 把白纸铺到画布上 Canvas canvas = new Canvas(copybiBitmap);//[2.4]开始作画 canvas.drawBitmap(srcBitmap, new Matrix(), paint);//[3]把copybimap显示到iv_copy上iv_copy.setImageBitmap(copybiBitmap);
图片处理的api:
Matrix matrix = new Matrix();
1.对图片进行旋转
//[2.5]对图片进行旋转 matrix.setRotate(20, srcBitmap.getWidth()/2, srcBitmap.getHeight()/2);
2.对图片进行缩放
//[2.5]对图片进行缩放matrix.setScale(0.5f, 0.5f);
3.对图片进行平移
//[2.6]对图片进行平移matrix.setTranslate(30, 0);
4.镜面(缩放和平移的组合)
//[2.7]镜面效果 如果2个方法一起用 matrix.setScale(-1.0f, 1);//post是在上一次修改的基础上进行再次修改 set 每次操作都是最新的 会覆盖上次的操作matrix.postTranslate(srcBitmap.getWidth(), 0);
5.倒影()
//[2,7]倒影效果matrix.setScale(1.0f, -1);//post是在上一次修改的基础上进行再次修改 set 每次操作都是最新的 会覆盖上次的操作matrix.postTranslate(0, srcBitmap.getHeight());canvas.drawBitmap(srcBitmap,matrix , paint);
音频:
使用mediaplayer播放音频以及视频(只能播放mp4或者3gp格式)
同步:一般播放本地音乐
异步:播放网络音乐 不用开子线程
Surfaceview控件:
SurfaceView 基础用法 - 分享美好的专栏 - 博客频道 - CSDN.NET
1.播放视频
2.重量级控件(在子线程开启好一点),建议可以睡眠几秒
3.它可以直接在子线程更新ui
4.内部的两个线程会相互交换工作:
VideoView控件介绍:
[Android基础] VideoView - 简书
VideoView是对surfaceView和mediaplayer的封装
vitamio框架:
Android视频框架 Vitamio 打造自己的万能播放器 - 郭朝的博客 - 博客频道 - CSDN.NET
解码原理:使用的是一个开源项目 ffmpeg
使用步骤:
1.引入vitamio框架到 Android Studio 或者 Eclipse
2.在布局中定义VideoView
.vov.vitamio.widget.VideoView android:id="@+id/vv" android:layout_width="match_parent" android:layout_height="match_parent" />
3.MainActivity中调用
// 插件vitamio框架检查是否可用if (!LibsChecker.checkVitamioLibs(this)) { return;}final VideoView vv = (VideoView) findViewById(R.id.vv);vv.setVideoPath("http://192.168.1.20:8080/movie.mp4"); //设置播放路径vv.setOnPreparedListener(new OnPreparedListener() { @Override public void onPrepared(MediaPlayer mp) { vv.start(); }});// 设置video的控制器vv.setMediaController(new MediaController(this));
4.使用Vitamio一定要在清单文件额外初始化InitActivity
<activity android:name="io.vov.vitamio.activity.InitActivity">activity>
PS:注意:InitActivity不要和MainActivity混淆。
照相和录像:
Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);//照相//Intent intent = new Intent(MediaStore.ACTION_VIDEO_CAPTURE);//录像File file = new File(Environment.getExternalStorageDirectory().getPath(),"haha.png");intent.putExtra(MediaStore.EXTRA_OUTPUT, Uri.fromFile(file)); // 保存图片的位置 // start the image capture IntentstartActivityForResult(intent, 1);
Android琐屑知识:
样式和主题:
一般在res文件夹的values文件夹下的styles.xml定义,也可以在values文件夹下新建一个xml来定义
<style name="my_style"><item name="android:layout_width">wrap_contentitem><item name="android:layout_height">wrap_contentitem><item name="android:textSize">40spitem><item name="android:textColor">#000000item>style><style name="my_theme"><item name="android:background">#ff0000item>style>
Andriod国际化
公司俗称”l18n”,其实就是语言的问题,应用需要多种语言
常见对话框
[1]Toast
[2]普通对话框 单选对话框 多选对话框 进度条对话框
普通对话框:
// 点击按钮 弹出一个普通对话框public void click1(View v) {// 通过builder 构建器来构造AlertDialog.Builder builder = new Builder(this);builder.setTitle("警告");builder.setMessage("世界上最遥远的距离是没有网络 ");builder.setPositiveButton("确定", new OnClickListener() {@Overridepublic void onClick(DialogInterface dialog, int which) { System.out.println("点击了确定按钮"); }});builder.setNegativeButton("取消", new OnClickListener() {@Overridepublic void onClick(DialogInterface dialog, int which) { System.out.println("点击了取消按钮 "); }});// 最后一步 一定要记得 和Toast 一样 show出来builder.show();}
单选对话框:
// 点击按钮 弹出一个单选对话框public void click2(View v) {// 通过builder 构建器来构造AlertDialog.Builder builder = new Builder(this);builder.setTitle("请选择您喜欢的课程");final String items[] = { "Android", "ios", "c", "C++", "html", "C#" };// -1代表没有条目被选中,0代表默认选中了Android,1代表选中了iosbuilder.setSingleChoiceItems(items, -1, new OnClickListener() {@Overridepublic void onClick(DialogInterface dialog, int which) {// [1]把选择的条目给取出来String item = items[which];Toast.makeText(getApplicationContext(), item, 1).show();// [2]把对话框关闭dialog.dismiss(); }});// 最后一步 一定要记得 和Toast 一样 show出来builder.show();}
复选对话框:
// 点击按钮 弹出一个对选对话框public void click3(View v) {// 通过builder 构建器来构造AlertDialog.Builder builder = new Builder(this);builder.setTitle("请选择您喜欢吃的水果");final String items[] = { "香蕉", "黄瓜", "哈密瓜", "西瓜", "梨", "柚子", "榴莲" };final boolean[] checkedItems = { true, false, false, false, false, false, true };builder.setMultiChoiceItems(items, checkedItems, new OnMultiChoiceClickListener() {@Overridepublic void onClick(DialogInterface dialog, int which, boolean isChecked) { }});builder.setPositiveButton("确定", new OnClickListener() {@Overridepublic void onClick(DialogInterface dialog, int which) {// 把选中的 条目的数据给我取出来StringBuffer sb = new StringBuffer();for (int i = 0; i < checkedItems.length; i++) {// 判断一下 选中的if (checkedItems[i]) {String fruit = items[i];sb.append(fruit + " "); }}Toast.makeText(getApplicationContext(), sb.toString(), 1).show();// 关闭对话框dialog.dismiss(); }});// 最后一步 一定要记得 和Toast 一样 show出来builder.show();}
进度条对话框:
PS:与进度相关的控件 都可以在子线程更新UI
// 点击按钮 弹出一个进度条对话框public void click4(View v) {final ProgressDialog dialog = new ProgressDialog(this);dialog.setTitle("正在玩命加载ing");// 设置一下进度条的样式dialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);// 最后一步一定要记得show 出来dialog.show();// 创建一个子线程new Thread() {public void run() {// 设置进度条最大值dialog.setMax(100);// 设置当前进度for (int i = 0; i <= 100; i++) {dialog.setProgress(i);//显示当前的进度// 睡眠一会SystemClock.sleep(50);//谷歌工程师搞出来的//Thread.sleep(50);//Java中定义的 }// 关闭对话框dialog.dismiss(); };}.start();}
Android中动画:
1.帧动画(Drawable animation ):加载一系列的图片资源
例子详见安卓开发文档
Drawable animation lets you load a series of Drawable resources one after another to create an animation. (帧动画容许你加载图片文件夹中的图片按顺序显示来创建一个动画)
稍微注意一下:
由于AnimationDrawable是在4.0才有的,所以4.0以下的版本认为这个是耗时的操作,所以不会显示动态效果.解决办法:
[1].新开一个子线程
@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);// [1]找到控件 显示动画效果final ImageView rocketImage = (ImageView) findViewById(R.id.iv);// [2]设置背景资源,加载图片rocketImage.setBackgroundResource(R.drawable.my_anim);//my_anim是xml文件名字,所有需要加载的图片资源都应当在这里配置// [2.1]兼容低版本的写法new Thread() {public void run() {// 这个睡眠是首次启动的时候有效的,可有可无SystemClock.sleep(20);// [3] 获取AnimationDrawable 类型AnimationDrawable rocketAnimation = (AnimationDrawable) rocketImage.getBackground();// [4]开启动画rocketAnimation.start(); };}.start();}
[2].像安卓开发离线文档中的例子来写(轻触一下才可以显示效果)
@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);ImageView rocketImage = (ImageView) findViewById(R.id.iv);rocketImage.setBackgroundResource(R.drawable.my_anim);//my_anim是xml文件名字,所有需要加载的图片资源都应当在这里配置rocketAnimation = (AnimationDrawable) rocketImage.getBackground();}// 轻触一下才会显示动态效果public boolean onTouchEvent(MotionEvent event) {if (event.getAction() == MotionEvent.ACTION_DOWN) {rocketAnimation.start();return true; }return super.onTouchEvent(event);}
2.View动画
也叫补间动画
[1]透明 AlphaAnim
[2]旋转 rotateAnim
[3]缩放 scaleAnim
[4]位移 translateAnim
原理:动画效果不会改变控件真实的坐标
// 点击按钮 实现透明效果public void click1(View v) { //创建透明动画 1.0意味着完全不透明 0.0意外者完全透明 AlphaAnimation aa = new AlphaAnimation(1.0f, 0.0f); aa.setDuration(2000); //设置动画执行的时间 aa.setRepeatCount(1); //设置动画重复的次数 aa.setRepeatMode(Animation.REVERSE); //设置重复的模式.默认为Animation.RESTART //开始执行动画 iv.startAnimation(aa);}// 点击按钮 实现旋转效果public void click2(View v) { //fromDegrees 开始角度 toDegrees 结束的角度// RotateAnimation ra = new RotateAnimation(0, 360); RotateAnimation ra = new RotateAnimation(0, 360, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f); ra.setDuration(2000); //设置动画执行的时间 ra.setRepeatCount(1); //设置动画重复的次数 ra.setRepeatMode(Animation.REVERSE); //设置重复的模式 //开始执行动画 iv.startAnimation(ra);}// 点击按钮 实现缩放效果public void click3(View v) { ScaleAnimation sa = new ScaleAnimation(1.0f, 2.0f, 1.0f, 2.0f, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f); sa.setDuration(2000); //设置动画执行的时间 sa.setRepeatCount(1); //设置动画重复的次数 sa.setRepeatMode(Animation.REVERSE); //设置重复的模式 //开始执行动画 iv.startAnimation(sa);}// 点击按钮 实现平移效果public void click4(View v) { TranslateAnimation ta = new TranslateAnimation(Animation.RELATIVE_TO_PARENT, 0, Animation.RELATIVE_TO_PARENT, 0, Animation.RELATIVE_TO_PARENT, 0, Animation.RELATIVE_TO_PARENT, 0.2f); ta.setDuration(2000); //设置动画执行的时间 ta.setFillAfter(true);//当动画结束后 停留在结束的位置上 //开始执行动画 iv.startAnimation(ta);}
使用xml方式定义补间动画
[1] 就是在res下创建一个目录
[2] 在该目录下创建对应的动画即可
3.属性动画
原理:会改变控件的真实坐标
两种上下文的区别
[1]this 最终继承Context 理解为子类
[2]getApplicationContext() 返回的对象为Context对象 理解为父类
[3]对话框只能用this
选项卡的使用
注意:这种方法已经被废弃,现在使用Fragment居多一点
1.在res/layout增加一个布局.名为activity_base_clear_cache.xml
<?xml version="1.0" encoding="utf-8"?><TabHost xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:id="@android:id/tabhost"> <FrameLayout android:id="@android:id/tabcontent" android:layout_marginBottom="50dp" android:layout_width="match_parent" android:layout_height="match_parent" > FrameLayout> <TabWidget android:id="@android:id/tabs" android:layout_gravity="bottom" android:layout_width="match_parent" android:layout_height="50dp" > TabWidget>TabHost>
2.定义一个BaseCacheClearActivity继承TabActivity,接着加载步骤1的xml文件作为布局文件
public class BaseCacheClearActivity extends TabActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_base_clear_cache); //1.生成选项卡1 TabSpec tab1 = getTabHost().newTabSpec("clear_cache").setIndicator("缓存清理");//这里可以加载一个view,也可简单加载字符串// ImageView imageView = new ImageView(this); // imageView.setBackgroundResource(R.drawable.ic_launcher);// View view = View.inflate(this, R.layout.test, null);// TabSpec tab1 = getTabHost().newTabSpec("clear_cache").setIndicator(view); //2.生成选项卡2 TabSpec tab2 = getTabHost().newTabSpec("sd_cache_clear").setIndicator("sd卡清理");//这里可以加载一个view,也可简单加载字符串 //3.告知点中选项卡后续操作 tab1.setContent(new Intent(this,CacheClearActivity.class));//CacheClearActivity.class为跳转的界面 tab2.setContent(new Intent(this,SDCacheClearActivity.class));//SDCacheClearActivity.class为跳转的界面 //4.将此两个选项卡维护host(选项卡宿主)中去 getTabHost().addTab(tab1); getTabHost().addTab(tab2); }}
流量监听
//获取手机下载流量 //获取流量(R 手机(2G,3G,4G)下载流量) long mobileRxBytes = TrafficStats.getMobileRxBytes(); //获取手机的总流量(上传+下载) //T total(手机(2G,3G,4G)总流量(上传+下载)) long mobileTxBytes = TrafficStats.getMobileTxBytes(); //total(下载流量总和(手机+wifi)) long totalRxBytes = TrafficStats.getTotalRxBytes(); //(总流量(手机+wifi),(上传+下载)) long totalTxBytes = TrafficStats.getTotalTxBytes(); //以上方法意义不大,一般都是发送短信给运营商查询为准 //流量获取模块(发送短信),运营商(联通,移动....),(流量监听)第三方接口,广告 //短信注册
抽屉控件SlidingDrawer 的使用
1.在res/layout的文件夹下定义一个xml布局文件,到时在activity文件加载即可
<SlidingDrawer android:handle="@+id/handler" android:content="@+id/content" android:orientation="vertical" android:layout_width="match_parent" android:layout_height="match_parent"> <ImageView android:id="@id/handler" android:background="@drawable/ic_launcher" android:layout_width="wrap_content" android:layout_height="wrap_content"/> <TextView android:id="@id/content" android:background="#f00" android:layout_width="match_parent" android:layout_height="match_parent"/> SlidingDrawer>
Application全局捕获异常的使用
1.需要在清单文件的application的标签的name配置一下
2.接着使用一个继承Application来实现
public class MyApplication extends Application { protected static final String tag = "MyApplication"; @Override public void onCreate() { super.onCreate(); //捕获全局(应用任意模块)异常 Thread.setDefaultUncaughtExceptionHandler(new UncaughtExceptionHandler() { @Override public void uncaughtException(Thread thread, Throwable ex) { //在获取到了未捕获的异常后,处理的方法 ex.printStackTrace(); Log.i(tag, "捕获到了一个程序的异常"); //将捕获的异常存储到sd卡中 String path = Environment.getExternalStorageDirectory().getAbsoluteFile()+File.separator+"error74.log"; File file = new File(path); try { PrintWriter printWriter = new PrintWriter(file); ex.printStackTrace(printWriter); printWriter.close(); } catch (FileNotFoundException e) { e.printStackTrace(); } //上传公司的服务器 //结束应用,发生异常的应用并不会弹出一个恶心的错误对话框 System.exit(0); } }); }}
Application获得全文的上下文方法
1.需要在清单文件的application的标签的name配置一下
2.继承Application实现其方法:
public class MyApplication extends Application { private static Context mContext; @Override public void onCreate() { mContext = getApplicationContext(); } public static Context getContext() { return mContext; }}
新特性
Fragments
在实际开发中必须要重写一个方法 onCreateView方法
还可以重写onDestroy方法(有些函数可能需要销毁的时候)
第一种方式(不常用):
[1] 通过onCreateView 这个方法Fragment可以加载自己的布局
[2] name属性指定的是一个我们自己定义的Fragment
[3]直接在布局中声明即可
第二种:
[1]获取Fragment的管理者
FragmentManager fragmentManager = getFragmentManager();//获取Fragment的管理者 通过上下文直接获取
[2]开启一个事物
FragmentTransaction beginTransaction = fragmentManager.beginTransaction(); //开启事物
[3]提交事物
beginTransaction.commit();//最后一步 记得comment
兼容低版本的写法:就是把所有的Fragment全部使用V4包中的fragment(import导包,v4的fragActivity等)
menu菜单:
[1] 声明菜单
getMenuInflater().inflate(R.menu.main, menu);//菜单item在main.xml那里声明(在menu文件夹下)
[2]动态去添加菜单(代码的方式)
//[2]通过代码的方式添加 /** * menu.add(groupId, itemId, order, title); * groupId 分组 * itemId 给组件起一个id,方便找到它 * order 排序,数字小的在上面 * title 代表是item的名字 */menu.add(0, 1, 0, "前进");menu.add(0, 2, 0, "后退");menu.add(0, 3, 0, "首页");
menu自定义小技巧:
自定义menu键的时候弹出警告对话框,哪个Activity需要就重写,不重写的Activity是使用系统默认的!!!
//当菜单打开之前调用这个方法 @Overridepublic boolean onMenuOpened(int featureId, Menu menu) { //弹出 一个对话框 AlertDialog.Builder builder = new Builder(this); builder.setTitle("警告"); builder.setPositiveButton("确定", new OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { } }); builder.setNegativeButton("取消", new OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { } }); //最后一步 记得 show出来 和Toast一样 builder.show(); return false;}
AutoCompleteTextView控件的介绍:
显示数据的原理跟listview一样 也需要数据适配器(参考官网文档的实例代码即可)
public class CountriesActivity extends Activity { //定义数据 private String[] COUNTRIES = new String[] { "Belgium", "France", "Italy", "Germany", "Spain" }; protected void onCreate(Bundle icicle) { super.onCreate(icicle); setContentView(R.layout.countries); //android.R.layout.simple_dropdown_item_1line其实就是一个系统已经定义好的textview,COUNTRIES是自定义的数组 ArrayAdapter adapter = new ArrayAdapter(this,android.R.layout.simple_dropdown_item_1line, COUNTRIES); //找到我们需要的组件 AutoCompleteTextView textView = (AutoCompleteTextView) findViewById(R.id.countries_list); //显示数据 textView.setAdapter(adapter); } }
应用程序的反编译:
Android逆向助手
安卓自定义控件
Android自身带的控件不能满足需求, 需要根据自己的需求定义控件.
Src跟Background区别
<ImageView android:src="#000" android:background="@null" android:layout_width="wrap_content" android:layout_height="wrap_content"/>
在自定义ViewGroup的时候,如果对子View的测量没有特殊的需求,那么可以继承系统已有的
布局(比如FrameLayout),目的是为了让已有的布局帮我们实行onMeasure;
自定义控件可以分为三大类型
1. 组合已有的控件实现
[1] Button或ImageButton等自带按钮功能的控件会抢夺所在Layout的焦点.导致其他区域点击不生效.在所在layout声明一个属性
android:descendantFocusability="blocksDescendants"
blocksDescendants : ViewGroup(父控件)会覆盖子类控件而直接获得焦点
beforeDescendants : ViewGroup(父控件)会优先其子类控件而获取到焦点
afterDescendants : ViewGroup(父控件)只有当其子类控件不需要获取焦点时才获取焦点
[2] popupwindow获取焦点, 外部可点击
// 设置点击外部区域, 自动隐藏popupWindow.setOutsideTouchable(true); // 外部可触摸popupWindow.setBackgroundDrawable(new BitmapDrawable()); // 设置空的背景, 响应点击事件 popupWindow.setFocusable(true); //设置可获取焦点
2. 完全自定义控件.(继承View, ViewGroup)
定义继承view的三个构造方法
/** * 自定义开关 * @author poplar * * Android 的界面绘制流程 * 测量 摆放 绘制 * measure -> layout -> draw * | | | * onMeasure -> onLayout -> onDraw 重写这些方法, 实现自定义控件 * * onResume()之后执行 * * View * onMeasure() (在这个方法里指定自己的宽高) -> onDraw() (绘制自己的内容) * * ViewGroup * onMeasure() (指定自己的宽高, 所有子View的宽高)-> onLayout() (摆放所有子View) -> onDraw() (绘制内容) */ /** * 用于代码创建控件 * @param context */ public ToggleView(Context context) { super(context); } /** * 用于在xml里使用, 可指定自定义属性 * @param context * @param attrs */ public ToggleView(Context context, AttributeSet attrs) { super(context, attrs); } /** * 用于在xml里使用, 可指定自定义属性, 如果指定了样式, 则走此构造函数 * @param context * @param attrs * @param defStyleAttr */ public ToggleView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); }
一.View移动的相关方法总结:
1.通过改变view在父View的layout位置来移动,但是只能移动指定的View:
view.layout(l,t,r,b);
view.offsetLeftAndRight(offset);//同时改变left和right
view.offsetTopAndBottom(offset);//同时改变top和bottom
2.通过改变scrollX和scrollY来移动,但是可以移动所有的子View;
scrollTo(x,y);
scrollBy(xOffset,yOffset);
3.通过改变Canvas绘制的位置来移动View的内容:
canvas.drawBitmap(bitmap, left, top, paint)
二.使用ViewDragHelper来处理移动
1.ViewDragHelper在高版本的v4包(android4.4以上的v4)中
2.它主要用于处理ViewGroup中对子View的拖拽处理
3.它是Google在2013年开发者大会提出的
4.它主要封装了对View的触摸位置,触摸速度,移动距离等的检测和Scroller,通过接口回调的
方式告诉我们;只需要我们指定是否需要移动,移动多少等;
5.本质是对触摸事件的解析类;
三.getHeight和getMeasuredHeight的区别:
getMeasuredHeight:只要view执行完onMeasure方法就能够获取到值;
getHeight:只有view执行完layout才能获取到值;
安卓开发小Tips:
1.不管你是什么版本的手机,只要进行耗时的操作,比如联网,拷贝大的数据,都默认开多一个线程。获取数据后想要更新UI,那就使用 Handler就可以了。
2.关于xml的数据,是服务器开发人员通过一定的技术手段返回的,对应的Android开发人员我们要解析回就ok,把我们关心的数据取出来,展示Android控件上
3.接口(interface)不可以直接new,但谷歌实现该接口子类都是用base simple default来命名的
4.与进度有关的都可以在子线程更新ui
5.方法上面有这个@Override标志,代表了继承了父类的方法
6.Activity跳转到另外一个Activity,不需要添加flag,但service跳转到Activity的时候,就需要添加flag
更多相关文章
- 浅谈Java中Collections.sort对List排序的两种方法
- mybatisplus的坑 insert标签insert into select无参数问题的解决
- python起点网月票榜字体反爬案例
- Python list sort方法的具体使用
- python list.sort()根据多个关键字排序的方法实现
- android上一些方法的区别和用法的注意事项
- 《Android开发从零开始》——25.数据存储(4)
- android实现字体闪烁动画的方法
- Android系统配置数据库注释(settings.db)