基础 工具 adb 命令
adb kill-server 杀死模拟器
adb start-server 启动模拟器
cd desktop 进入桌面文件夹
adb install path/x.apk 安装应用
adb uninstall com.xxx 卸载应用
adb shell 进入linux指令(ctrl+C退出)
# ls 列出目录下所有文件(夹)清单
adb pull a.txt 从手机中导出文件
adb push a.txt /mnt/sdcard 把一个文件导入到手机
monkey 1000 随机点模拟器1000次(冒烟测试(压力测试))
简易拨号应用 设置按钮点击事件的四种方式 1、内部类 使用 onClickListener() 类
1 2 3 4 5 6 7 8 9 10 btn = (Button) findViewById(R.id.button1); btn.setOnClickListener(new MyClickListener()); private class MyClickListener implements OnClickListener { @Override public void onClick (View v) { System.out.println("按钮被点击了" ); } }
2、匿名内部类 1 2 3 4 5 6 btn.setOnClickListener(new OnClickListener(){ @Override public void onClick (View v) { } });
3、setOnClickListener(this ) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 public class MainActivity extends Activity implements onClickListener { public void onCreate () { btn.setOnClickListener(this ); } @Override public void onClick (View v) { switch (v.getID()){ case R.id.btn1 : break ; case R.id.btn2 : break ; } } }
4、布局中的 onClick 在 AndroidManiFest 中设置 attribute(属性)
1 2 3 4 5 6 7 <Button android:onClick="myClick" /> public void myClick (View v) { }
获取 EdiText 文本内容 1 2 3 4 5 ed = (EditText) findViewById(R.id.editText1); String str = ed.getText().toString().trim(); if (str.equals("" )){ Toast.makeText(MainActivity.this , "文本不能为空" , 1 ).show(); }
判断文本是否为空的API:TextUtils.isEmpty(str)
吐司 Toast 1 2 Toast.makeText(context, text, duration).show();
注意:如果是在自定义的类例如 MyClickListener 中,context 不能是 this
context 可以改成:MainActivity.this
duration 弹出时长,参数有:
最后别忘了弹出吐司: .show()
意图 Intent 1 2 3 4 Intent intent = new Intent(); intent.setAction(Intent.ACTION_CALL); intent.setData(Uri.parse("tel:" + num)); startActivity(intent);
布局 1、线性布局 1 2 3 4 5 6 android:orientation="horizontal" // 水平的(默认) android:orientation="vertitcal" // 竖直的 android:layout_width="match_parent" // 填充父 android:layout_height="wrap_content" // 自适应 android:paddingBottom="@dimen/activity_vertical_margin" // 控件内部内容(文字)位置 android:marginLeft="10dp" // 控件整体左边留空 10 像素
1 2 3 4 5 6 7 8 9 <LinearLaout xmlns:android ="http://schemas.android.com/apk/res/android" xmlns:toos ="http://schemas.android.com/tools" android:layout_width ="match_parent" android:layout_height ="match_parent" android:orientation ="vertical" tools:context =".MainActivity" > </LinearLaout >
2、相对布局 控件默认左上角,需手动确定相对关系
1 2 3 4 5 android:id="@+id/btn2" // 添加一个 ID android:layout_below="@id/btn1" // 在 btn1 的下面(即当前控件的的上面) android:layout_toRightOf="@id/btn3" // 在 btn3 的右边 android:layout_alignParentRgiht="true" // 在布局的最右边 android:layout_alignBottom="@id/bt4" // 与 bt4 底部对齐
1 2 3 <RelativeLayout xmlns ="" > </RelativeLayout >
3、帧布局 一层一层显示,后面的Frame在前面的Frame上面(默认窗口左上角)
1 android:layout_gravity="center"
center 居中
center_vertical 垂直居中
center_horizontal 水平居中
4、表格布局 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 <TableLayout android:layout_width ="match_parent" android:layout_height ="match_parent" <!--代表一行--> <TableRow android:layout_width ="match_parent" android:layout_height ="wrap_content" > <TextView android:layout_width ="wrap_content" android:layout_height ="wrap_content" android:text ="所有控件开头都是大写的" android:textColoe ="#ff0000" /> <TextView android:layout_width ="wrap_content" android:layout_height ="wrap_content" android:layout_marginLeft ="10dp" android:text ="这是第二列" android:textSize ="18sp" /> </TableRow > <TableRow > </TableRow > <TableRow > </TableRow > </TableLayout >
5、绝对布局
absolution 已经废弃了
权重 只能在线性布局中使用,平分布局。
1 2 3 4 <LinearLayout > <TextView android:layout_weight ="2" /> <TextView android:layout_weight ="1" /> </LinearLayout >
单位
px 像素(不适配屏幕,不建议使用)
dp 自动适应屏幕的单位(px的替代单位)
sp 控件文字大小 textSize
调试 单元测试
定义一个类继承 AndroidTestCase
1 2 3 4 5 6 7 8 9 10 11 12 import android.test.AndroidTestCasepublic class CalcTest extends AndroidTestCase { public void testAdd () { Calc calc = new Calc(); int result = calc.add(5 , 3 ); assertEquals(8 , result); } }
在清单文件(AndroidManifest) 中加入 uses-library 和 instrumentation :
1 2 3 4 5 6 7 8 9 10 11 12 13 <manifest xmlns:android... > <application android:icon ="@drawable/icon" ... > <uses-library android:name ="android.test.runner" /> ... </application > <uses-sdk android:minSdkVersion ="6" /> <instrumentation android:name ="android.test.InstrumentationTestRunner" android:targetPackage ="应用包名" android:lable ="标题(可省)" /> </manifest >
\<application android:icon="@drawable/icon"...>
***\<uses-library android:name="android.test.runner" />***
\</application>
***<instrumentation android:name="android.test.InstrumentationTestRunner"***
***android:targetPackage="cn.itcast.action" android:lable="Tests for My APP" />***
Run As : Android JUnit Test
加入误相减,console 会出现:junit.framework.AssertionFailedError: expected:<8> but was <2>
可根据不同的错误级别来显示
日志猫 1 2 3 4 5 Log.v(tag, msg); Log.i(tag, msg); Log.d(tag, msg); Log.w(tag, msg); Log.e(tag, msg);
文件存储操作 过程:File -> FileStream -> write / read & read_buffer -> close
向SD卡写数据需要存储权限 AndroidManiFest 增加权限:android.permission.WRITE_EXTERNAL_STORAGE
txt读写例程:绝对路径 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 import java.io.FileNotFoundException;import java.io.FileOutputStream;import java.io.FileInputStream;public class UserInfoUtiles { public static boolean saveInfo (String username, String password) { try { String result = username + "&" + password; File file = new File("/data/data/包名/info.txt" ); FileOutputStream fos = new FileOutputStream(file); fos.write(result.getBytes()); fos.close(); return true ; } catch (Exception e) { e.printStackTrace(); return false ; } } public static Map<String, String> readInfo () { try { Map<String, String> maps = new HashMap<String, String>(); File file = new File("/data/data/包名/info.txt" ); FileInputStream fis = new FileInputStream(file); BufferedReader bufr = new BufferedReader(new InputStreamReader(fis)); String content = bufr.readLine(); String[] splits = content.split("&" ); String name = splits[0 ]; String pwd = splits[1 ]; maps.put("username" , name); maps.put("password" , pwd); fis.close(); return maps; } catch (Exception e) { e.printStackTrace(); } return null ; } } if (UserInfoUtils.saveInfo(username, password)){ ... } Map<String, String> maps = UserInfoUtils.readInfo(); if (maps != null ){ String name = maps.get("username" ); String pwd = maps.get("password" ); ... }
API 获取内部路径 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 public static boolean saveInfo (Context context, String username, String password) { String path = context.getFilesDir().getpath(); File file = new File(path, "info.txt" ); FileOutputStream fos = new FileOutputStream(file); fos.write(result.getBytes()); fos.close(); } UserInfoUtils.saveInfo(MainActivity.this , username, password); UserInfoUtils.readInfo(MainActivity.this );
context 获取内部路径 1 2 3 4 5 FileOutputStream fos = context.openFileOutput("info,txt" , 0 ); fos.write(result.getBytes()); fos.close();
Environment 获取SD卡路径 1 2 3 String sdPath = Environment.getExternalStorageDirectory().getPath(); File file = new File(sdPath, "info.txt" );
判断SD卡是否可用 1 2 3 if ( Environment.MEDIA_MOUNTED.equals( Environment.getExternalStorageState() ) ) { Toast.makeText(getApplicationContext(), "SD卡可用" , 1 ).show(); }
获取SD卡的可用空间 1 2 3 4 5 6 7 File file = Envirinment.getExternalStorageDirectory(); long total = file.getTotalSpace(); long usable = file.getUsableSpace(); String Total = Formatter.formatFileSize(this , total); String Usable = Formatter.formatFileSize(this , usable);
文件权限 四种模式
MODE_PRIVATE — 值为0
MODE_APPEND —
MODE_WORLD_READABLE r–
MODE_WORLD_WRITEABLE -w-
Linux文件权限表示:十位数
第一位:文件的类型 ( 例如:文件夹 d )
2-4位:用户的权限
5-7位:用户所在的组的权限
8-10位:其他用户的权限
r w x
rw- <=> 110 <=> 6
改变文件权限
Linux下的chhmod命令: chmod 764 a.txt // 文件权限为:rwx rw- r– 111 110 100 == 7 6 4
XML 保存设置文件 SharedPreferences (必须得会)
Sharedpreferences setting = getSharedPreferences(name, mode); name:任意 mode:同上,4种模式 会自动生成:/data/data/包名/shared_prefs/name.xml 文件
1 2 3 4 5 6 7 8 9 10 11 SharedPreferences sp = new SharedPreferences("config" , 0 ); Editor edit = sp.edit(); edit.putString("username" , username); edit.putInt("Age" , age); edit.putFloat("PI" , 3.14 ); edit.commit();
SharedPreferences.getString(key, defaultValue /*默认的,找不到时*/ );
1 2 SharedPreferences sp = getSharedPreferences("config" , 0 ); sp.getString("username" , "" );
对应的 xml 内容
1 2 3 4 5 6 <?xml version="1.0" encodeing="UTF-8" standalone="true"?> <map > <string name ="username" > myname</string > <string name ="Age" > 20</string > ... </map >
手动构建 xml 文件 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 public void build_xml () { List<Sms> smsLists = new ArrayList<Sms>(); StringBuffer sb = new StringBuffer(); sb.append("<?xml version=\"1.0\" encodeing=\"UTF-8\" ?>" ); sb.append("<smss>" ); for (Sms sms : smsLists) { sb.append("<num>" ); sb.append("110" ); sb.append("</num>" ); sb.append("<people>" ); sb.append("小明" ); sb.append("</people>" ); } sb.append("</smss>" ); try { File file = new File(Environment.getExternalStorageDirectory().getPath(), "backup.xml" ); FileOutputStream fos = new FileOutputStream(file); fos.write(sb, .toString().getBytes()); fos.close(); } catch ( Exception e) { e.printStackTrace(); } }
xml序列化器 xmlSerializer 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 try { XmlSerializer sl = Xml.newSerializer(); File file = new File(Environment.getExternalStorageDirectory().getPath(), "backup2.xml" ); FileOutputStream fos = new FileOutputStream(file); sl.setOutput(fos, "utf-8" ); sl.startDocument("utf-8" , true ); sl.startTag(null , "smss" ); for (Sms sms : smsLists) { sl.startTag(null , "sms" ); sl.startTag(null , "num" ); sl.text("110" ); sl.endTag(null , "num" ); sl.startTag(null , "people" ); sl.text("小明" ); sl.endTag(null , "people" ); sl.endTag(null , "sms" ); } sl.endTag(numll, "smss" ); sl.endDocument(); fos.close(); }
xml的解析 1 2 3 4 5 6 7 8 9 10 11 12 13 <weather > <channel id ="1" > <city > 背景</cith > <temp > 25</temp > </channel > <channel > ... </channel > ... </weather >
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 try { InputStream inputStream = getAssets().open("weather.xml" ); List<Channel> weatherlists = WeatherParser.parserXml(inputstream); StringBuffer sb = new StringBuffer(); for (Channel channel : weatherlists) { sb.append(channel.toString()); } System.out.println(sb.toString()); }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 public static List<Channel> parserXml (InputStream in) { try { List<Channel> weather: ists = null ; Channel channel = null ; XmlPullParser parser = Xml.newPullParser(); parser.setInput(in, "utf-8" ); int type = parser.getEventType(); while (type != XmlPullParser.END_DOCUMENT ) { switch (type) { case XmlPullParser.START_TAG : if ("weather" .equals(parser.getName())) { weatherLists = new ArrayList<Channel>(); } else if ("channel" .equals(parser.getName())) { channel = new Channel(); String id = parser.getAttributeValue(0 ) channel.setId(id); } else if ("city" .equals(parser.getName())) { String city = parser.getText(); channel.setCity(city); } else if ("temp" .equals(parser.getName())) { String temp = parser.getText(); channel.setTemp(temp); } break ; case XmlPullParser.END_TAG : if ("channel" .equals(parser.getName())) { weatherLists.add(channel); } break ; } parser.next(); } return weatherLists; } catch ( Exception e ) { } }
数据库 sqlite 数据库初始化 SQLiteOpenHelper SQLiteOpenHelper类 用来管理数据库的创建 ,创建子类来继承它
db.execSQL(string sql) 执行SQL语句
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 import android.content.Context;import android.database.sqlite.SQLiteDababase;import android.database.sqlite.SQLiteOpenHelper;public class MyOpenHelper extends SQLiteOpenHelper { public MyOpenHelper (Context context) { super (context, "test.db" , null , 1 ); } @Override public void onCreate (SQLiteDatabase db) { db.execSQL("create table info( _id integer primary key autoincrement, name varchar(20) )" ); } @Override public void onUpgrade (SQLiteDatabase db, int oldVersion, int newVersion) { db.execSQL("alter table info add phone varchar(20)" ); } }
MainActivity部分 数据库路径:/data/data/包名/databases/数据库名字.db
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 public class MainActivity extends Activity { private MyOpenHelp myOpenHelper; @Override protected void onCreate (Bundle savedInstanceState) { super .onCreate(savedInstanceState); setContentView(R.layout.activity_main); myOpenHelper = new MyOpenHelper(getApplicationContext()); SQLiteDatabase sqLiteDatabase = myOpenHelper.getWriteableDatabase(); } }
执行SQL语句 execSQL void execSQL(sql, bindArgs); // 修改语句,无返回值
SQLiteDatabase execSQL(sql); // 查找语句,有返回值
lite 1 2 3 4 insert into info(name , phone) values ("Tom" , "110" ); delete from info where name = "Tom" lupdate info set phone = "120" where name = "Tom" ;select name , phone from info;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 public void onClick (View v) { SQLiteDatabase db = myOpenHelper.getWritableDatabase(); db.execSQL("insert into info (name, phone) values(?, ?)" , new Object[] {"Tim" , "119" }); db.execSQL("delete from info where name=?" , new Object[] {"Tom" }); db.close(); }
Cursor
int getCount() 行数 getColumnCount() 列数
String getString(int columnIndex)
boolean moveToNext() moveToFirst() moveToLast() moveToPosition(int position) moveToPrevious()
String[] getColumnNames() 所有列的名字
1 2 3 4 5 6 7 8 9 10 11 12 13 14 public void onClick (View v) { SQLiteDatabase db = myOpenHelper.getReadableDatabase(); Cursor cursor = db.rawQuery("select * from info" , null ); if (cursor != null && cursor.getCount() > 0 ) { while (cursor.moveToNext()) { String name = cursor.getString(1 ); System.out.println("name:" + name); } } }
SQL优缺点 缺点
优点
sqlite3 命令行SQL语句 开启shell adb shell
定位到目录 # cd /data/data/包名/databases
打开数据库 # sqlite3 数据库名字.db
sqlite> SQL语句
(不包括前面的sqlite>)
改变DOS编码
如果中文乱码,点击cmd属性,看编码是否为UTF-8(默认GBK)
改成GBK chcp 936
改成UTF-8 chcp 65001
chcp: ch ar c hange p age
谷歌封装好的数据库API 原理是组拼SQL语句
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 public void onClick (View v) { SQLiteDatabase db = myOpenHelper.getWritableDatabase(); ContentValues values = new ContentValues(); values.put("name" , "Tom" ); values.put("phone" , "110" ); long num = db.insert("info" , null , values); int num = db.delete("info" , "name=?" , new String[] {"Tom" }); int num = db.update("info" , values, "name=?" , new String[] {"Tom" }); Cursor cursor = db.query("info" , null , "name=?" , new String[] {"Tom" }, null , null , null ); if (cursor != null && cursor.getCount() > 0 ) { while (cursor.moveToNext()) { String name = cursor.getString(0 ); String phone = cursor.getString(1 ); System.out.println("name=" + name + " phone=" + phone); } } db.close(); }
优点
缺点
数据库的事物 例如:转账,需要取出、存入同时成功
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 public void onClick (Veiw v) { SQLiteDatabase db = myOpenHelper.getReadableDatabase(); db.beginTransaction(); try { db.execSQL("update ac_mon set money = money - 100 where name = ?" , new String[]{"userA" }); db.execSQL("update ac_mon set money = money + 100 where name = ?" , new String[]{"userB" }); db.setTansactionSuccessful(); } catch (Exception e) { Toast.makeText(getApplicationContext(), "服务器忙,请稍后再试" , 1 ).show(); } finally { db.endTransaction(); } }
ListView 使用方法 1、布局:activity_main 1 2 3 4 5 6 <ListView android:id ="@+id/lv" android:layout_witdh ="match_parent" android:layout_height ="match_parent" android:fastScrollEnabled ="true" > </ListView >
2、设置Adapter 连接到数据适配器:setAdapter()
1 2 3 4 5 6 7 8 9 10 11 12 13 public class MainActivity extends Activity { @Override protected void onCreate (Bundle saveInstanceState) { super .onCreate(savedInstanceState); setContentView(R.layout.activity_main); ListView lv = (ListView) findViewById(R.id.lv); lv.setAdapter(new MyListAdapter()); } }
3、实现BaseAdapter方法 BaseAdapter
可以放到上面的 MainActivity 类的代码里面,作为类中类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 private class MyListAdapter extends BaseAdapter { @Override public int getCount () { return 4 ; } @Override public Object getItem (int position) { return null ; } @Override public long getItemID (int position) { return 0 ; } @Override public View getView (int position, View convertView, ViewGroup parent) { TextView tv = new TextView(MainActivity.this ); tv.setText("索引:" + position); return tv; } }
错误提示 Attempt to invoke virtual method 'int android.view.View.getImportantForAccessibility()' on a null object reference
原因是 getView() 返回值为空……
结果示例 getCount()
控制条目数量。当数量=4时:
1 public int getCount () { return 4 ; }
结果:
每个item都是一个TextView,其由getView()
方法决定
显示数据的原理 MVC
Javaweb:
m : mode 数据
v : view 视图 jsp
c : controller 控制 sevlet
Android:
m : mode 数据(javabean)
v : listview
c : adapter
ListView优化 getView() 方法说明 只有在屏幕上显示(包括只显示一点点)的item才会触发getView()
方法。 滚动时新显示的条目也会触发。 快速滚动加载大量View可能会导致内存溢出(Out Of Memory)。
convertView 缓存机制 item并不是滚动完就销毁。 使用 convertView
历史缓存可避免内存溢出。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 @Override public View getView (int position, View convertView, ViewGroup parent) { TextView tv; if (convertView == null ) { tv = new TextView(MainActivity.this ); } else { tv = (TextView) convertView; } tv.setText("索引:" + position); return tv; }
复用历史缓存对象:一开始时是真的创建可见范围内的对象,滚动上去后,新出现(底部)的对象实际上是复用已经加载好但是看不见了的(顶部)的对象。根据 position
调用 getView()
方法。
例如,快速滚动时加载100个item,不用缓存需要创建100个TextView。使用 convertView 后,只需要设置TextView的值就好了。
List高度设置 能否设置成 layout_height="wrap_content"
?
1 2 3 <ListView android:layout_height ="match_parent" > </ListView >
改成android:layout_height="wrap_content"
后,无法确定高度,校验很多次,重复调用 getView()
方法,严重影响性能。
复杂的ListView ┍━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┑ │ ┌───────┐ 这里是项目标题,字… │ │ │ Image │ 这个部分是项目相对应
│ │ └───────┘ 的信息内容
│ ┕━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┙
1、Layout:activity_main 1 2 3 4 5 <ListView android:id ="@+id/lv" android:layout_witdh ="match_parent" android:layout_height ="match_parent" > </ListView >
2、Layout : item(xml) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 <RelativeLayout xmlns:android ="http://schemas.android.com/apk/res/android" android:layout_witdh ="match_parent" android:layout_height ="match_parent" > <ImageView android:id ="@+id/iv_icon" android:layout_width ="wrap_content" android:layout_height ="wrap_content" android:src ="@drawable/ic_launcher" /> <TextView android:id ="@+id/tv_title" android:layout_width ="match_parent" // 左边与父容器右边的距离 android:layout_height ="wrap_content" android:layout_toRightOf ="@id/iv_icon" // 图片右边 android:layout_marginTop ="3dp" // 顶部留空 android:singleLine ="true" // 单行显示 android:ellipsize ="end" // 显示开头,末尾三个点。start 值相反 androi:textColor ="#000000" android:textsize ="20sp" android:text ="这个里项目标题,字体加粗加黑" /> <TextView android:id ="@+id/tv_message" android:layout_width ="match_parent" android:layout_height ="wrap_content" android:layout_toRightOf ="@id/iv_icon" // 图片右边 android:layout_below ="@id/tv_text" // 标题下面 android:singleLine ="true" android:ellipsize ="end" androi:textColor ="#666666" android:textsize ="15sp" android:text ="这个里项目标题,字体加粗加黑" /> </RelativeLayout >
3、BaseAdapter:打气筒 View View.inflate(context, resource, root)
方法(打气筒) 介绍:创建新的View对象,把布局资源(xml 文件)折换成一个 View 对象,放到父容器中,并返回这个 View 对象
context : getApplicationContext()
,或 this
resource : 定义的布局文件 R.id.xxx
root : null,或者待放入的容器(ViewGroup)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 public class MainActivity extends Activity { @Override protected void onCreate (Bundle saveInstanceState) { super .onCreate(savedInstanceState); setContentView(R.layout.activity_main); ListView lv = (ListView) findViewById(R.id.lv); lv.setAdapter(new MyListAdapter); } private class MyListAdapter extends BaseAdapter { @Override public int getCount () { return 4 ; } @Override public Object getItem (int position) { return null ; } @Override public long getItemID (int position) { return 0 ; } @Override public View getView (int position, View convertView, ViewGroup parent) { View view; if (convertView == null ) { view = View.inflate(getApplicationContext, R.id.item, null ); } else { view = (TextView) convertView; } return view; } } }
获取 XML 布局里面的控件:view.findViewById()
,不能直接 findViewById()
打气筒的三种写法 打气筒写法一:View.inflate()
1 view = View.inflate(getApplicationContext, R.id.item, null );
打气筒写法二:LayoutInflater.from()
1 view = LayoutInflater.from(getApplicationContext()).inflate(R.layout.item, null );
打气筒写法三:getSystemService() 据说实际上用得比较多
1 2 LayoutInflater inflater = (LayoutInflater) getSystemService(LAYOUT_INFLATER_SERVICE); view = inflater.inflate(R.layout.item, null );
其他写法:不常见
ArrayAdapter 布局:activity_main
1 2 3 4 <ListView android:layout_width ="wrap_content" android:layout_height ="wrap_content" > </ListView >
布局:item
1 2 3 4 <TextView android:layout_width ="wrap_content" android:layout_height ="wrap_content" > </TextView >
MainActivity
ArraryAdapter<String>(context, resource, objects)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 import android.R.array;public class MainAcitivity extends Activity { String names[] = { "111" , "222" , "333" , "444" }; @Override protected void onCreat (Bundle savedInstanceState) { super .onCreate(savedInstanceState); setContentView(R.layout.activity_main); ListView lv = (ListView) findViewById(R.id.lv); ArrayAdapter<String> adapter = new ArrayAdapter<String>(this , R.id.item, names); lv.setAdapter(adapter); } }
结果
重载的四个参数的ArrayAdapter用法:
ArraryAdapter<String>(context, resource, textViewResourceId, objects)
textViewResourceId
为 resource 布局文件里面的某个特定的 TextView 的 ID
1 ArrayAdapter<String> adapter = new ArrayAdapter<String>(this , R.id.item, R.id.tv, names);
1 2 3 4 <RelativeLayout ... > <TextView android:id ="@+id/tv" ... /> </RelativeLayout >
SimpleAdapter ┍━━━━━━━━━━━━━━━━━━━━━┑ │┌───────┐┌───────┐│ ││ name │ │ phone ││ │└───────┘└───────┘│ ┕━━━━━━━━━━━━━━━━━━━━━┙
布局:activity_main
1 2 3 <RelativeLayout ... > <ListView ... /> </RelativeLayout >
布局:item
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 <LinearLayout xmlns ="..." android:layout_width ="match_parent" android:layout_height ="match_parent" android:orientation ="horizental" > <TextView android:id ="@+id/tv_name" android:layout_width ="0dp" android:layout_weight ="1" android:layout_height ="wrap_content" /> <TextView android:id ="@+id/tv_phone" android:layout_width ="0dp" android:layout_weight ="1" android:layout_height ="wrap_content" /> </LinearLayout >
代码:MainActivity
SimpleAdapter adapter = new SimapleAdapter(context, data, resource, from, to);
// 参数长的那个
data
是 List<Map<String, String>>
类型数据
resource
是 布局文件(上面的 item)
from
是 String[] 类型,表示 Map<String, String>
的键
to
是 int[] 类型数组,与from
对应的布局文件中的 TextView 的 ID
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 ListView lv = (ListView) findViewById(R.id.lv); List<Map<String, String>> data = new ArrayList<Map<String, String>>(); Map<String, String> map1 = new HashMap<String, String>(); map1.put("name" , "Tom" ); map1.put("phone" , "110" ); Map<String, String> map2 = new HashMap<String, String>(); map2.put("name" , "Alice" ); map2.put("phone" , "120" ); data.add(map1); data.add(map2); SimpleAdapter adapter = new SimapleAdapter(getApplicationContext(), data, R.id.item, new String[]{ "name" , "phone" }, new String[]{ R.id.tv_name, R.id.tv_phone }); lv.setAdapter(adapter);
用ListView展示数据库的数据 ┍━━━━━━━━━━━━━━━━━━━━━┑ │┌───────┐┌───────┐│ ││ name │ │ phone ││ │└───────┘└───────┘│ ┕━━━━━━━━━━━━━━━━━━━━━┙
布局:activity_main
布局:item
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 <LinearLayout xmlns ="..." android:layout_width ="match_parent" android:layout_height ="match_parent" android:orientation ="horizental" > <TextView android:id ="@+id/tv_name" android:layout_width ="0dp" android:layout_weight ="1" android:layout_height ="wrap_content" /> <TextView android:id ="@+id/tv_phone" android:layout_width ="0dp" android:layout_weight ="1" android:layout_height ="wrap_content" /> </LinearLayout >
类:Person
MainActivity
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 public class MainAcitivity extends Activity { ListView lv = (ListView) findViewById(R.id.lv); List<Person> lists = new ArrayList<Person>(); @Override protected void onCreat (Bundle savedInstanceState) { super .onCreate(savedInstanceState); setContentView(R.layout.activity_main); } public void onClick (View v) { SQLiteDatabase db = myOpenHelper.getWritableDatabase(); Cursor cursor = db.query("info" , null , null , null , null , null , null ); if (cursor != null && cursor.getCount() > 0 ) { while (cursor.moveToNext()) { String name = cursor.getString(0 ); String phone = cursor.getString(1 ); Person person = new Person(name, phone); lists.add(person); } lv.setAdapter(new MyListAdapter); } db.close(); } private class MyListAdapter extends BaseAdapter { @Override public int getCount () { return lists.size(); } @Override public Object getItem (int position) { return null ; } @Override public long getItemID (int position) { return 0 ; } @Override public View getView (int position, View convertView, ViewGroup parent) { TextView view; if (convertView == null ) { view = View.inflate(getApplicationContext, R.id.item, null ); } else { view = (TextView) convertView; } Person person = lists.get(position); TextView tv_name = (TextView) view.findViewById(R.id.tv_name); TextView tv_phone = (TextView) view.findViewById(R.id.tv_phone); tv_name.setText(person.getName()); tv_phone.setText(person.getPhone()); return view; } } }
网络 网页源码查看器 联网权限 android.permission.INTERNET
HttpUrlConnection 类 用于发送或者接受数据,取网页源代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 String path = "http://www.baidu.com" ; URL url = new URL(path); try { HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection(); urlConnection.setRequestMethod("GET" ); urlConnection.setConnectTimeout(5000 ); int code = urlConnection.getResponseCode(); if (code == 200 ) { InputStream in = urlConnection.getInputStream(); String content = StreamTools.readStream(in); } } public static class StreamTools { public static String readStream (InputStream in) { int len = -1 ; byte [] buffer = new byte [1024 ]; ByteArrayOutputStream baos = new ByteArrayOutputStream(); while ( ( len = in.read(buffer) ) != -1 ) { baos.write(buffer, 0 , len); } in.close(); String content = new String(baos.toByteArray()); return content; } }
scrollview 只能有一个子控件
布局
1 2 3 4 5 6 7 8 <ScrollView android:layout_width ="match_parent" android:layout_height ="match_parent" > <TextView android:id ="@+id/tv" /> </ScrollView >
如果要多个子控件,可以将这些子控件包裹在 <LinearLayout>
里面,orientation="vertical"
消息机制的写法 主线程(UI线程) ANR Application not response 应用无响应
耗时的操作放到子线程当中
Android 4.0 之后,谷歌强制要求连接网络不能再主线程进行访问
只有主线程(UI线程)才可以更新UI
Handler 使用步骤 1、在主线程定义一个 Handler
1 private Handler handler = new Handler();
2、使用 Handler,重写里面的 handlerMessage 方法
1 public void handleMessage (android.os.Message msg) { }
3、用 Handler 去子线程发消息
1 handler.sendMessage(msg);
4、handleMessage 方法就会执行,更新UI
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 public class MainActivity extends Activity { TextView tv = (TextView) findVIewById(R.id.tv); private Handler handler = new Handler() { public void handleMessage (android.os.Message msg) { String content = (String) msg.obj; tv.setText(content); }; } public void click (View v) { new Thread() { public void run () { try { String path = "http://www.baidu.com" ; URL url = new URL(path); HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection(); urlConnection.setRequestMethod("GET" ); urlConnection.setConnectTimeout(5000 ); int code = urlConnection.getResponseCode(); if (code == 200 ) { InputStream in = conn.getInputStream(); String content = StreamTools.readStream(in); Message msg = new Message(); msg.obj = content; handler.sendMessage(msg); } } } } .start(); } }
消息机制的原理 消息队列,有一个Looper不断读取队列,然后 handlerMessage(msg)
Handler 完善:what 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 protected final int REQUESTSUCESS = 0 ;protected final int REQUESTNOTFOUND = 1 ;protected final int REQUESTEXCEPTION = 2 ;public void handleMessage (android.os.Message msg) { switch (msg.what) { case REQUESTSUCESS : String content = (String) msg.obj; tv.setText(content); break ; case REQUESTNOTFOUND : Toast.makeText(getApplicationContext(), "请求资源不存在" , 0 ).show(); break ; case REQUESTEXCEPTION : Toast.makeText(getApplicationContext(), "服务器忙,请稍后再访问" , 0 ).show(); break ; } } try { if (code == 200 ) { Mseeage msg = new Message(); msg.what = REQUESTSUCESS; handler.sendMessage(msg); } else { Mseeage msg = new Message(); msg.what = REQUESTNOTFOUND; handler.sendMessage(msg); } } catch (Exception e) { Mseeage msg = new Message(); msg.what = REQUESTEXCEPTION; handler.sendMessage(msg); }
程序运行错误,尽量提示用户:服务器忙,请稍后访问
而不是:程序运行错误,让用户担忧
图片查看器 ImageView 显示网络图片 1 2 3 <manifest > <uses-permission android:name ="android.permission.INTERNET" /> </manifest >
1 2 3 4 5 6 <EditView /> <Button /> <ImageView android:id ="@+id/iv" android:layout_width ="wrap_content" android:layout_height ="wrap_content" />
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 private Handler handler = new Handler(){ public void handleMessage (android.os.Message msg) { bitmap bitmap = (Bitmap) msg.obj; iv.setImageBitmap(bitmap); } } public void click (View v) { new Thread() { public void run () { try { String path = ev.getText().toString().trim(); URL url = new URL(path); HttpURLConnection conn = (HttpURLConnection) url.openConnection(); conn.setRequestMethond("GET" ); conn.setConnectTimeout(5000 ); int code = conn.getResponseCode(); if (code == 200 ) { InputStream in = conn.getInputStream(); Bitmap bitmap = BitmapFactory.decodeStream(in); Message msg = Message.obtain(); msg.obj = bitmap; handler.sendMessage(msg); } else { } } catch (Exception e) { e.printStackTrace(); } } } .start(); }
使用 Message 的静态方法 可以减少对象的创建
Message msg = Message.obtain();
效果等同于:Message msg = new Message()
但是效率快
图片缓存到本地 1 2 3 4 5 6 7 8 9 10 11 12 13 14 if (code == 2 ) { InputStream in = conn.getInputStream(); File file = new File(getCacheDir(), "test.png" ); FileOutputStream fos = new FileOutputStream(file); int len = -1 ; byte [] buffer = new byte [1024 ]; while ((len = in.read(buffer)) != -1 ) { fos.write(buffer, 0 , len); } fos.close(); in.close(); }
使用缓存图片 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 new Thread(){ public void run () { try { File file = new File(getCacheDir(), "test.png" ); if (file.exists() && file.length() > 0 ) { Bitmap cacheBitmap = BitmapFactory.decodeFile(file.getAbsolutePath()); Message msg = Message.obtain(); msg.obj = cacheBitmap; handler.sendMessage(msg); } else { String path = ev.getText().toString().trim(); URL url = new URL(path); HttpURLConnection conn = (HttpURLConnection) url.openConnection(); conn.setRequestMethond("GET" ); conn.setConnectTimeout(5000 ); int code = conn.getResponseCode(); if (code == 200 ) { InputStream in = conn.getInputStream(); FileOutputStream fos = new FileOutputStream(file); int len = -1 ; byte [] buffer = new byte [1024 ]; while ((len = in.read(buffer)) != -1 ) { fos.write(buffer, 0 , len); } fos.close(); in.close(); } } } } }
对图片进行加密 Base64加密: Base64.encodeToString(byte[] input, int flags);
1 File file = new File(getCacheDir(), Base64.encodeToString(path.getBytes[], Base64.DEFAULT));
上述代码针对文件路径进行加密,文件名为URL路径加密后的密文
cache 和 filedir 区别 写出文件示例 1 2 3 4 5 6 7 8 9 10 11 try { FileOutputStream fos = openFileOutput("info.txt" , 0 ); File file = new File(getCacheDir(), "info.txt" ); FileOutputStream fos = new FileOutputStream(file); fos.write("haha" .getByte()); fos.close(); }
Thread API runOnUiThread 写法 runOnUiThread(Runable action)
不管在什么位置,里面运行的语句都运行在 UI 线程上。
1 2 3 4 5 6 7 8 9 new Thread() { public void run () { runOnUiThread(new Runnable() { public void run () { tv.setText("HHH" ); } }); } }
Handler API 延迟线程 new Handler().postDelayed(Runnable r, int delayMillis);
效果等同于 Sleep()
这个方法执行在 UI 线程里,可更新 UI
1 2 3 4 5 6 7 8 9 10 11 public void f () { new Handler().postDelayed( new Runnable(){ @Override public run () { } }, 2000 ); }
定时器 Timer Timer.schedule(TimerTask task, long delay);
延迟后执行
Timer.schedule(TimerTask task, long delay, long period);
指定的延迟后进行重复的固定延迟执行
还有其它重载的 schedule 函数
相当于子线程,不能用来更新 UI 。可以用 runOnUiThread
来执行 UI 操作
1 2 3 4 5 6 7 8 9 10 11 12 13 14 public void click () { Timer timer = new Timer(); TimerTask task = new TimerTask() { @Override public void run () { } } timer.schedule(task, 5000 ); }
Timer.cancel();
Task.cancel();
销毁一个执行
1 2 3 4 5 6 protected void onDestroy () { timer.cancel(); task.cancel(); super .onDestroy(); }
网络基础 网络图片 SmartImageView 1 2 3 <com.loopj.android.image.SmartImageView android:id ="@+id/siv" ... />
1 2 3 4 5 6 7 8 imageUrl = "..." ; view = View.inflate(...); SmartImageView svi = (SmartImageView) view.findViewById(R.id.siv); siv.setImageUrl(imageUrl); siv.setImageUrl(String imageUrl, Integer fallbackResource); siv.setImageView(String imageUrl, Integer OnCompleteListener completeListener); siv.setImageView(String imageUrl, Integer fallbackResource, OnCompleteListener completeListener); siv.setImageView(String imageUrl, Integer fallbackResource, Integer loadingResource, OnCompleteListener completeListener);
GitHub 开源项目 网址:github.com
导入开源项目:下载 zip 后,把解压出来后的 com 文件夹(源码包)复制到项目的 src 中
源码:在 com 文件夹内,.java 后缀名的
布局:用的时候,需要用开源项目的完整包名
HttpURLConnextion 联网的基类。
如果连接要求不是很高,只是用来发发数据的话,可以用下面的开源项目。
封装 toast 方法 通用方法,获取数据后立即弹出提示
1 2 3 4 5 6 7 8 public void showToast (final String content) { runOnUiThread(enw Runnable () { @Override public void run () { Toast.makeText(getApplicationContext(), content, 1 ).show(); } }) }
GET 和 POST 的区别
URL路径不同:GET 拼接 网址 和 数据
POST 通过请求体(流)的形式把数据发送给服务器
POST 比 GET 多了两个头信息:content-length 和 content-type
使用 POST 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 String path = "http://xxx" ; String data = "name=" + name + "&pass=" + pass; URL url = new URL(path); HttpURLConnextion conn = (HttpURLConnection) url.openConnection(); conn.setRequestMethond("POST" ); conn.setConnextTimeout(5000 ); conn.setRequestProperty("Content-Type" , "application/x-www-form-url" ); conn.setRequestProperty("Content-Length" , data.length()+"" ); con.setDoOutput(true ); conn.getOutputStream().write(data.getBytes()); int code = conn.getReponseCode();if (code == 200 ) { InputStream inputStream = conn.getInputStream(); String content = StreamTools.readStream(inputStream); showToast(content); }
乱码问题 Android 编码:UTF-8
服务器编码:本地服务器的编码,一般为 iso-859-1 (类似 GBK)
改变服务器编码(Java): 返回中文乱码 System.out.prrintln("ans : " + new String(ans.getBytes("utf-8")));
发送中文乱码:先以 iso-8859-1 编码,再以 UTF-8 解码
`System.out.prrintln("ans : " + new String(ans.getBytes("iso-8859-1"), "utf-8"));`
改变 Android 编码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 public class StreamTools { public static String readStream (InputStream in) throws Exception { ByteArrayOutputStream baos = new ByteArrayOutStream(); int len = -1 ; byte [] buffer = new byte [1024 ]; while ( (len = in.read(buffer)) != -1 ) { baos.write(buffer, 0 , len); } in.close(); String content = new String(baos.toByteArray(), "gbk" ); return content; } }
URLEncode 类 encode方法: URLEncode.encode(String s, String charseName);
path = path + "?username=" + URLEncode(name, "utf-8") + "&password=" + URLEncode(pass, "utf-8");
HttpClient 方式提交数据 HttpClient 是一个接口,而不是类。(没有人用这个,了解)
GET 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 new Thread(){ public void run () { try { String path = "http://xxx?id=" + URLEncoder.encode(id); DefaultHttpClient client = new DefaultHttpClient(); HttpGet get = new HttpGet(path); HttpResponse response = client.execute(get); int code = response.getStatusLine().getStatusCode(); if (code == 200 ) { InputStream inputstream = response.getEntity().getContent(); String content = StreamTools.readStream(inputStream); showToast(content); } } } } .start();
POST Entity、BasicNameValuePair 等也都是接口
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 String name = "..." , pwd = "..." ; String path = "..." ; DefaultHttpClient client = new DefaulthttpClient(); HttpPost post = new HttpPost(path); List<NameValuePair> lists = new ArrayList<NameValuePair>(); BasicNameValuePair nameValuePair = new BasicNameValuePair("username" , name); BasicNameValuePair pwdValuePair = new BasicNameValuePair("password" , pwd); lists.add(nameValuePair); lists.add(pwdValuePair); UrlEncodeFormEntity entity = new UrlEncodeFormEntity(parameters); post.setEntity(entity); HttpResponse response = client.execute(post); int code = response.getStatusLine().getStatusCode();if (code == 200 ) { InputStream inputStream = response.getEntity().getContent(); String content = StreamTools.readStream(inputStream); showToast(content); }
asyncHttpClient 开源项目 GitHub 上下载,把 com 包复制到 src 中。
GET 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 String path = "..." ; AsyncHttpClient client = new AsyncHttpClient(); client.get(path, new AcynchttpResponseHandler() { @Override public onSuccess (int statusCode, Header[] headers, byte [] responseBody) { showToast(new String(responseBody, "gbk" )); } @Override public void onFailure (int statusCode, Header[] headers, byte [] responseBody, Throwable error) { } });
POST 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 String path = "..." ; AsyncHttpClient client = new AsyncHttpClient(); RequestParams params = new RequestParams(); params.put("username" , "admin" ); params.put("password" , "123" ); params.put("email" , "my@email.com" ); params.put("profile_picture" , new FIle("pic.jpg" )); params.put("profile_picture2" , someInputStream); params.put("profile_picture" , new ByteArrayInputStream(someBytes)); client.post(path, params, new AcynchttpResponseHandler() { @Override public onSuccess (int statusCode, Header[] headers, byte [] responseBody) { showToast(new String(responseBody, "gbk" )); } @Override public void onFailure (int statusCode, Header[] headers, byte [] responseBody, Throwable error) { } });
多线程下载 原理 分段下载
服务器没有限速的话,线程并不是越多越好
获取一部分文件,返回的状态码是206 ,而不是200 。
RandomAccessFile 类 随机读取和写入文件,使用见下方代码
代码 test.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 import java.io.*;import java.net.*;class test { private static String path = "http://download.dcloud.net.cn/HBuilder.9.0.1.windows.zip" ; private static int threadCount = 3 ; public static void main (String[] args) { try { URL url = new URL(path); HttpURLConnection conn = (HttpURLConnection) url.openConnection(); conn.setRequestMethod("GET" ); conn.setConnectTimeout(5000 ); int code = conn.getResponseCode(); if (code == 200 ) { int length = conn.getContentLength(); RandomAccessFile raf = new RandomAccessFile(getFileName(path), "rw" ); raf.setLength(length); int blockSize = length / threadCount; for (int i = 0 ; i < threadCount; i++) { int startIndex = i * blockSize; int endIndex = (i + 1 ) * blockSize - 1 ; if (i == threadCount - 1 ) { endIndex = length - 1 ; } DownLoadThread dlt = new DownLoadThread(path, startIndex, endIndex, i); dlt.start(); } } } catch (Exception e) { e.printStackTrace(); } } public static String getFileName (String path) { int start = path.lastIndexOf("/" ) + 1 ; return path.substring(start); } }
DownLoadThread.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 import java.io.*;import java.net.*;public class DownLoadThread extends Thread { private String path; private int startIndex; private int endIndex; private int threadId; DownLoadThread(String path, int startIndex, int endIndex, int threadId) { this .path = path; this .startIndex = startIndex; this .endIndex = endIndex; this .threadId = threadId; } @Override public void run () { try { URL url = new URL(path); HttpURLConnection conn = (HttpURLConnection) url.openConnection(); conn.setRequestMethod("GET" ); conn.setConnectTimeout(5000 ); conn.setRequestProperty("Range" , "bytes=" + startIndex + "-" + endIndex); int code = conn.getResponseCode(); if (code == 206 ) { RandomAccessFile raf = new RandomAccessFile(getFileName(path), "rw" ); raf.seek(startIndex); InputStream in = conn.getInputStream(); int len = -1 ; byte [] buffer = new byte [1024 ]; while ((len = in.read()) != -1 ) { raf.write(buffer, 0 , len); } System.out.println("下载完毕" ); raf.close(); } } catch (Exception e) { e.printStackTrace(); } } public static String getFileName (String path) { int start = path.lastIndexOf("/" )+1 ; return path.substring(start); } }
断点续传 下载过程中保存每个线程的下载位置到文件
保存的间隔越短(缓冲区越小)则越卡顿
1 2 runningThread = threadCount;
下载类:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 File file = new File(threadId + ".txt" ); if (file.exists() && file.length() > 0 ){ FileInputStream fis = new FileInputStream(file); BufferedReader bufr = new BufferedReader(new InputStream(fis)); String lastPositions = bufr.readLine(); int lastPosition = Integer.parseInt(lastPositions); startIndex = lastPosition; fis.close(); } if (code == 206 ){ RandomAccessFile raf = new RandomAccessFile("file.exe" , "rw" ); raf.seek(startIndex); InputStream in = conn.getInputStream(); int len = -1 ; byte [] buffer = new byte [1024 * 1024 ]; int total = 0 ; while ((len = in.read) != -1 ) { raf.write(buffer, 0 , len); total += len; int currentThreadPosition = startIndex + total; RandomAccessFile raff = new RandomAccessFile(threadId + ".txt" , "rwd" ); raff.write(String.valueOf(currentThreadPosition).getBytes()); raff.close(); } raf.close(); System.out.println("下载完毕" ); synchronized (DownLoadThread.class) { runningThread--; if (runningThread == 0 ) { for (int i = 0 ; i < threadCount; i++) { File file = new File(threadId + ".txt" ) file.delete(); } } } }
安卓动态添加进度条 进度条布局
1 2 3 4 5 <progressBar xmlns:android ="http://schemas.android.com/apk/res/android" android:id ="@+id/progressBar1" style ="?android:attr/progressBarStyleHorizontal android:layout_width=" match_parent " android:layout_height ="wrap_content" />
动态添加进度条
1 2 3 4 5 6 7 8 9 10 11 12 13 14 List<ProgressBar> pbLists = new ArrayList<ProgressBar>(); barLayout = (LinearLayout) findViewById(R.id.barLayout); barLayout.removeAllViews(); pbLists.clear(); int threadCount = Integer.parseInt(threadCount);for (int i = 0 ; i < threadCount; i++){ View pbView = View.inflate(getApplicationContext(), R.id.barItem, null ) pbLists.add(pbView); barLayout.addView(pbView); }
安卓多线程下载 与进度相关的控件都可以在子线程更新UI
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 private int PbMax; private int pbPos; pbMax = endIndex - startIndex; pbPos = 0 ; if (code == 206 ){ RandomAccessFile raf = new RandomAccessFile(getFileName(path), "rw" ); raf.seek(startIndex); InputStream in = conn.getInputStream(); int len = -1 ; byte [] buffer = new byte [1024 ]; while ((len = in.read()) != -1 ) { raf.write(buffer, 0 , len); } System.out.println("下载完毕" ); raf.close(); pbLists.get(threadId).setMax(pbMax); pbLists.get(threadId).setProgress(pbPos); }
多线程下载开源项目 xUtils
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 public void click (View v) { String path = "..." ; HttpUtils http = new HttpUtils(); http.download(path, "/mnt/sdcard/haha.exe" , true , new RequstCallBack<File>() { @Override public void onSucess (RequestInfo<File> responseInfo) { Toast.makeText(getApplicationContext(), "下载成功" , 1 ).show(); } public void onFailure (HttpException error, String msg) { ; } @Override public void onLoading (long total, long current, boolean isUploading) { pb.setMax((int ) total); pb.setProgress((int ) current); } }) }
Android 四大组件 Activity 创建新的 Activity 创建新的 Class 继承 Activity
新的 Activity
1 2 3 4 5 6 7 8 9 10 public class TestActivity extends Activity { @Override protected void onCreate (Bundle savedInstanceState) { super .onCreate(savedInstanceState); setContentView(R.layout.activity_test) } }
清单文件 AndroidManifest
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 <application ... > <activity android:name ="com.包名.MainActivity" android:label ="第一个页面" android:icon ="@drawable/icon1" /> <intent-filter > <action android:name ="android.intent.action.MAIN" /> <category android:name ="android.intent.category.LAUNCHER" /> </intent-filter > </activity > <activity android:name ="com.包名.TestActivity" /> android:label="第二个页面" android:icon="@drawable/icon2" /> <intent-filter > <action android:name ="android.intent.action.MAIN" /> <category android:name ="android.intent.category.LAUNCHER" /> </intent-filter > </activity > </application >
Android 四大组件都要在清单文件里面配置
如果要多个入口(启动图标),则 intent-filter
必须要 .MAIN 和 .LAUNCHER。(一般就一个启动图标)
Activity 下的 label 和 icon 属性可以和 Application 节点的属性不一样默认使用 Application 节点下的属性。
隐式意图 1 2 3 4 5 6 7 8 Intent intent = new Intent(); intent.setAction("com.包名.活动名" ); intent.addCategory("android.intent.category.DEFALT" ); startActivity(intent);
意图过滤器 1 2 3 4 5 <intent-filter > <data android:mimetype ="audio/mp4" android:scheme ="wxy" /> </intent-filter >
可以配置多个 filter,只要匹配到完整的一个(setData 和 .setType)就行 mimetype 和 scheme 都可以不写
mimetype Java 代码中设置 Date 也为 wxy(只要和约束相同就行)intent.setData(Uri.parse("wxy:" + 110));
不设置约束,其实效果相同。
scheme 设置 setType 会自动把 setData 方法的数据清除。 反之亦然…… intent.setType("audio/mp4");
谷歌自定义了很多数据类型,开发者也可以用自己定义的例如aa/bb
。
两个要一起使用时,可以用下面的方法:intent.setDataAndType(data, type);
例如:intent.setDataAndType(Uri.parse("wxy:" + 110), "audio/mp4");
####显式意图
通过制定具体的包名和类名来切换窗口
1 2 3 4 5 6 7 public void click (View v) { Intent intent = new Intent(); intent.setClassName("com.wxy.hello" , "com.wxy.hello.HelloActivity2" ); startActivity(intent); }
1 2 startActivity(new Intent(this , HelloActivity.class));
开启自己应用的界面用显示意图,开启其他应用用隐式意图。
显式意图更加安全一些(仅自己能调用)
####传递数据 切换 Activity 时传递数据
获取传递过来的数据 Uri getIntent();
1 Uri data = intent.getData();
Intent.putExtra(name, value);
value 支持Android八大类型的数据
存入数据
1 2 3 Intent intent = new Intent(this , SecondActivity.class); intent.putExtra("name" , "hhh" ); intent.putExtra("sex" , 0 );
取出数据
1 2 3 4 Intent intent = getIntent(); String name = intent.getStringExtra("name" ); int sex = intent.getIntExtra("sex" , 0 ); textView.setTex(name);
####人品计算器
1 2 3 4 5 6 7 8 9 10 11 12 13 byte [] bytes = NULL;int total = 0 ;if (sex == 1 ) bytes = name.getBytes("GBK" );else bytes = name.getBytes("UTF-8" );for (byte b : bytes) { int number = b & 0xff ; total += number; } int score = Math.abs(total) % 100 ;if (score > 90 ) { toast("您的人品爆棚!" ); break ; }
#####RadioGroup
1 2 3 4 5 <RadioGroup ... > <Radio ... /> <Radio android:checked ="checked" /> </RadioGroup >
1 2 3 RadioGroup rg = (RadioGroup) findViewByUd(R.id.rg); int res = rg.getCheckedRadioButtonId(); if (res == NULL) { ; }
短信发送器 制作一个能够发送短信模板的小应用。
####简单的 ListView
1 2 3 4 String msgs[] = {"短信1" , "短信2" , "短信3" , ...}; ListView lv = (ListView) findViewById(R.id.lv); ArrayAdapter<String> adapter = new ArrayAdapter<String>(this , R.layout.item, msg); lv.setAdapter(adapter);
####设置 ListView 事件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 lv.setonItemClickListener(new onItemClickListener(){ @Override public void onItemClickListener (AdapterView<?> parent, View view, int position, long id) { String content = msgs[position]; Intent intent = new Intent(); intent.setAction("android.intent.action.SEND" ); intent.addCategory("android.intent.category.DEFAULT" ); intent.setType("text/plain" ); intent.putExtra("sms_body" , content); startActivity(intent); } });
####请求码和结果码
自定义短信模板页面并插入
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 public void addTemplate (View v) { Intent intent = new Intent(this , SmsActivity.class); startActivityForResult(intent, 1 ); } @Override protected void onActivityResult (int requestCode, int resultCode, Intent data) { if (requestCode == 1 ) { ; } if (resultCode == 10 ) { String msg = data.getStringExtra("msg" ); edit_num.setText("msg" ); } super .onActivityResult(requestCode, resultCode, data); }
选择模板页面
1 2 3 4 5 6 7 8 9 10 11 12 13 lv.setOnItemClickListener(newOnItemClickListener(){ @Overridepublic void onItemClickListener (AdapterView<?> parent, View view, int position, long id) { String msg = msgs.get(position).getPhone(); Intent intent - new Intent(); intent.putExtra("msg" , msg); setResult(10 , intent); finish(); } })
####发送短信
SmsManager 类
添加权限 android.permission.SEND_SMS
1 2 3 4 String number, content; SmsManager sm = SmsManager.getDefault(); sm.sendTextMessage(number, null , content, NULL);
注:这个API有限制,英文70字符,中文140。太多不会发送,需要划分成多个片段。
###Activity 生命周期
onCreate onResuart onStart 变成可视界面时 onResume 允许获取焦点(按钮能点击等) onPause 不允许获取焦点 onStop 界面不可见时 onDestroy
打开其他 Activity:onPause onStart 返回当前 Activity:onRestart onStart onResume
####设置横屏 横竖屏切换时,会Destroy再Create。 竖屏:portrait 横屏:landscape<activity android:screenOrientation="portrait">
###任务栈
任务栈和 Activity 有关
进栈:打开一个 Activity 出栈:关闭一个 Activity 操作的 Activity 永远是栈顶的
任务栈是用来维护操作体验的
应用程序退出完是任务栈清空了
一般情况一个应用程序对应一个任务栈
###Activity 的四种启动模式
<activity android:launchMode="stander" ... >
stander 每创建一次 Activity 就一个任务栈
singletop 若Activity在栈顶则复用原Activity,否则创建Activity
singletask 一个 Activity 单一任务栈。浏览器。
singleinstance 有专属的任务栈,仅一个实例,不会重复创建。任务栈和任务栈之间再形成顺序。来电页面。
广播 广播接收 类名一般是 xxReceiver 格式 退出程序后,进程还在,广播接受器还能收到广播
IP拨号器 (加特定前缀能省话费)
权限:android.permission.OUTGONG_CALL
清单文件配置
1 2 3 4 5 6 7 8 9 <application > ...... <receiver android:name ="com.包名.OutGoingCallReceiver" > <intent-filter > <action android:name ="android.intent.action.NEW_OUTGONG_CALL" /> </intent-filter > </receiver > </application >
定义广播接受者(java类) 若没有界面的话,可以不用编辑 MainActivity
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 import android.content.BroadcastReceiver;public class OutGoingCallReceiver extends BroadcastReceiver { @Override public void onReceiver (Context context, Intent intent) { SharedPreferences sp = context.getSharedPreferences("config" , 0 ); String IPnumber = sp.getString("IPnumber" , "" ); String number = getResultData(); if (currentNumber.startsWith("0" )) { setResultData(IPnumber + number); } } }
主函数 设置用户手动输入的 IPnumber
1 2 3 4 5 6 7 public void click (View v) { EditText et = (EditText) findViewById(R.id.et); String IPnumber = et.getText().toString().trim(); SharedPreferences sp = getSharedPreferences("cinfig" , 0 ); sp.edit().putString("IPnumber" , IPnumber).commit(); Toast.makeText(getApplicationContext, "保存成功" , 1 ).show(); }
SD卡状态的监听 1 2 3 4 5 6 7 8 <receiver android:name ="包名.AdcardStateReceiver" > <intent-filter > <action android:name ="android.intent.action.MEDIA_MOUNTED" /> <action android:name ="android.intent.action.MEDIA_UNMOUNTED" /> <data android:scheme ="file" /> </intent-filter > </receiver >
小细节:SD卡里面存的数据类型是 file
,所以需要 file 约束。另一个要配置data的广播事件是 安装应用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 import android.content.BroadcastReceiver;public class SdcardStateReceiver extends BroadcastReceiver { @Override public void onReceive (Context context, Intent intent) { Strning action = intent.getAction(); if ("android.intent.action.MEDIA_MOUNTED" .equals(action)) { } else if ("android.intent-filter.action.MEDIA_UNMOUNTED" .equals(action)) { } } }
短信监听器 权限:android.permission.RECEIVE_SMS
1 <action android:name ="android.provider.Telephony.SMS_RECEIVED" />
1 2 3 4 5 6 7 8 9 10 11 12 @Override public void onReceive (Context context, Intent intent) { Object[] objects = (Objectp[]) intent.getExtras().get("pdus" ); for (Object obj : objects) { SmsMessage smsMessage = SmsMessage.createFromPdu((byte []) boj); String messageBody = smsMessage.getMessageBody(); String address = smsMessage.getOriginatingAddress(); } }
卸载安装实例 这个也要配置 data
1 2 3 4 <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" />
1 2 3 4 5 6 7 8 9 10 11 12 13 14 public void onReceive (Context context, Intent intent) { String action = intent.getAction(); if ("android.intent.action.PACKAGE_INSTALL" .equals(action)) { } else if ("android.intent.action.PACKAGE_ADDED" .equals(action)) { String packageName = intent.getData(); } else if ("android.intent.action.PACKAGE_REMOVED" .equals(action)) { } }
手机重启实例 不能在广播接收者里面开启 activity 需要添加一个任务栈的标记
权限:anroid.permission.RECEIVE_BOOT_COMPLETED
1 <action android:name ="android.intent.action.BOOT_COMPLETED" />
1 2 3 4 5 6 7 8 9 10 11 12 13 public class BootReceiver extends BroadcastReceiver { @Override public void onReceive (Context context, Intent intent) { Intent intent2 = new Intent(context, MainActivity.class); intent2.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); context.startActivity(intent2); } }
1 2 3 4 5 @Override public void onBackPressed () { ; }
广播类型 有序广播:按照一定的优先级进行接收,在接收的过程中可以被修改或者终止
无序广播:不可以被修改或者终止
特殊广播:不能在清单文件中注册,需要动态注册
无序广播 发送无序广播
1 2 3 4 5 6 7 8 9 public void click (View v) { Intent intent = new Intent(); intent.setAction("com.HHH" ); intent.putExtra("name" , "HHH" ); sendBroadcast(intent); }
接收无序广播
1 <action name ="com.HHH" />
1 2 3 4 public onReceive (Context context, Intent intent) { String context = intent.getStringExtra("name" ); }
有序广播 发送有序广播
1 2 sendOrderedBroadcast(intent, null , null , null , 1 , "这是广播" , null );
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 <application ... > <activity > ... </activity > <receiver android:name ="包名.FirstReceiver" > <intent-filter android:priority ="1000" > <action android:name ="myReceiver" /> </intent-filter > </receiver > <receiver android:name ="包名.SecondReceiver" > <intent-filter android:priority ="0" > <action android:name ="myReceiver" /> </intent-filter > </receiver > <receiver android:name ="包名.LastReceiver" > <intent-filter android:priority ="-200" > <action android:name ="myReceiver" /> </intent-filter > </receiver > </application >
按顺序接收广播
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 public class FirstReceiver extends BroadcastReceiver { @Override public onReceive (Context context, Intent intent) { String content = getResultData(); setResultData("哈哈哈" ); } } public class SecondReceiver extends BroadcastReceiver { @Override public onReceive (Context context, Intent intent) { abortBroadcast(); } } public class LastReceiver extends BroadcastReceiver { @Override public onReceive (Context context, Intent intent) { } }
特殊广播接收者 操作特别频繁的广播事件,比如 屏幕的锁屏和解锁、电池电量的变化,这种事件的广播在清单文件里面是注册无效 的,可以动态注册广播
注册广播接受者的两种方式:
在清单文件通过 receiver tag 节点
动态注册:代码方式(即清单方式注册无效,并且无需注册)
类似于android.intent.action.SCREEN_ON
或 android.intent.action.SCREEN_OFF
这种,注册是无效的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 public class MainActivity extends Activity { ScreenReceiver screenreceiver = new ScreenReceiver(); @Override protected void onCreate (Bundle savedInstanceState) { super .onCreate(savedInstanceState); setContentView(R.layout.activity_main); IntentFilter filter = new IntentFilter(); filter.addAction("android.intent.action.SCREEN_OFF" ); filter.addAction("android.intent.action.SCREEN_ON" ); registerReceiver(screenReceiver, filter); } @Override protected void onDestory () { unregisterReceiver(screenreceiver); } } public class ScreenReceiver extends BroadcastReceiver { @Override public onReceive (Context context, Intent intent) { }
样式和主题 样式 样式一般作用在控件上
style 文件
1 2 3 4 5 <style name ="myStyle" > <item name ="android:layout_height" > match_parent</item > <item name ="android:textSize" > 20SP</item > <item name ="android:textColor" > #ff000</item > </style >
layout 文件
1 2 3 <TextView style ="@style/mystyle" android:text ="哈哈哈" />
主题 样式一般作用在 Activity 或 Application 节点下
style 文件
1 2 3 <style name ="myStyle" > <item name ="android:background" > #ff0000</item > </style >
manifest 文件
1 2 <application android:theme ="@style/mystyle" >
区别 两者定义的方式是一样的。
样式的作用范围比较窄,主题比较大。
不一定要在 style.xml 上定义,其他类似 txt 上也行。
Android 国际化 国际化:i18n
类似于android:text="你好世界"
这种硬编码的字符串,一般都是放到string.xml
里面
多国语言文件:res目录下的values-语言代码 文件夹里面的string.xml
(固定写法) 语言代码:en英语,zh中文简体,zh-Hant中文繁体…… 例如:/res/values-zh/string.xml
表示中文资源
对话框 常见对话框
Toast
普通对话框
单选对话框
多选对话框
进度条对话框
普通对话框 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 AlertDialog.Builder builder = new Builder(this ); builder.setTitle("提示" ); builder.setMessage("这里是提示内容" ); builder.setPositiveButton("确定" , new OnClickListener() { @Override public void onClick (DialogInterface dialog, int which) { System.out.println("点击了确定按钮" ); } }); builder.setNegativeButton("取消" , new OnClickListener() { @Override public void onClick (DialogInterface dialog, int which) { System.out.println("点击了取消按钮" ); } }); builder.show();
如果报错,则把new OnClickListener(){}
改成new DialogInterface.OnClickListener(){}
两种上下文的区别
this (即:类名.this)
getApplicationContext() 返回的是 Context 对象
getApplicationContext() 获取的是整个应用上下文,this 是其子类,子类有的父类不一定有。
this 多了token(令牌)。
就对话框来说,必须要用 this ,否则会报错。
单选对话框 1 2 3 4 5 6 7 8 9 10 11 12 13 AlertDialog.Builder builder = new Builder(this ); builder.setTitle("请选择" ); String items[] = { "item1" , "item2" }; builder.setSingleChoiceItems(items, -1 , new OnClickListener() { @Override public void onClick (DialogInterface dialog, int which) { System.out.println("您选择了" + items[which]); dialog.dismiss(); } }); builder.show();
多选对话框 注意:listener 是OnMultiChoiceClickListener
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 AlertDialog.Builder builder = new Builder(this ); String items[] = {"item1" , "item2" , "item3" }; Boolean[] checkedItems = {true , false , true }; builder.setMultiChoiceItems(items, checkedItems, new OnMultiChoiceClickListener() { @Override public void onClick (DialogInterface dialog, int which, boolean isChecked) { System.out.println("您点击了" + items[which]); dialog.dismiss(); } }); builder.setPositiveButton("确定" , new OnClickListener() { @Override public void onClick (DialogInterface dialog, int which) { StringBuffer sb = new StringBuffer(); for (int i = 0 ; i < checkedItems.length; i++) { if (checkedItems[i]) { sb.append(items[i] + " " ); } } Toast.makeText(getApplicationContext(), sb.toString(), 1 ).show(); dialog.dismiss(); } }); builder.show();
进度条对话框 1 2 3 4 5 ProgressDialog dialog = new ProgressDialog(this ); dialog.setTitle("正在加载中" ); dialog.setProgressStyle(style); dialog.show();
style 属性有:
ProgressDialog.STYLE_HORIZONTAL
横向进度条,0%
和进度相关的控件,都能在子线程中更新UI
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 ProgressDialog dialog = new ProgressDialog(this ); dialog.setTitle("正在加载中" ); dialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL); dialog.show(); new Thread() { public void run () { dialog.setMax(100 ); for (int i = 0 ; i < 100 ; i++) { SystemClock.sleep(50 ); dialog.setProgress(i); } dialog.dismiss(); } }.start();
帧动画 Android 中动画
帧动画:加载一系列的图片资源
xml 文件在res/drawable/directory
目录下,标签是<animation-list>
res/drawable/myanim.xml 文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 <animation-list xmlns:android ="..." androidLoneshot ="true" > <item android:drawable ="@drawable/pic_1" android:duration ="200" /> <item android:drawable ="@drawable/pic_2" android:duration ="200" /> <item android:drawable ="@drawable/pic_3" android:duration ="200" /> <item android:drawable ="@drawable/pic_4" android:duration ="200" /> </animation-list >
java文件
1 2 3 4 5 6 7 8 9 10 ImageView rocketImage = (ImageView) findViewById(R.id.rocketImage); rocketImage.setBackgroundResource(R.drawable.myanim); AnimationDrawable rocketAnimation = (AnimationDrawable) rocketImage.getBackground(); rocketAnimation.start();
兼容低版本 2.3
因为数据并未准备好,这个 API 是 2.3 之后出的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 ImageView rocketImage = (ImageView) findViewById(R.id.rocketImage); rocketImage.setBackgroundResource(R.drawable.myanim); new Thread(){ public void run () { SystemClock.sleep(100 ); AnimationDrawable rocketAnimation = (AnimationDrawable) rocketImage.getBackground(); rocketAnimation.start(); } }.start();
服务 服务在后台运行,没有界面
Service extends CpntextWrapper implements ...
进程
前台进程
可视进程
服务进程
后台进程
空进程
start方式开启服务 清单文件
1 2 3 4 <application ... > <activity ... > ... </activity > <service android:name ="包名.服务名" > </service > </application >
MyService.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 public class MyService extends Service { @Override public Ibinder onBind (Intent intent) { return null ; } @Override public void onCreate () { super .onCreate(); } @Override public int onStartCommand (Intent intent, int flags, int startId) { return super .StartCommand(intent, flags, startId); } @Override public void onDestroy () { super .onDestroy; } }
MainActivity.java
1 2 3 4 5 startService(new Intent(this , MyService.class)); stopService(new Intent(this , MyService.class));
start方式开启服务的特点
定义四大组件的方式是一样的
定义一个类继承Service
第一次开启服务,会执行 onCreate 和 onStart 方案
第二次开启服务,会执行 onStart 方法
服务一定开启,就会长期后台运行,直到用户手动停止
电话监听器
定义一个服务,开启服务。(记得在清单文件中配置服务)
在服务的 onCreate 里面获取 TelephonyManager
注册电话的监听
定义一个类用来判断电话的状态
TelephoneManager 监视电话的状态的改变
权限:<uses-permission android:name="android.permission.READ_PHONE_STATE" />
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 public class PhoneService extends Service { @Override Ibinder onBind (Intent intent) { return null ; } @Override public void onCreate () { TelephonyManager tm = (TelephonyManager)getSystemService(TELEPHONY_SERVICE); tm.listen(new MyPhoneStateListener(), PhoneStateListener.LISTEN_CALL_STAT) super .onCreate(); } @Override public void onDestroy () { super .onDestroy(); } private class MyPhoneStateListener extends PhoneStateListener { @Override public void onCallStateChanged (int state, String incomingNumber) { switch (state) { case TelephonyManager.CALL_STATE_IDLE: break ; case TelephonyManager.CALL_STATE_OFFHOOK: break ; case TelephonyManager.CALL_STATE_RINGING: break ; } } } {} }
录音机 录音需要权限:<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 MediaRecorder recorder = new MediaRecorder(); recorder.setAudioSource(MediaRecorder.AudioSource.MIC); recorder.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP); recorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB); recorder.setOutputFile("/mnt/sdcard/luyin.3gp" ); try { recorder.prepare(); } recorder.start(); if (recorder != null ){ recorder.stop(); recorder.reset(); recorder.release(); }
使用服务注册特殊的广播接收者
定义广播接收者
写个服务用来注册广播接收者
在MainActivity里面开启服务
一定要记得配置服务
MainActivity.java
1 2 3 4 onCreate() { Intent intent = newe Intent (this , ScreenService.class) ; startService(intent); }
ScreenService.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 private ScreenReceiver receiver = new ScreenReceiver();public void onCreate () { IntentFilter filter = new IntentFilter(); filter.addAction("android.intent.action.SCREEN_OFF" ); filter.addAction("android.intent.action.SCREEN_ON" ); registerReceiver(receiver, filter) super .onCreate(); } onDestroy() { unregisterReceiver(receiver); super .onDestroy(); }
ScreenReceiver.java
1 2 3 4 5 6 7 8 9 10 11 12 13 public class ScreenReceiver extends BroadcastReceiver { @Override public void onReceive (Context context, Intent intent) { String acation = intent.getAction(); if ("android.intent.action.SCREEN_OFF" .equals(action)) ; else if ("android.intent.action.SCREEN_ON" .equals(action)) ; } }
bind 方式开启服务 bindService(service, conn, flags);
点击按钮绑定服务
MainActivity.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 MyConn conn; public void click (View v) { Intent intent = new Intent(this , MyService.class); conn = new MyConn(); bindService(intent, conn, BIND_AUTI_CREATE); } private void MyConn extends ServiceConnection{ @Override public void onServiceConnected (ComponentName name, IBinder serviec) { ; } @Override public void onServiceDisconnected (ComponentName name) { ; } } protected void onDestroy () { unbindService(conn); super .onDestroy(); }
MyService.java
1 2 3 4 5 6 public IBinder onBinder (Intent intent) { }
bind方法开启服务的特点
点击按钮(或执行操作)后,会此项服务的onCreate方法和onBind方法
当onBind方法返回null的时候,onServiceConnected方法是不执行的
第二次点击按钮,服务没有响应
activity和service之间,不求同时生,但求同时死……
服务多次解绑会报异常
设置页面里面不能找到bind方式开启的服务,相当于隐形的服务
为什么要引入 bindService 在 Activity 里面调用 Service 的方法
Binder 是一个接口。
MyService.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 @Override public IBinder onbind (Intent intent) { return new MyBinder; } public void f () { ; } public class MyBinder extends Binder () { public void callF () { f(); } }
MainActivity.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 MyConn conn; private MyBinder myBinder; public void onCreate (...) { Intent intent = new Intent(this , MyService.class); conn = new MyConn(); bindService(intent, conn, BIND_AUTI_CREATE); } private void MyConn extends ServiceConnection{ @Override public void onServiceConnected (ComponentName name, IBinder serviec) { myBinder = (MyBinder) service; } @Override public void onServiceDisconnected (ComponentName name) { ; } } public void click () { myBinder.callF(); } protected void onDestroy () { unbindService(conn); super .onDestroy(); }
通过接口调用服务里面的方法 接口可以隐藏代码内部的细节,让程序员只暴露自己想暴露的方法
把想暴露的方法都定义在接口里面
Binder对象实现我们定义的方法
把获取到的IBinder对象转换成暴露部分方法的接口
Iservice.java (I 开头表示是一个接口)
1 2 3 4 public interface Iservice { public void callF1 () ; }
MyService.java
1 2 3 4 5 6 7 8 9 10 public void f1 () { }public void f2 () { }private class MyBinder extends Binder implements Iservice { public void callF1 () { } public void callF2 () { } }
MainActivity.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 private MyConn conn;private Iservice myBinder; private calss MyConn implements ServiceConnection { @Override public void onServiceConnected (ComponentName name, IBinder service) { myBinder = (Iservice) service; } } public void click (View v) { myBinder.callF1(); }
混合方式开启服务 既能在后台长期运行,又能调用服务里面的方法
start 方法开启服务
调用 bindService 获取中间对象
调用 unbindService 解绑服务
彻底退出时 stopService
百度音乐盒框架 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 public void MainActivity extends Activity { private MyConn conn; private Iservice iservice; @Override protected void onCreate (Bundle savedInstanceState) { super .onCreate(savedInstanceState); setContentVIew(R.layout.activity_main); Intent intent = new Intent(this , MusicService.class); startService(intent); conn = new MyConn(); bindService(intent, conn, BIND_AUTO_CREATE); } private class MyConn { @Override public void onServiceConnected (ComponentName name, IBinder service) { iservice = (Iservice) service; } } public void playButton (View v) { iservice.callPlayMusic(); } public void pauseButton (View v) { iservice.callPauseMusic(); } public void rePlayButton (View v) { iservice.callRePlayMusic(); } @Override protected void onDestroy () { unbindService(conn); super .onDestroy(); } } public class MusicService extends Service { @Override public Ibinder onBind (Intent intent) { return null ; } @Override public void onCreate () { super .onCreate(); } @Override public int onStartCommand (Intent intent, int flags, int startId) { return super .StartCommand(intent, flags, startId); } @Override public void onDestroy () { super .onDestroy; } public void playMusic () { ; } @Override public void pauseMusic () { ; } @Override public void rePlayMusic () { ; } private class MyBinder extends Binder implements Iservice { @Override public void callPlayMusic () { playMusic(); } @Override public void callPauseMusic () { pauseMusic(); } @Override public void callRePlayMusic () { rePlayMusic(); } } } public interface Iservice { public void callPlayMusic () ; public void callPauseMusic () ; public void callRePlayMusic () ; }
AIDL 介绍
本地服务:运行在自己应用里面的服务
远程服务:运行在其他应用里面的服务
实现进程间通讯(IPC)
AIDL:安卓接口定义语言,专门用来解决进程间通讯
每个应用都要配置服务。其中一个定义 IBinder
把 Iservice.java 文件重命名为 Iservice.aidl。
aidl 不认识 public,所以这个关键词要去掉
会自动生成Iservice.java,里面有个类叫 Stub
MyBinder 直接继承 Stub
两个应用的 aidl 文件包名相同
Stub.asInterface(service);
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 Iservice iservice; onCreate() { Intent intent = new Intent(); Intent.setAction("目标应用的包名" ); MyConn conn = new MyConn(); bindService(intent, conn, BIND_QUTO_CREATE); } private class MyConn implements ServiceConnection { @Override public void onServiceConnected (ComponentName name, IBinder service) { iservice = Stub.asInterface(service); } } public void click (View v) { iservice.callF(); }
1 2 3 4 5 6 7 private class MyBinder extends Stub { public void callF () { f(); } }
Iservice.aidl
1 2 3 interface Iservice { void f () ; }
AIDL 的应用场景 类似调用支付宝进行支付
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 import android.os.Bundle;public class MainActivity extends Activity { private MyCon conn; private Iservice iservice; @Override protected void onCreate (Bundle savedInstanceState) { super .onCreate(savedInstanceState); setContentView(R.layout.activity_main); Intent intent = new Intent(); intent.setAction("com.test.pay" ); conn = new MyConn(); bindService(intent, conn, BIND_AUTO_CREATE); } public void payClick () { try { boolean result = iservice.callPay("abc" , "123" , 1000 ); if (result) { Toast.makeText(getApplicationContext(), "支付成功" , 1 ).show(); } else { Toast.makeText(getApplicationContext(), "支付失败" , 1 ).show(); } } catch (RemoteException e) { e.printStackTrace(); } } private class MyConn implements ServiceConnection { @Override public void onServiceConnected (ComponentName name, IBinder service) { iservice = Stub.asInterface(service); } @Override public void onServiceDisconnected (ComponentName name) { ; } } @Override protected void onDestroy () { unbinderService(conn); super .onDestroy() } } import android.app.Service;public class PayService extends Service { @Override publci IBinder onBind (Intent intent) { return new MyBinder(); } publci void pay (String username, String pwd, int money) { if ("abc" . equals(username) && "123" . equals(pwd) && money <= 5000 ) { return true ; } else { return false ; } } } private class MyBinder extends Stub /*Binder implements Iservice */ { @Override public boolean callPay (String username, String pwd, int money) { return pay(username, pwd, money); } } interface Iservice { boolean callPay (String username, String pwd, int money) { pay(); } } <service android:name="com.test.pay.PayService" > <intent-filter> <action android:name="com.test.pay" /> </intent-filter> </service>
内容提供者(contentProvider) 作用:在app2中读取app1的数据库(暴露私有的数据库)
使用:
建一个类,继承 contentProvider
。
在清单文件中配置
添加静态代码块
暴露你想暴露的方法(增删改查)
其他应用就能用内容解析者去操作数据库
暴露方法 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 import android.content.ContentProvider;public class MyProvider extends ContentProvider { private static final UriMather sURIMather = new UriMather(UriMather.NO_MATH); private static final int QUERYSUCCESS = 0 ; private MyOpenHelper myOpenHelper; static { sURIMather.addURI("mystr" , "query" , QUERYSUCCESS); } @Override public boolean onCreate () { myOpenHelper = new MyOpenHelper(getContext()); return false ; } @Override public Cursor query (Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) { int code = sURIMather.math(uri); if (code == QUERYSUCCESS) { SQLiteDatabase db = myOpenHelper.getReadableDatabase(); Cursor cursor = db.query("info" , projection, selection, selectionArgs, null , null . sortOrder); return cursor; } else { throw new IlleagalArgumentException("路径不匹配" ); } } @Override public String getType (Uri uri) { return null ; } @Override public Uri insert (Uri uri, ContentValues values) { return null ; } }
1 2 3 <provider android:name ="com.app1.MyProvider" android:authorities ="mystr" > </provider >
app2读取app1的数据库 由于app1里面的私有数据库已经通过内容提供者的方式暴露出来了,所以app2可以直接通过内容的解析这进行访问
1 2 3 4 5 6 7 8 9 10 11 12 13 Uri uri = Uri.parse("content://mystr/query" ); Cursor cursor = getContentResolver().query(uri, null , null , null , null ); if (cursor != null && cursor.getCount() > 0 ) { while (cursor.moveToNext()) { String name = sursor.getString(1 ); String phone = cursor.getString(2 ); System.out.println("app2 name:" +name+";phone:" +phone); } }
暴露增删改查 app1
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 import android.content.ContentProvider;public class MyProvider extends ContentProvider { private static final UriMather sURIMather = new UriMather(UriMather.NO_MATH); private static final int QUERYSUCCESS = 0 ; private static final int INSERTSUCCESS = 1 ; private static final int UPDATESUCCESS = 2 ; private static final int DELETESUCCESS = 3 ; private MyOpenHelper myOpenHelper; static { sURIMather.addURI("mystr" , "query" , QUERYSUCCESS); sURIMather.addURI("mystr" , "insert" , INSERTSUCCESS); sURIMather.addURI("mystr" , "update" , UPDATESUCCESS); sURIMather.addURI("mystr" , "delete" , DELETESUCCESS); } @Override public boolean onCreate () { myOpenHelper = new MyOpenHelper(getContext()); return false ; } @Override public Cursor query (Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) { int code = sURIMather.math(uri); if (code == QUERYSUCCESS) { SQLiteDatabase db = myOpenHelper.getReadableDatabase(); Cursor cursor = db.query("info" , projection, selection, selectionArgs, null , null . sortOrder); return cursor; } else { throw new IlleagalArgumentException("路径不匹配" ); } } @Override public String getType (Uri uri) { return null ; } @Override public Uri insert (Uri uri, ContentValues values) { int code = sURIMather.match(uri); if (code == INSERTSUCCESS) { SQLiteDatabase db = myOpenHelper.getReadableDatabase(); long ins = db.insert("info" , null , values); Uri uri2 = Uri.parse("插入的行号:" + ins); return uri2; } else { throw new IlleagalArgumentException("路径不匹配" ); } return null ; } @Override public int delete (Uri uri, String selection, String[] selectionArgs) { int code = sURIMather.match(uri); if (code == DELETESUCCESS) { SQLiteDatabase db = myOpenHelper.getReadableDatabase(); int del = db.delete("info" , selection, selectionArgs); return del; } else { throw new IlleagalArgumentException("路径不匹配" ); } return null ; } @Override public int delete (Uri uri, ContentValues values, String selection, String[] selectionArgs) { int code = sURIMather.match(uri); if (code == DELETESUCCESS) { SQLiteDatabase db = myOpenHelper.getReadableDatabase(); int upd = db.delete("info" , values, selection, selectionArgs); return upd; } else { throw new IlleagalArgumentException("路径不匹配" ); } return null ; } }
app2
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 public void toInsert () { Uri uri = Uri.parse("cintent://mystr/insert" ); ContentValues values = new ContentValues(); values.put("name" , "123" ); values.put("money" , "100" ); Uri ins = getContentResolver().insert(uri, values); System.out.println("insert:" +ins); } public void toDelete () { Uri uri = Uri.parse("cintent://mystr/delete" ); int del = getContentResolver().insert(uri, "name=?" , new String[]{"asd" }); Toast.makeText(getApplicationContext(), "delete:" +del, 1 ).show(); } public void toUpdate () { Uri uri = Uri.parse("cintent://mystr/insert" ); ContentValues values = new ContentValues(); values.put("money" , "100" ); int upd = getContentResolver().update(uri, values, "nname=?" , new String[]{"asd" }); Toast.makeText(getApplicationContext(), "update:" +upd, 1 ).show(); }
备份短信 权限:WRITE_SMS
READ_SMS
READ_EXTERNAL_STORAGE
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 public sms_backup () { XmlSerializer serializer = Xml.newSerializer(); File file = new File(Environment.getExternalStorageDirectory().getPath(), "smsBackup.xml" ); FileOutputStream fos = new FileOutputStream(file); serializer.setOutput(fos, "utf-8" ); serializer.startDocument("utf-8" , true ); { serializer.startTag(null , "smss" ); { 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 ); serializer.startTag(null , "sms" ); { serializer.startTag(null , "address" ); serializer.text(address); serializer.endTag(null , "address" ); serializer.startTag(null , "body" ); serializer.text(body); serializer.endTag(null , "body" ); serializer.startTag(null , "date" ); serializer.text(date); serializer.endTag(null , "date" ); } serializer.endTag(null , "sms" ); } } serializer.endTag(null , "smss" ); } serializer.endDocument(); }
插入短信 通过内容提供者在短信的私有数据库中插入一条短信
权限:WRITE_SMS
READ_SMS
1 2 3 4 5 6 Uri uri = Uri.parse("content://sms" ); ContentValues values = new ContentValues(); values.put("address" , "110" ); values.put("body" , "报警" ); values.put("date" , System.currentTimeMillis()); getContentResolver().insert(uri, values);