首页 文章 精选 留言 我的

精选列表

搜索[web开发框架],共10000篇文章
优秀的个人博客,低调大师

Android开发学习笔记:数据存取之SQLite浅析

一.SQLite的介绍 1.SQLite简介 SQLite是一款轻型的数据库,是遵守ACID的关联式数据库管理系统,它的设计目标是嵌入 式的,而且目前已经在很多嵌入式产品中使用了它,它占用资源非常的低,在嵌入式设备中,可能只需要几百K的内存就够了。它能够支持 Windows/Linux/Unix等等主流的操作系统,同时能够跟很多程序语言相结合,比如Tcl、PHP、Java、C++、.Net等,还有ODBC接口,同样比起 Mysql、PostgreSQL这两款开源世界著名的数据库管理系统来讲,它的处理速度比他们都快。 2.SQLite的特点: 轻量级 SQLite和C/S模式的数据库软件不同,它是进程内的数据库引擎,因此不存在数据库的客户端和服务器。使用SQLite一般只需要带上它的一个动态 库,就可以享受它的全部功能。而且那个动态库的尺寸也挺小,以版本3.6.11为例,Windows下487KB、Linux下347KB。 不需要"安装" SQLite的核心引擎本身不依赖第三方的软件,使用它也不需要"安装"。有点类似那种绿色软件。 单一文件 数据库中所有的信息(比如表、视图等)都包含在一个文件内。这个文件可以自由复制到其它目录或其它机器上。 跨平台/可移植性 除了主流操作系统 windows,linux之后,SQLite还支持其它一些不常用的操作系统。 弱类型的字段 同一列中的数据可以是不同类型 开源 这个相信大家都懂的! 3.SQLite数据类型 一般数据采用的固定的静态数据类型,而SQLite采用的是动态数据类型,会根据存入值自动判断。SQLite具有以下五种常用的数据类型: NULL: 这个值为空值 VARCHAR(n):长度不固定且其最大长度为 n 的字串,n不能超过 4000。 CHAR(n):长度固定为n的字串,n不能超过 254。 INTEGER: 值被标识为整数,依据值的大小可以依次被存储为1,2,3,4,5,6,7,8. REAL: 所有值都是浮动的数值,被存储为8字节的IEEE浮动标记序号. TEXT: 值为文本字符串,使用数据库编码存储(TUTF-8, UTF-16BE or UTF-16-LE). BLOB: 值是BLOB数据块,以输入的数据格式进行存储。如何输入就如何存储,不改 变格式。 DATA :包含了 年份、月份、日期。 TIME:包含了 小时、分钟、秒。 相信学过数据库的童鞋对这些数据类型都不陌生的!!!!!!!!!! 二.SQLiteDatabase的介绍 Android提供了创建和是用SQLite数据库的API。SQLiteDatabase代表一个数据库对象,提供了操作数据库的一些方法。在Android的SDK目录下有sqlite3工具,我们可以利用它创建数据库、创建表和执行一些SQL语句。下面是SQLiteDatabase的常用方法。 SQLiteDatabase的常用方法 方法名称 方法描述 openOrCreateDatabase(String path,SQLiteDatabase.CursorFactory factory) 打开或创建数据库 insert(String table,String nullColumnHack,ContentValues values) 添加一条记录 delete(String table,String whereClause,String[] whereArgs) 删除一条记录 query(String table,String[] columns,String selection,String[] selectionArgs,String groupBy,String having,String orderBy) 查询一条记录 update(String table,ContentValues values,String whereClause,String[] whereArgs) 修改记录 execSQL(String sql) 执行一条SQL语句 close() 关闭数据库 1、打开或者创建数据库 在Android 中以使用SQLiteDatabase的静态方法openOrCreateDatabase(String path,SQLiteDatabae.CursorFactory factory)打开或者创建一个数据库。它会自动去检测是否存在这个数据库,如果存在则打开,不存在则创建一个数据库;创建成功则返回一个SQLiteDatabase对象,否则抛出异常FileNotFoundException。 下面是创建名为“stu.db”数据库的代码: db=SQLiteDatabase.openOrCreateDatabase("/data/data/com.lingdududu.db/databases/stu.db",null); 2、创建表 创建一张表很简单。首先,编写创建表的SQL语句,然后,调用SQLiteDatabase的execSQL()方法来执行SQL语句便可以创建一张表了。 下面的代码创建了一张用户表,属性列为:_id(主键并且自动增加)、sname(学生姓名)、snumber(学号) privatevoidcreateTable(SQLiteDatabasedb){ //创建表SQL语句 Stringstu_table="createtableusertable(_idintegerprimarykeyautoincrement,snametext,snumbertext)"; //执行SQL语句 db.execSQL(stu_table); } 3、插入数据 插入数据有两种方法: ①SQLiteDatabase的insert(String table,String nullColumnHack,ContentValues values)方法,参数一是表名称,参数二是空列的默认值,参数三是ContentValues类型的一个封装了列名称和列值的Map; ②编写插入数据的SQL语句,直接调用SQLiteDatabase的execSQL()方法来执行 第一种方法的代码: privatevoidinsert(SQLiteDatabasedb){ //实例化常量值 ContentValuescValue=newContentValues(); //添加用户名 cValue.put("sname","xiaoming"); //添加密码 cValue.put("snumber","01005"); //调用insert()方法插入数据 db.insert("stu_table",null,cValue); } 第二种方法的代码: privatevoidinsert(SQLiteDatabasedb){ //插入数据SQL语句 Stringstu_sql="insertintostu_table(sname,snumber)values('xiaoming','01005')"; //执行SQL语句 db.execSQL(sql); } 4、删除数据 删除数据也有两种方法: ①调用SQLiteDatabase的delete(String table,String whereClause,String[] whereArgs)方法,参数一是表名称,参数二是删除条件,参数三是删除条件值数组; ②编写删除SQL语句,调用SQLiteDatabase的execSQL()方法来执行删除。 第一种方法的代码: privatevoiddelete(SQLiteDatabasedb){ //删除条件 StringwhereClause="_id=?"; //删除条件参数 String[]whereArgs={String.valueOf(2)}; //执行删除 db.delete("stu_table",whereClause,whereArgs); } 第二种方法的代码: privatevoiddelete(SQLiteDatabasedb){ //删除SQL语句 Stringsql="deletefromstu_tablewhere_id=6"; //执行SQL语句 db.execSQL(sql); } 5、修改数据 修改数据有两种方法: ①调用SQLiteDatabase的update(String table,ContentValues values,String whereClause, String[] whereArgs)方法。参数是表名称,参数是更行列ContentValues类型的键值对(Map),参数是更新条件(where字句),参数是更新条件数组。 ②编写更新的SQL语句,调用SQLiteDatabase的execSQL执行更新。 第一种方法的代码: privatevoidupdate(SQLiteDatabasedb){ //实例化内容值 ContentValuesvalues=newContentValues(); //在values中添加内容 values.put("snumber","101003"); //修改条件 StringwhereClause="id=?"; //修改添加参数 String[]whereArgs={String.valuesOf(1)}; //修改 db.update("usertable",values,whereClause,whereArgs); } 第二种方法的代码: privatevoidupdate(SQLiteDatabasedb){ //修改SQL语句 Stringsql="updatestu_tablesetsnumber=654321whereid=1"; //执行SQL db.execSQL(sql); } 6、查询数据 在Android中查询数据是通过Cursor类来实现的,当我们使用SQLiteDatabase.query()方法时,会得到一个Cursor对象,Cursor指向的就是每一条数据。它提供了很多有关查询的方法,具体方法如下: public Cursor query(String table,String[] columns,String selection,String[] selectionArgs,String groupBy,String having,String orderBy,String limit); 各个参数的意义说明: ①table:表名称 ②columns:列名称数组 ③selection:条件字句,相当于where ④selectionArgs:条件字句,参数数组 ⑤groupBy:分组列 ⑥having:分组条件 ⑦orderBy:排序列 ⑧limit:分页查询限制 ⑨Cursor:返回值,相当于结果集ResultSet Cursor是一个游标接口,提供了遍历查询结果的方法,如移动指针方法move(),获得列值方法getString()等. Cursor游标常用方法 方法名称 方法描述 getCount() 获得总的数据项数 isFirst() 判断是否第一条记录 isLast() 判断是否最后一条记录 moveToFirst() 移动到第一条记录 moveToLast() 移动到最后一条记录 move(int offset) 移动到指定记录 moveToNext() 移动到下一条记录 moveToPrevious() 移动到上一条记录 getColumnIndexOrThrow(String columnName) 根据列名称获得列索引 getInt(int columnIndex) 获得指定列索引的int类型值 getString(int columnIndex) 获得指定列缩影的String类型值 下面就是用Cursor来查询数据库中的数据,具体代码如下: privatevoidquery(SQLiteDatabasedb) { //查询获得游标 Cursorcursor=db.query("usertable",null,null,null,null,null,null); //判断游标是否为空 if(cursor.moveToFirst(){ //遍历游标 for(inti=0;i<cursor.getCount();i++){ cursor.move(i); //获得ID intid=cursor.getInt(0); //获得用户名 Stringusername=cursor.getString(1); //获得密码 Stringpassword=cursor.getString(2); //输出用户信息 System.out.println(id+":"+sname+":"+snumber); } } } 7、删除指定表 编写插入数据的SQL语句,直接调用SQLiteDatabase的execSQL()方法来执行 privatevoiddrop(SQLiteDatabasedb){ //删除表的SQL语句 Stringsql="DROPTABLEstu_table"; //执行SQL db.execSQL(sql); } 三.SQLiteOpenHelper 该类是SQLiteDatabase一个辅助类。这个类主要生成一 个数据库,并对数据库的版本进行管理。当在程序当中调用这个类的方法getWritableDatabase()或者 getReadableDatabase()方法的时候,如果当时没有数据,那么Android系统就会自动生成一个数据库。 SQLiteOpenHelper 是一个抽象类,我们通常需要继承它,并且实现里面的3个函数: 1.onCreate(SQLiteDatabase) 在数据库第一次生成的时候会调用这个方法,也就是说,只有在创建数据库的时候才会调用,当然也有一些其它的情况,一般我们在这个方法里边生成数据库表。 2. onUpgrade(SQLiteDatabase,int,int) 当数据库需要升级的时候,Android系统会主动的调用这个方法。一般我们在这个方法里边删除数据表,并建立新的数据表,当然是否还需要做其他的操作,完全取决于应用的需求。 3. onOpen(SQLiteDatabase): 这是当打开数据库时的回调函数,一般在程序中不是很常使用。 写了这么多,改改用实际例子来说明上面的内容了。下面这个操作数据库的实例实现了创建数据库,创建表以及数据库的增删改查的操作。 SQLiteActivity.java packagecom.lingdududu.testSQLite; importcom.lingdududu.testSQLiteDb.StuDBHelper; importandroid.app.Activity; importandroid.content.ContentValues; importandroid.database.Cursor; importandroid.database.sqlite.SQLiteDatabase; importandroid.os.Bundle; importandroid.view.View; importandroid.view.View.OnClickListener; importandroid.widget.Button; /* *@authorlingdududu */ publicclassSQLiteActivityextendsActivity{ /**Calledwhentheactivityisfirstcreated.*/ //声明各个按钮 privateButtoncreateBtn; privateButtoninsertBtn; privateButtonupdateBtn; privateButtonqueryBtn; privateButtondeleteBtn; privateButtonModifyBtn; @Override publicvoidonCreate(BundlesavedInstanceState){ super.onCreate(savedInstanceState); setContentView(R.layout.main); //调用creatView方法 creatView(); //setListener方法 setListener(); } //通过findViewById获得Button对象的方法 privatevoidcreatView(){ createBtn=(Button)findViewById(R.id.createDatabase); updateBtn=(Button)findViewById(R.id.updateDatabase); insertBtn=(Button)findViewById(R.id.insert); ModifyBtn=(Button)findViewById(R.id.update); queryBtn=(Button)findViewById(R.id.query); deleteBtn=(Button)findViewById(R.id.delete); } //为按钮注册监听的方法 privatevoidsetListener(){ createBtn.setOnClickListener(newCreateListener()); updateBtn.setOnClickListener(newUpdateListener()); insertBtn.setOnClickListener(newInsertListener()); ModifyBtn.setOnClickListener(newModifyListener()); queryBtn.setOnClickListener(newQueryListener()); deleteBtn.setOnClickListener(newDeleteListener()); } //创建数据库的方法 classCreateListenerimplementsOnClickListener{ @Override publicvoidonClick(Viewv){ //创建StuDBHelper对象 StuDBHelperdbHelper=newStuDBHelper(SQLiteActivity.this,"stu_db",null,1); //得到一个可读的SQLiteDatabase对象 SQLiteDatabasedb=dbHelper.getReadableDatabase(); } } //更新数据库的方法 classUpdateListenerimplementsOnClickListener{ @Override publicvoidonClick(Viewv){ //数据库版本的更新,由原来的1变为2 StuDBHelperdbHelper=newStuDBHelper(SQLiteActivity.this,"stu_db",null,2); SQLiteDatabasedb=dbHelper.getReadableDatabase(); } } //插入数据的方法 classInsertListenerimplementsOnClickListener{ @Override publicvoidonClick(Viewv){ StuDBHelperdbHelper=newStuDBHelper(SQLiteActivity.this,"stu_db",null,1); //得到一个可写的数据库 SQLiteDatabasedb=dbHelper.getWritableDatabase(); //生成ContentValues对象//key:列名,value:想插入的值 ContentValuescv=newContentValues(); //往ContentValues对象存放数据,键-值对模式 cv.put("id",1); cv.put("sname","xiaoming"); cv.put("sage",21); cv.put("ssex","male"); //调用insert方法,将数据插入数据库 db.insert("stu_table",null,cv); //关闭数据库 db.close(); } } //查询数据的方法 classQueryListenerimplementsOnClickListener{ @Override publicvoidonClick(Viewv){ StuDBHelperdbHelper=newStuDBHelper(SQLiteActivity.this,"stu_db",null,1); //得到一个可写的数据库 SQLiteDatabasedb=dbHelper.getReadableDatabase(); //参数1:表名 //参数2:要想显示的列 //参数3:where子句 //参数4:where子句对应的条件值 //参数5:分组方式 //参数6:having条件 //参数7:排序方式 Cursorcursor=db.query("stu_table",newString[]{"id","sname","sage","ssex"},"id=?",newString[]{"1"},null,null,null); while(cursor.moveToNext()){ Stringname=cursor.getString(cursor.getColumnIndex("sname")); Stringage=cursor.getString(cursor.getColumnIndex("sage")); Stringsex=cursor.getString(cursor.getColumnIndex("ssex")); System.out.println("query------->"+"姓名:"+name+""+"年龄:"+age+""+"性别:"+sex); } //关闭数据库 db.close(); } } //修改数据的方法 classModifyListenerimplementsOnClickListener{ @Override publicvoidonClick(Viewv){ StuDBHelperdbHelper=newStuDBHelper(SQLiteActivity.this,"stu_db",null,1); //得到一个可写的数据库 SQLiteDatabasedb=dbHelper.getWritableDatabase(); ContentValuescv=newContentValues(); cv.put("sage","23"); //where子句"?"是占位符号,对应后面的"1", StringwhereClause="id=?"; String[]whereArgs={String.valueOf(1)}; //参数1是要更新的表名 //参数2是一个ContentValeus对象 //参数3是where子句 db.update("stu_table",cv,whereClause,whereArgs); } } //删除数据的方法 classDeleteListenerimplementsOnClickListener{ @Override publicvoidonClick(Viewv){ StuDBHelperdbHelper=newStuDBHelper(SQLiteActivity.this,"stu_db",null,1); //得到一个可写的数据库 SQLiteDatabasedb=dbHelper.getReadableDatabase(); StringwhereClauses="id=?"; String[]whereArgs={String.valueOf(2)}; //调用delete方法,删除数据 db.delete("stu_table",whereClauses,whereArgs); } } } StuDBHelper.java packagecom.lingdududu.testSQLiteDb; importandroid.content.Context; importandroid.database.sqlite.SQLiteDatabase; importandroid.database.sqlite.SQLiteDatabase.CursorFactory; importandroid.database.sqlite.SQLiteOpenHelper; importandroid.util.Log; publicclassStuDBHelperextendsSQLiteOpenHelper{ privatestaticfinalStringTAG="TestSQLite"; publicstaticfinalintVERSION=1; //必须要有构造函数 publicStuDBHelper(Contextcontext,Stringname,CursorFactoryfactory, intversion){ super(context,name,factory,version); } //当第一次创建数据库的时候,调用该方法 publicvoidonCreate(SQLiteDatabasedb){ Stringsql="createtablestu_table(idint,snamevarchar(20),sageint,ssexvarchar(10))"; //输出创建数据库的日志信息 Log.i(TAG,"createDatabase------------->"); //execSQL函数用于执行SQL语句 db.execSQL(sql); } //当更新数据库的时候执行该方法 publicvoidonUpgrade(SQLiteDatabasedb,intoldVersion,intnewVersion){ //输出更新数据库的日志信息 Log.i(TAG,"updateDatabase------------->"); } } main.xml <?xmlversion="1.0"encoding="utf-8"?> <LinearLayoutxmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="fill_parent" android:layout_height="fill_parent" > <TextView android:layout_width="fill_parent" android:layout_height="wrap_content" android:text="@string/hello" /> <Button android:id="@+id/createDatabase" android:layout_width="fill_parent" android:layout_height="wrap_content" android:text="创建数据库" /> <Button android:id="@+id/updateDatabase" android:layout_width="fill_parent" android:layout_height="wrap_content" android:text="更新数据库" /> <Button android:id="@+id/insert" android:layout_width="fill_parent" android:layout_height="wrap_content" android:text="插入数据" /> <Button android:id="@+id/update" android:layout_width="fill_parent" android:layout_height="wrap_content" android:text="更新数据" /> <Button android:id="@+id/query" android:layout_width="fill_parent" android:layout_height="wrap_content" android:text="查询数据" /> <Button android:id="@+id/delete" android:layout_width="fill_parent" android:layout_height="wrap_content" android:text="删除数据" /> </LinearLayout> 程序运行的效果图: 当点击创建数据库按钮: LogCat窗口输出的信息为: 当点击更新数据库按钮: LogCat窗口输出的信息为: 当点击查询数据库按钮: LogCat窗口输出的信息为: 使用adb命令查看数据库: 1.在命令行窗口输入adb shell回车,就进入了Linux命令行,现在就可以使用Linux的命令了。 2.ls回车,显示所有的东西,其中有个data。(ls:显示所有,cd:进入) 3.cd data回车,再ls回车,cd data回车,ls回车后就会看到很多的com.。。。,那就是系统上的应用程序包名,找到你数据库程序的包名,然后进入。 4.进去后在查看所有,会看到有databases,进入databases,显示所有就会发现你的数据库名字,这里使用的是"stu_db"。 5.sqlite3 stu_db回车就进入了你的数据库了,然后“.schema”就会看到该应用程序的所有表及建表语句。 6.之后就可以使用标准的SQL语句查看刚才生成的数据库及对数据执行增删改查了。 #sqlite3 stu_dbsqlite3 stu_dbSQLite version 3.6.22 Enter ".help" for instructions Enter SQL statements terminated with a ";" sqlite>.schema.schema CREATE TABLE android_metadata (locale TEXT); CREATE TABLEstu_table(id int,sname varchar(20),sage int,ssex varchar(10));--->创建的表 sqlite>select * from stu_table; select * from stu_table; 1|xiaoming|21|male sqlite> 插入数据 sqlite>insert into stu_table values(2,'xiaohong',20,'female'); 插入的数据记得要和表中的属性一一对应 insert into stu_table values(2,'xiaohong',20,'female'); sqlite> select * from stu_table; select * from stu_table; 1|xiaoming|21|male 2|xiaohong|20|female--------------> 插入的数据sqlite> 当点击修改数据的按钮时候 sqlite> select * from stu_table; select * from stu_table; 1|xiaoming|23|male-------------->年龄被修改为232|xiaohong|20|female sqlite> 当点击删除数据的按钮 sqlite> select * from stu_table; select * from stu_table; 1|xiaoming|23|maleid=2的数据已经被删除 总之,我们可以在代码中执行数据库的增删改查,也可以在adb命令行下实现。不过因为SQLite没有客户端,不能直接的查看数据库变化后的信息,所以常用adb命令行查看数据库改变后的信息。 本文转自 lingdududu 51CTO博客,原文链接: http://blog.51cto.com/liangruijun/663686

优秀的个人博客,低调大师

Android开发学习笔记:BroadcastReceiver简介和注册方式

一.BroadcastReceiver简介 BraodcastReceiver(广播接收器)是为了实现系统广播而提供的一种组件,它和事件处理机制类似,但是事件处理机制是程序组件级别的,而广播事件处理机制是系统级别的。比如,我们可以发出一种广播来测试手机电量的变化,这时候就可以定义一个BraodcastReceiver来接受广播,当手机电量较低时提示用户。我们既可以用Intent来启动一个组件,也可以用sendBroadcast()方法发起一个系统级别的事件广播来传递消息。我们同样可以在自己的应用程序中实现BroadcastReceiver来监听和响应广播的Intent。 在程序中使用BraodcastReceiver是比较简单的。首先要定义一个类继承BraodcastReceiver,并且覆盖onReceiver()方法来响应事件。然后注册在程序中BraodcastReceiver。最后构建Intent对象调用sendBroadcast()方法将广播发出。 二.BroadcastReceiver的注册方式 1.静态注册方式 静态注册方式是在AndroidManifest.xml的application里面定义receiver并设置要接收的action。静态注册方式的特点:不管改应用程序是否处于活动状态,都会进行监听,比如某个程序时监听 下面是具体的例子: MainActivity.java packagecom.android.broadcast; importandroid.app.Activity; importandroid.content.Intent; importandroid.os.Bundle; importandroid.view.View; importandroid.view.View.OnClickListener; importandroid.widget.Button; publicclassMainActivityextendsActivity{ //定义action常量 protectedstaticfinalStringACTION="com.android.broadcast.RECEIVER_ACTION"; //定义Button对象 privateButtonbtnBroadcast; @Override publicvoidonCreate(BundlesavedInstanceState){ super.onCreate(savedInstanceState); setContentView(R.layout.main); btnBroadcast=(Button)findViewById(R.id.btnBroadcast); //为按钮设置单击监听器 btnBroadcast.setOnClickListener(newOnClickListener(){ @Override publicvoidonClick(Viewv){ //实例化Intent Intentintent=newIntent(); //设置Intent的action属性 intent.setAction(ACTION); //发出广播 sendBroadcast(intent); } }); } } 在“com.android.broadcast”包中定义一个MyReceiver类,继承于BroadcastReceiver,覆盖onReceive()方法。 MyReceiver.java packagecom.android.broadcast; importandroid.content.BroadcastReceiver; importandroid.content.Context; importandroid.content.Intent; importandroid.util.Log; publicclassMyReceiverextendsBroadcastReceiver{ //定义日志标签 privatestaticfinalStringTAG="Test"; @Override publicvoidonReceive(Contextcontext,Intentintent){ //输出日志信息 Log.i(TAG,"MyReceiveronReceive--->"); } } main.xml <?xmlversion="1.0"encoding="utf-8"?> <LinearLayoutxmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="fill_parent" android:layout_height="fill_parent" > <Button android:id="@+id/btnBroadcast" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="发送Broadcast" /> </LinearLayout> 在AndroidManifest.xml配置文件中16~20行声明receiver <?xmlversion="1.0"encoding="utf-8"?> <manifestxmlns:android="http://schemas.android.com/apk/res/android" package="com.android.broadcast" android:versionCode="1" android:versionName="1.0"> <uses-sdkandroid:minSdkVersion="10"/> <applicationandroid:icon="@drawable/icon"android:label="@string/app_name"> <activityandroid:name=".MainActivity" android:label="@string/app_name"> <intent-filter> <actionandroid:name="android.intent.action.MAIN"/> <categoryandroid:name="android.intent.category.LAUNCHER"/> </intent-filter> </activity> <receiverandroid:name="MyReceiver"> <intent-filter> <actionandroid:name="com.android.broadcast.RECEIVER_ACTION"/> </intent-filter> </receiver> </application> </manifest> 效果图: 当我们点击按钮的时候,程序会调用onReceive()方法,LogCat输出信息如下: 2.动态注册方式 动态注册方式在activity里面调用函数来注册,和静态的内容差不多。一个形参是receiver,另一个是IntentFilter,其中里面是要接收的action。动态注册方式特点:在代码中进行注册后,当应用程序关闭后,就不再进行监听。 下面是具体的例子: MainActivity.java packagecom.android.broadcast; importandroid.app.Activity; importandroid.content.Intent; importandroid.content.IntentFilter; importandroid.os.Bundle; importandroid.view.View; importandroid.view.View.OnClickListener; importandroid.widget.Button; publicclassMainActivityextendsActivity{ //定义Action常量 protectedstaticfinalStringACTION="com.android.broadcast.RECEIVER_ACTION"; privateButtonbtnBroadcast; privateButtonregisterReceiver; privateButtonunregisterReceiver; privateMyReceiverreceiver; @Override publicvoidonCreate(BundlesavedInstanceState){ super.onCreate(savedInstanceState); setContentView(R.layout.main); btnBroadcast=(Button)findViewById(R.id.btnBroadcast); //创建事件监听器 btnBroadcast.setOnClickListener(newOnClickListener(){ @Override publicvoidonClick(Viewv){ Intentintent=newIntent(); intent.setAction(ACTION); sendBroadcast(intent); } }); registerReceiver=(Button)findViewById(R.id.btnregisterReceiver); //创建事件监听器 registerReceiver.setOnClickListener(newOnClickListener(){ @Override publicvoidonClick(Viewv){ receiver=newMyReceiver(); IntentFilterfilter=newIntentFilter(); filter.addAction(ACTION); //动态注册BroadcastReceiver registerReceiver(receiver,filter); } }); unregisterReceiver=(Button)findViewById(R.id.btnunregisterReceiver); //创建事件监听器 unregisterReceiver.setOnClickListener(newOnClickListener(){ @Override publicvoidonClick(Viewv){ //注销BroadcastReceiver unregisterReceiver(receiver); } }); } } 在“com.android.broadcast”包中定义一个MyReceiver类,继承于BroadcastReceiver,覆盖onReceive()方法。 MyReceiver.java packagecom.android.broadcast; importandroid.content.BroadcastReceiver; importandroid.content.Context; importandroid.content.Intent; importandroid.util.Log; publicclassMyReceiverextendsBroadcastReceiver{ //定义日志标签 privatestaticfinalStringTAG="Test"; @Override publicvoidonReceive(Contextcontext,Intentintent){ //输出日志信息 Log.i(TAG,"MyReceiveronReceive--->"); } } main.xml <?xmlversion="1.0"encoding="utf-8"?> <LinearLayoutxmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="fill_parent" android:layout_height="fill_parent" > <Button android:id="@+id/btnBroadcast" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="发送广播" /> <Button android:id="@+id/btnregisterReceiver" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="注册广播接收器" /> <Button android:id="@+id/btnunregisterReceiver" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="注销广播接听器" /> </LinearLayout> 效果图: ①当我们首先点击按钮的时候,因为程序没有注册BraodcastReceiver,所以LogCat没有输出任何信息。 ②当我们先点击再点击按钮的时候,这时程序会动态的注册BraodcastReceiver,之后会调用onReceive()方法,LogCat输出信息如下: ③当我们点击按钮的时候,这时程序会注销BraodcastReceiver,再点击,LogCat没有输出任何信息。 三.BroadcastReceiver的生命周期 一个BroadcastReceiver 对象只有在被调用onReceive(Context, Intent)的才有效的,当从该函数返回后,该对象就无效的了,结束生命周期。 本文转自 lingdududu 51CTO博客,原文链接:http://blog.51cto.com/liangruijun/655885

优秀的个人博客,低调大师

Android开发学习笔记:浅谈Tab选项卡

Tab选项卡是一个非常方便的组件。 一.使用Tab组件的步骤: 1.在布局文件中使用FrameLayout列出Tab组件以及Tab中的内容组件 2.Activity要继承TabActivity 3.调用TabActivity的getTabHost( )方法来获得TabHost对象 4.通过TabHost创建Tab选项 二.实现不同Tab里面的内容有两种方式: 1.切换不同的Tab时候,不同Tab里面的内容在同一个Activity显示,主要是通过修改布局文件里面的id来实现的。下面是一个具体的例子: MainActivity.java packagecom.android.tab.activity; importandroid.app.TabActivity; importandroid.os.Bundle; importandroid.view.LayoutInflater; importandroid.widget.TabHost; publicclassMainActivityextendsTabActivity{ @Override publicvoidonCreate(BundlesavedInstanceState){ super.onCreate(savedInstanceState); //获得TabHost对象 TabHosttah=getTabHost(); //from(this)从TabActivity获取LayoutInflater //R.layout.main存放Tab布局 //通过TabHost获得存放Tab标签页内容的FrameLayout //是否将inflate加到根布局元素上 LayoutInflater.from(this).inflate(R.layout.main,tah.getTabContentView(),true); //设置Tab标签的内容和显示内容 tah.addTab(tah.newTabSpec("tab1").setIndicator("图片1").setContent(R.id.TextView01)); tah.addTab(tah.newTabSpec("tab2").setIndicator("图片2").setContent(R.id.TextView02)); tah.addTab(tah.newTabSpec("tab3").setIndicator("图片3").setContent(R.id.TextView03)); } } main.xml <?xmlversion="1.0"encoding="utf-8"?> <FrameLayoutxmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/FrameLayout01" android:layout_width="wrap_content" android:layout_height="wrap_content" > <TabHost android:id="@+id/TabHost01" android:layout_width="wrap_content" android:layout_height="wrap_content" /> <TextView android:id="@+id/TextView01" android:background="@drawable/pic1" android:layout_width="wrap_content" android:layout_height="wrap_content" /> <TextView android:id="@+id/TextView02" android:background="@drawable/pic2" android:layout_width="wrap_content" android:layout_height="wrap_content" /> <TextView android:id="@+id/TextView03" android:background="@drawable/pic3" android:layout_width="wrap_content" android:layout_height="wrap_content" /> </FrameLayout> 效果图: 1.切换不同的Tab时候,不同Tab里面的内容在不同的Activity显示 先创建三个类FirstActivity.java,SecondActivity.java,ThirdActivity.java,都是继承Activity FirstActivity.java packagecom.android.tabtest.activity; importandroid.app.Activity; importandroid.os.Bundle; importandroid.widget.TextView; publicclassFirstActivityextendsActivity{ publicvoidonCreate(BundlesavedInstanceState){ super.onCreate(savedInstanceState); TextViewtextview=newTextView(this); textview.setText("这是Tab1"); setContentView(textview); } } SecondActivity.java packagecom.android.tabtest.activity; importandroid.app.Activity; importandroid.os.Bundle; importandroid.widget.TextView; publicclassSecondActivityextendsActivity{ publicvoidonCreate(BundlesavedInstanceState){ super.onCreate(savedInstanceState); TextViewtextview=newTextView(this); textview.setText("这是Tab2"); setContentView(textview); } } ThirdActivity.java packagecom.android.tabtest.activity; importandroid.app.Activity; importandroid.os.Bundle; importandroid.widget.TextView; publicclassThirdActivityextendsActivity{ publicvoidonCreate(BundlesavedInstanceState){ super.onCreate(savedInstanceState); TextViewtextview=newTextView(this); textview.setText("这是Tab3"); setContentView(textview); } } 在layout文件夹中对main.xml进行修改 <?xmlversion="1.0"encoding="utf-8"?> <TabHostandroid:id="@android:id/tabhost"android:layout_width="fill_parent" android:layout_height="fill_parent"xmlns:android="http://schemas.android.com/apk/res/android"> <LinearLayout android:orientation="vertical" android:layout_width="fill_parent" android:layout_height="fill_parent" > <TabWidget android:id="@android:id/tabs" android:layout_width="fill_parent" android:layout_height="wrap_content" /> <FrameLayout android:id="@android:id/tabcontent" android:layout_width="fill_parent" android:layout_height="fill_parent" android:padding="5dp" /> </LinearLayout> </TabHost> 新建一个测试Tab的类TabDemo.java,继承于TabActivity类 packagecom.android.tabtest.activity; importandroid.app.TabActivity; importandroid.content.Intent; importandroid.content.res.Resources; importandroid.os.Bundle; importandroid.widget.TabHost; importandroid.widget.TabHost.TabSpec; publicclassTabDemoextendsTabActivity{ /**Calledwhentheactivityisfirstcreated.*/ @Override publicvoidonCreate(BundlesavedInstanceState){ super.onCreate(savedInstanceState); setContentView(R.layout.main);//这里使用了上面创建的xml文件(Tab页面的布局) Resourcesres=getResources();//ResourceobjecttogetDrawables TabHosttabHost=getTabHost();//TheactivityTabHost TabSpecspec; Intentintent;//ReusableIntentforeachtab //第一个Tab intent=newIntent(this,FirstActivity.class);//新建一个Intent用作Tab1显示的内容 spec=tabHost.newTabSpec("tab1")//新建一个Tab .setIndicator("Tab1",res.getDrawable(android.R.drawable.ic_media_play))//设置名称以及图标 .setContent(intent);//设置显示的intent,这里的参数也可以是R.id.xxx tabHost.addTab(spec);//添加进tabHost //第二个Tab intent=newIntent(this,SecondActivity.class);//第二个Intent用作Tab1显示的内容 spec=tabHost.newTabSpec("tab2")//新建一个Tab .setIndicator("Tab2",res.getDrawable(android.R.drawable.ic_menu_edit))//设置名称以及图标 .setContent(intent);//设置显示的intent,这里的参数也可以是R.id.xxx tabHost.addTab(spec);//添加进tabHost //第三个Tab intent=newIntent(this,SecondActivity.class);//第二个Intent用作Tab1显示的内容 spec=tabHost.newTabSpec("tab2")//新建一个Tab .setIndicator("Tab3",res.getDrawable(android.R.drawable.ic_menu_help))//设置名称以及图标 .setContent(intent);//设置显示的intent,这里的参数也可以是R.id.xxx tabHost.addTab(spec);//添加进tabHost tabHost.setCurrentTab(0);//设置当前的选项卡,这里为Tab1 } } 在AndroidManifest.xml里加入9~29行的声明代码 <?xmlversion="1.0"encoding="utf-8"?> <manifestxmlns:android="http://schemas.android.com/apk/res/android" package="com.android.tabtest.activity" android:versionCode="1" android:versionName="1.0"> <uses-sdkandroid:minSdkVersion="10"/> <applicationandroid:icon="@drawable/icon"android:label="@string/app_name"> <activityandroid:name=".TabDemo" android:label="@string/app_name"> <intent-filter> <actionandroid:name="android.intent.action.MAIN"/> <categoryandroid:name="android.intent.category.LAUNCHER"/> </intent-filter> </activity> <activityandroid:name=".FirstActivity" android:label="@string/app_name"> <intent-filter> <actionandroid:name="android.intent.action.MAIN"/> <categoryandroid:name="android.intent.category.LAUNCHER"/> </intent-filter> </activity> <activityandroid:name=".SecondActivity" android:label="@string/app_name"> <intent-filter> <actionandroid:name="android.intent.action.MAIN"/> <categoryandroid:name="android.intent.category.LAUNCHER"/> </intent-filter> </activity> </application> </manifest> 效果图: 本文转自 lingdududu 51CTO博客,原文链接: http://blog.51cto.com/liangruijun/643043

优秀的个人博客,低调大师

Android开发者指南(4) —— Application Fundamentals(二)

线程安全方法(Thread-safe methods) 在一些情况下,你所实现的方法有可能会被多于一个的线程所调用,所以它们必须被写成线程安全的。 对于我们上一节所讨论的RPC机制中的可以被远程调用的方法来说,这是必须首先考虑的。如果针对一个IBinder对象中实现的方法的调用源自这个IBinder对象所在的进程时,这个方法将会在调用者的线程中执行。然而,如果这个调用源自其它的进程,则这个方法将会在一个线程池中选出的线程中运行,这个线程池由Android加以管理,并与IBinder存在于同一进程内;这个方法不会在进程的主线程内执行。反过来说,一个服务的onBind()方法应为服务进程的主线程所调用,而实现了由onBind()返回的对象(比如说,一个实现了RPC方法的Stub的子类)的方法将为池中的线程所调用。因为服务可以拥有多于一个的客户端,而同一时间,也会有多个池中的线程调用同一个IBinder方法。因此IBinder方法必须实现为线程安全的。 类似的,一个内容提供者能接受源自其它进程的请求数据。尽管ContentResolver和ContentProvider类隐藏了交互沟通过程的管理细节,ContentProvider会由query(),insert(),delete(),update()和getType()方法来相应这些请求,而这些方法也都是由那个内容提供者的进程中所包涵的线程池提供的,而不是进程的主线程本身。所以这些有可能在同一时间被很多线程调用的方法也必须被实现为线程安全的。 组件生命周期(Component Lifecycles) 应用程序组件有其生命周期──由Android初始化它们以相应intent直到这个实例被摧毁。在此之间,它们有时是激活的有时则相反。或者,如果它是一个activity,则是可为用户所见或者不能。这一节讨论了activity、服务以及广播接收器的生命周期,包括它们在生命周期中的状态、在状态之间转变时通知你的方法、以及当这些进程被关闭或实例被摧毁时,这些状态产生的效果。 Activity生命周期(Activity lifecycle) 一个activity主要有三个状态: *当在屏幕前台时(位于当前任务堆栈的顶部),它是活跃或运行的状态。它就是相应用户操作的activity。 *当它失去焦点但仍然对用户可见时,它处于暂停状态。即是:在它之上有另外一个activity。这个activity也许是透明的,或者未能完全遮蔽全屏,所以被暂停的activity仍对用户可见。暂停的activity仍然是存活状态(它保留着所有的状态和成员信息并连接至窗口管理器),但当系统处于极低内存的情况下,仍然可以杀死这个activity。 *如果它完全被另一个activity覆盖是,它处于停止状态。它仍然保留所有的状态和成员信息。然而它不在为用户可见,所以它的窗口将被隐藏,如果其它地方需要内存,则系统经常会杀死这个activity。 如果一个activity处于暂停或停止状态,系统可以通过要求它结束(调用它的finish()方法)或直接杀死它的进程来将它驱出内存。当它再次为用户可见的时候,它只能完全重新启动并恢复至以前的状态。 当一个activity从这个状态转变到另一个状态时,它被以下列protected方法所通知: void onCreate(Bundle savedInstanceState) void onStart() void onRestart() void onResume() void onPause() void onStop() void onDestroy() 你可以重载所有这些方法以在状态改变时进行合适的工作。所有的activity都必须实现onCreate()用以当对象第一次实例化时进行初始化设置。很多activity会实现onPause()以提交数据变化或准备停止与用户的交互。 调用父类(Calling into the superclass) 所有activity生命周期方法的实现都必须先调用其父类的版本。比如说: 总得来说,这七个方法定义了一个activity完整的生命周期。实现这些方法可以帮助你监察三个嵌套的生命周期循环: *一个activity完整的生命周期自第一次调用onCreate()开始,直至调用onDestroy()为止。activity在onCreate()中设置所有“全局”状态以完成初始化,而在onDestroy()中释放所有系统资源。比如说,如果activity有一个线程在后台运行以从网络上下载数据,它会以onCreate()创建那个线程,而以onDestroy()销毁那个线程。 *一个activity的可视生命周期自onStart()调用开始直到相应的onStop()调用。在此期间,用户可以在屏幕上看到此activity,尽管它也许并不是位于前台或者正在与用户做交互。在这两个方法中,你可以管控用来向用户显示这个activity的资源。比如说,你可以在onStart()中注册一个BroadcastReceiver来监控会影响到你UI的改变,而在onStop()中来取消注册,这时用户是无法看到你的程序显示的内容的。onStart()和onStop()方法可以随着应用程序是否为用户可见而被多次调用。 *一个activity的前台生命周期自onResume()调用起,至相应的onPause()调用为止。在此期间,activity位于前台最上面并与用户进行交互。activity会经常在暂停和恢复之间进行状态转换──比如说当设备转入休眠状态或有新的activity启动时,将调用onPause()方法。当activity获得结果或者接收到新的intent的时候会调用onResume()方法。因此,在这两个方法中的代码应当是轻量级的。 下图展示了上述循环过程以及activity在这个过程之中历经的状态改变。着色的椭圆是activity可以经历的主要状态。矩形框代表了当activity在状态间发生改变的时候,你进行操作所要实现的回调方法。 下表详细描述了这些方法,并在activity的整个生命周期中定位了它们。 方法 描述 是否可被杀死(Killable?) 下一个 onCreate() 在activity第一次被创建的时候调用。这里是你做所有初始化设置的地方──创建视图、绑定数据至列表等。如果曾经有状态记录(参阅后述Saving Activity State。),则调用此方法时会传入一个包含着此activity以前状态的包对象做为参数。 接下来始终遵循调用onStart()。 否 onStart() onRestart() 在activity停止后,在再次启动之前被调用。 接下来始终遵循调用onStart()。 否 onStart() onStart() 当activity正要变得为用户所见时被调用。 当activity转向前台时接下来调用onResume(),在activity变为隐藏时接下来调用onStop()。 否 onResume() 或 onStop() onResume() 在activity开始与用户进行交互之前被调用。此时activity位于堆栈顶部,并接受用户输入。 接下来始终遵循调用onPause()。 否 onPause() onPause() 当系统将要启动另一个activity时调用。此方法主要用来将未保存的变化进行持久化,停止类似动画这样耗费CPU的动作等。这一切动作应该在短时间内完成,因为下一个activity必须等到此方法返回后才会继续。 当activity重新回到前台时接下来调用onResume()。当activity变为用户不可见时接下来调用onStop()。 是 onResume() 或 onStop() onStop() 当activity不再为用户可见时调用此方法。这可能发生在它被销毁或者另一个activity(可能是现存的或者是新的)回到运行状态并覆盖了它。 如果activity再次回到前台跟用户交互则接下来调用onRestart(),如果关闭activity则接下来调用onDestroy()。 是 onRestart() or onDestroy() onDestroy() 在activity销毁前调用。这是activity接收的最后一个调用。这可能发生在activity结束(调用了它的finish()方法)或者因为系统需要空间所以临时的销毁了此acitivity的实例时。你可以用isFinishing()方法来区分这两种情况。 是 无 请注意上表中可被杀死一列。它标示了在方法返回后,还没执行activity的其余代码的任意时间里,系统是否可以杀死包含此activity的进程。三个方法(onPause()、onStop()和onDestroy())被标记为“是”。onPause()是三个中的第一个,它也是唯一一个在进程被杀死之前必然会调用的方法──onStop()和onDestroy()有可能不被执行。因此你应该用onPause()来将所有持久性数据(比如用户的编辑结果)写入存储之中。 在可被杀死一列中标记为“否”的方法在它们被调用时将保护activity所在的进程不会被杀死。所以只有在onPause()方法返回后到onResume()方法被调用时,一个activity才处于可被杀死的状态。在onPause()再次被调用并返回之前,它不会被系统杀死。 如后面一节进程和生命周期所述,即使是在这里技术上没有被定义为“可杀死”的activity仍然有可能被系统杀死──但这仅会发生在实在没有其它方法的极端情况之下。 保存activity状态(Saving activity state) 当系统而不是用户自己出于回收内存的考虑,关闭了一个activity之后。用户会期望当他再次回到那个activity的时候,它仍保持着上次离开时的样子。 为了获取activity被杀死前的状态,你应该为activity实现onSaveInstanceState()方法。Android在activity有可能被销毁之前(即onPause()调用之前)会调用此方法。它会将一个以名称-值对方式记录了activity动态状态的Bundle对象传递给该方法。当activity再次启动时,这个Bundle会传递给onCreate()方法和随着onStart()方法调用的onRestoreInstanceState(),所以它们两个都可以恢复捕获的状态。 与onPause()或先前讨论的其它方法不同,onSaveInstanceState()和onRestoreInstanceState()并不是生命周期方法。它们并不是总会被调用。比如说,Android会在activity易于被系统销毁之前调用onSaveInstanceState(),但用户动作(比如按下了BACK键)造成的销毁则不调用。在这种情况下,用户没打算再次回到这个activity,所以没有保存状态的必要。 因为onSaveInstanceState()不是总被调用,所以你应该只用它来为activity保存一些临时的状态,而不能用来保存持久性数据。而是应该用onPause()来达到这个目的。 服务生命周期(Coordinating activities) 服务以两种方式使用: *它可以启动并运行,直至有人停止了它或它自己停止。在这种方式下,它以调用Context.startService()启动,而以调用Context.stopService()结束。它可以调用Service.stopSelf()或Service.stopSelfResult()来自己停止。不论调用了多少次startService()方法,你只需要调用一次stopService()来停止服务。 *它可以通过自己定义并暴露出来的接口进行程序操作。客户端建立一个到服务对象的连接,并通过那个连接来调用服务。连接以调用Context.bindService()方法建立,以调用Context.unbindService()关闭。多个客户端可以绑定至同一个服务。如果服务此时还没有加载,bindService()会先加载它。 这两种模式并不是完全分离的。你可以绑定至一个用startService()启动的服务。比如说,一个后台音乐播放服务可以调用startService()并传递给它一个包含欲播放的音乐列表的Intent对象来启动。不久,当用户想要对播放器进行控制或者查看当前播放曲目的详情时,会启用一个activity,调用bindService()连接到服务来完成操作。在这种情况下,直到绑定连接关闭stopService()才会真正停止一个服务。 与activity一样,服务也有一系列你可以实现以用于监控其状态变化的生命周期方法。但相对于activity要少一些,只有三个,而且,它们是public属性,并非protected: void onCreate() void onStart(Intent intent) void onDestroy() 倚仗实现这些方法,你监控服务的两个嵌套的生命周期循环: *服务的完整生命周期始于调用onCreate()而终于onDestroy()方法返回。如同activity一样,服务在onCreate()里面进行它自己的初始化,而在onDestroy()里面释放所有资源。比如说,一个音乐回放服务可以在onCreate()中创建播放音乐的线程,而在onDestroy()中停止这个线程。 *服务的活跃生命周期始于调用onStart()。这个方法用于处理传递给startService()的Intent对象。音乐服务会打开Intent来探明将要播放哪首音乐,并开始播放。 服务停止时没有相应的回调方法──不存在onStop()方法。 onCreate()和onDestroy()方法在所有服务中都会被调用,无论它们是由Context.startService()还是由Context.bindService()所启动的。而onStart()仅会被startService()所启用的服务调用。 如果一个服务允许别的进程绑定,则它还会有以下额外的回调方法以供实现: IBinder onBind(Intent intent) boolean onUnbind(Intent intent) void onRebind(Intent intent) 传递给bindService的Intent的对象也会传递给onBind()回调方法,而传递给unbindService()的Intent对象同样传递给onUnbind()。如果服务允许绑定,onBind()将返回一个供客户端与服务进行交互的通讯渠道。如果有新的客户端连接至服务,则onUnbind()方法可以要求调用onRebind()。 下图描绘了服务的回调方法。尽管图中对由startService和startService方法启动的服务做了区分,但要记住,不论一个服务是怎么启动的,它都可能允许客户端的连接,所以任何服务都可以接受onBind()和onUnbind()调用。 广播接收器生命周期(Broadcast receiver lifecycle) 广播接收器只有一个回调方法: void onReceive(Context curContext, Intent broadcastMsg) 当广播消息抵达接收器时,Android调用它的onReceive()方法并将包含消息的Intent对象传递给它。广播接收器仅在它执行这个方法时处于活跃状态。当onReceive()返回后,它即为失活状态。 拥有一个活跃状态的广播接收器的进程被保护起来而不会被杀死。但仅拥有失活状态组件的进程则会在其它进程需要它所占有的内存的时候随时被杀掉。 这种方式引出了一个问题:如果响应一个广播信息需要很长的一段时间,我们一般会将其纳入一个衍生的线程中去完成,而不是在主线程内完成它,从而保证用户交互过程的流畅。如果onReceive()衍生了一个线程并且返回,则包涵新线程在内的整个进程都被会判为失活状态(除非进程内的其它应用程序组件仍处于活跃状态),于是它就有可能被杀掉。这个问题的解决方法是令onReceive()启动一个新服务,并用其完成任务,于是系统就会知道进程中仍然在处理着工作。 下一节中,我们会讨论更多进程易误杀的问题。 进程与生命周期(Processes and lifecycles) Android系统会尽可能长的延续一个应用程序进程,但在内存过低的时候,仍然会不可避免需要移除旧的进程。为决定保留或移除一个进程,Android将每个进程都放入一个“重要性层次”中,依据则是它其中运行着的组件及其状态。重要性最低的进程首先被消灭,然后是较低的,依此类推。重要性共分五层,依据重要性列表如下: 1.前台进程是用户操作所必须的。当满足如下任一条件时,进程被认为是处于前台的: *它运行着正在与用户交互的activity(Activity对象的onResume()方法已被调用)。 *一个正在与用户交互的activity使用着它提供的一个服务。 *它包含着一个正在执行生命周期回调方法(onCreate()、onStart()或onDestroy())的Service对象。 *它包含着一个正在执行onReceive()方法的BroadcastReceiver对象。 任一时间下,仅有少数进程会处于前台,仅当内存实在无法供给它们维持同时运行时才会被杀死。一般来说,在这种情况下,设备已然处于使用虚拟内存的状态,必须要杀死一些前台进程以用户界面保持响应。 2.可视进程没有前台组件,但仍可被用户在屏幕上所见。当满足如下任一条件时,进程被认为是可视的: *它包含着一个不在前台,但仍然为用户可见的activity(它的onPause()方法被调用)。这种情况可能出现在以下情况:比如说,前台activity是一个对话框,而之前的activity位于其下并可以看到。 *它包含了一个绑定至一个可视的activity的服务。 可视进程依然被视为是很重要的,非到不杀死它们便无法维持前台进程运行时,才会被杀死。 3.服务进程是由startService()方法启动的服务,它不会变成上述两类。尽管服务进程不会直接为用户所见,但它们一般都在做着用户所关心的事情(比如在后台播放mp3或者从网上下载东西)。所以系统会尽量维持它们的运行,除非系统内存不足以维持前台进程和可视进程的运行需要。 4.背景进程包含目前不为用户所见的activity(Activity对象的onStop()方法已被调用)。这些进程与用户体验没有直接的联系,可以在任意时间被杀死以回收内存供前台进程、可视进程以及服务进程使用。一般来说,会有很多背景进程运行,所以它们一般存放于一个LRU(最后使用)列表中以确保最后被用户使用的activity最后被杀死。如果一个activity正确的实现了生命周期方法,并捕获了正确的状态,则杀死它的进程对用户体验不会有任何不良影响。 5.空进程不包含任何活动应用程序组件。这种进程存在的唯一原因是做为缓存以改善组件再次于其中运行时的启动时间。系统经常会杀死这种进程以保持进程缓存和系统内核缓存之间的平衡。 Android会依据进程中当前活跃组件的重要程度来尽可能高的估量一个进程的级别。比如说,如果一个进程中同时有一个服务和一个可视的activity,则进程会被判定为可视进程,而不是服务进程。 此外,一个进程的级别可能会由于其它进程依赖于它而升高。一个为其它进程提供服务的进程级别永远高于使用它服务的进程。比如说,如果A进程中的内容提供者为进程B中的客户端提供服务,或进程A中的服务为进程B中的组件所绑定,则A进程最低也会被视为与进程B拥有同样的重要性。 为运行着一个服务的进程重要级别总高于一个背景activity。所以一个activity以启动一个服务的方式启动一个长时间运行过程比简单的衍生一个线程来进行处理要好。尤其是当处理过程比activity本身存在时间要长的情况之下。我们以背景音乐播放和上传一个相机拍摄的图片至网站上为例。使用服务则不论activity发生何事,都至少可以保证操作拥有“服务进程”的权限。如上一节广播接收器生命周期所提到的,这也正是广播接收器使用服务,而不是使用线程来处理耗时任务的原因。 本文转自over140 51CTO博客,原文链接:http://blog.51cto.com/over140/582308,如需转载请自行联系原作者

优秀的个人博客,低调大师

Android开发者指南(29) —— USB Host and Accessory

USB主从设备 Android支持各种USB外围设备,通过两种模式来支持Android USB外设(实现了Android外设协议的硬件):USB外设模式和USB主机模式。在USB外设模式下,外部USB硬件(装有Android的设备要连接的外部设备)充当USB主机。外设的例子包括机器人、扩展插座、诊断和音乐设备、电子报亭、读卡器等其他设备。这种模式给予不具备主机功能的Android设备以与USB硬件交互的能力。Android USB外设必须设计用来与装有Android的设备一起工作,并且必须遵循Android外设通讯协议。在USB主机模式下,装有Android的设备扮演着主机的角色。这种设备的例子包括数码像机,键盘,鼠标和游戏手柄。那些适应面很广的USB设备仍可以与Android应用交互,前提是这些Android应用可以正确的与这些设备通讯。 图1展示了两种模式的异同。当Android设备处于主机模式时,它扮演USB主机角色并为总线供电。当Android设备处于附件模式时,被连接的USB硬件(在这种情况下是一个Android USB附件)扮演主机角色并给总线供电。 图1. USB主从模式 USB外设和主机模式在Android 3.1 (API level 12)或更高的平台中直接支持。USB外设模式作为一个外设库也被回馈到Android 2.3.4 (API level 10)来支持更广泛的设备。设备厂商可以选择是否在设备的系统镜像中包含附加库。 注意:对USB主机和外设模式的支持最终取决于设备的硬件,不管平台的等级(是多少)。你可以通过<uses-feature>元素过滤那些支持USB主机和外设的设备。查看USB外设和主机文档获取更多详细信息。 调试注意事项 当调试那些使用了USB外设和主机特性的应用时,你很有可能把你的USB硬件连接到你的Android设备上,这将阻止你通过USB建立adb到Android设备的连接。你通过网络仍可以访问adb。通过网络连接adb: 通过USB将Android设备连接到电脑。 从SDK的platform-tools目录,在命令行输入adb tcpip 5555 输入:adb connect <设备的IP地址>:5555,你现在将被连接到Android设备并能像adb logcat一样发出通用的adb命令。 要设置你的设备监听USB,输入adb usb。 本文转自over140 51CTO博客,原文链接:http://blog.51cto.com/over140/716803,如需转载请自行联系原作者

优秀的个人博客,低调大师

Android开发10——Activity的跳转与传值

Activity跳转与传值,主要是通过Intent类,Intent的作用是激活组件和附带数据。 intent可以激活Activity,服务,广播三类组件。本博文讲的是显示意图激活Activity组件。所谓显示意图就是在activity的激活时,显示指出了需要激活的activity的名字。 一、Activity跳转 方法一 Intent intent = new Intent(A.this, B.class); startActivity(intent); 方法二 Intent intent = new Intent(); intent.setClass(A.this, B.class); startActivity(intent); 实现从A跳转到B(A、B均继承自Activity) 二、传递数据 Activity A 传递数据 方法一 Intent intent = new Intent(); intent.setClass(A.this, B.class); intent.putExtra("name", "xy"); intent.putExtra("age", 22); startActivity(intent); 方法二 Intent intent = new Intent(A.this, B.class); Bundle bundle = new Bundle(); bundle.putString("name", "xy"); bundle.putInt("age", 22); intent.putExtras(bundle); startActivity(intent); Activity B 接收数据 // 获取参数1 Intent intent = this.getIntent(); String name = intent.getStringExtra("name"); int age = intent.getIntExtra("age", 22);// 缺省值为22 // 获取参数2 Bundle bundle = intent.getExtras(); String name2 = bundle.getString("name"); int age2 = bundle.getInt("age", 22); 两种获取参数方式均可,并不是和传参1,2方法一一对应 三、Activity返回值 跳转后前一个Activity已经被destroy了。如若要返回并显示数据,就必须将前一个Activity再次唤醒,同时调用某个方法来获取并显示数据。做法如下 1.从A页面跳转到B页面时不可使用startActivity()方法,而要使用startActivityForResult()方法 2.在A页面的Activity中,需要重写onActivityResult(int requestCode, int resultCode, Intent data)方法 Activity A //有返回值的Activity publicvoidopenNewActivity2(Viewv) { Intentintent=newIntent(); intent.setClass(this.getApplicationContext(),OtherActivity2.class); intent.putExtra("name","xy"); intent.putExtra("age",20); startActivityForResult(intent,1); } @Override protectedvoidonActivityResult(intrequestCode,intresultCode,Intentdata) { //requestCode用于区分业务 //resultCode用于区分某种业务的执行情况 if(1==requestCode&&RESULT_OK==resultCode) { Stringresult=data.getStringExtra("result"); Toast.makeText(this.getBaseContext(),result,Toast.LENGTH_SHORT).show(); } else { Toast.makeText(this.getBaseContext(),"无返回值",Toast.LENGTH_SHORT).show(); } } Activity B publicvoidclose(Viewv) { Intentintent=newIntent(); intent.putExtra("result","返回值"); this.setResult(RESULT_OK,intent);//设置结果数据 this.finish();//关闭Activity } 四、总结 以上采用的意图intent均是显示意图。 参考地址:http://www.cnblogs.com/feng88724/archive/2011/02/10/1961225.html 本文转自IT徐胖子的专栏博客51CTO博客,原文链接http://blog.51cto.com/woshixy/1081317如需转载请自行联系原作者 woshixuye111

优秀的个人博客,低调大师

Android开发7——android.database.CursorIndexOutOfBoundsException:Index -1 ...

android中数据库处理使用cursor时,游标不是放在为0的下标,而是放在为-1的下标处开始的。 也就是说返回给cursor查询结果时,不能够马上从cursor中提取值。 下面的代码会返回错误 Useru=null; SQLiteDatabasedb=dbHelper.getReadableDatabase(); Cursorcursor=db.rawQuery("select*fromuserwhereid=?",newString[]{id.toString()}); Integeruid=cursor.getInt(cursor.getColumnIndex("id")); Stringuname=cursor.getString(cursor.getColumnIndex("name")); floatuamount=cursor.getFloat(cursor.getColumnIndex("amount")); u=newUser(uid,uname,uamount); 正确的用法 Useru=null; SQLiteDatabasedb=dbHelper.getReadableDatabase(); Cursorcursor=db.rawQuery("select*fromuserwhereid=?",newString[]{id.toString()}); if(cursor.moveToFirst()) { Integeruid=cursor.getInt(cursor.getColumnIndex("id")); Stringuname=cursor.getString(cursor.getColumnIndex("name")); floatuamount=cursor.getFloat(cursor.getColumnIndex("amount")); u=newUser(uid,uname,uamount); } 本文转自IT徐胖子的专栏博客51CTO博客,原文链接http://blog.51cto.com/woshixy/1079514如需转载请自行联系原作者 woshixuye111

优秀的个人博客,低调大师

全面理解iOS开发中的Scroll View[转]

from:http://mobile.51cto.com/hot-430409.htm 可能你很难相信,UIScrollView和一个标准的UIView差异并不大,scroll view确实会多一些方法,但这些方法只是UIView一些属性的表面而已。因此,要想弄懂UIScrollView是怎么工作之前,你需要了解 UIView,特别是视图渲染过程的两步。 AD: 可能你很难相信,UIScrollView和一个标准的UIView差异并不大,scroll view确实会多一些方法,但这些方法只是UIView一些属性的表面而已。因此,要想弄懂UIScrollView是怎么工作之前,你需要了解 UIView,特别是视图渲染过程的两步。 光栅化和组合 渲染过程的第一部分是众所周知的光栅化,光栅化简单的说就是产生一组绘图指令并且生成一张图片。比如绘制一个圆角矩形、带图片、标题居中的UIButtons。这些图片并没有被绘制到屏幕上去;取而代之的是,他们被自己的视图保持着留到下一个步骤用。 一旦每个视图都产生了自己的光栅化图片,这些图片便被一个接一个的绘制,并产生一个屏幕大小的图片,这便是上文所说的组合。视图层级(view hierarchy)对于组合如何进行扮演了很重要的角色:一个视图的图片被组合在它父视图图片的上面。然后,组合好的图片被组合到父视图的父视图图片上 面,就这样。。。最终视图层级最顶端是窗口(window),它组合好的图片便是我们看到的东西了。 概念上,依次在每个视图上放置独立分层的图片并最终产生一个图片,单调的图像将会变得更容易理解,特别是如果你以前使用过像Photoshop这样的工具。我们还有另外一篇文章详细解释了像素是如何绘制到屏幕上去的。 现在,回想一下,每个视图都有一个bounds和frame。当布局一个界面时,我们需要处理视图的frame。这允许我们放置并设置视图的大小。 视图的frame和bounds的大小总是一样的,但是他们的origin有可能不同。弄懂这两个工作原理是理解UIScrollView的关键。 在光栅化步骤中,视图并不关心即将发生的组合步骤。也就是说,它并不关心自己的frame(这是用来放置视图的图像)或自己在视图层级中的位置(这 是决定组合的顺序)。这时视图只关心一件事就是绘制它自己的content。这个绘制发生在每个视图的drawRect:方法中。 在drawRect:方法被调用前,会为视图创建一个空白的图片来绘制content。这个图片的坐标系统是视图的bounds。几乎每个视图 bounds的origin都是{0,0}。因此,当在删格化图片左上角绘制一些东西的时候,你都会在bounds的origin({x:0,y:0}) 处绘制。在一个图片右下角的地方绘制东西的时候,你都会绘制在{x:width, y:height}处。如果你的绘制超出了视图的bounds,那么超出的部分就不属于删格化图片的部分了,并且会被丢弃。 在组合的步骤中,每个视图将自己光栅化图片组合到自己父视图的光栅化图片上面。视图的frame决定了自己在父视图中绘制的位置,frame的 origin表明了视图光栅化图片左上角相对父视图光栅化图片左上角的偏移量。所以,一个origin为{x:20,y:15}的frame所绘制的图片 左边距其父视图20点,上边距父视图15点。因为视图的frame和bounds矩形的大小总是一样的,所以光栅化图片组合的时候是像素对齐的。这确保了 光栅化图片不会被拉伸或缩小。 记住,我们才仅仅讨论了一个视图和它父视图之间的组合操作。一旦这两个视图被组合到一起,组合的结果图片将会和父视图的父视图进行组合。。。这是一个雪球效应。 考虑一下组合图片背后的公式。视图图片的左上角会根据它frame的origin进行偏移,并绘制到父视图的图片上: CompositedPosition.x=View.frame.origin.x-Superview.bounds.origin.x; CompositedPosition.y=View.frame.origin.y-Superview.bounds.origin.y; 我们可以通过几个不同的frames看一下: 这样做是有道理的。我们改变button的frame.origin后,它会改变自己相对紫色父视图的位置。注意,如果我们移动button直到它 的一部分已经在紫色父视图bounds的外面,当光栅化图片被截去时这部分也将会通过同样的绘制方式被截去。然而,技术上讲,因为iOS处理组合方法的原 因,你可以将一个子视图渲染在其父视图的bounds之外,但是光栅化期间的绘制不可能超出一个视图的bounds。 Scroll View的Content Offset 现在,我们所讲的跟UIScrollView有什么关系呢?一切都和它有关!考虑一种我们可以实现的滚动:我们有一个拖动时frame不断改变的视 图。这达到了相同的效果,对吗?如果我拖动我的手指到右边,那么拖动的同时我增大视图的origin.x,瞧,这货就是scroll view。 当然,在scroll view中有很多具有代表性的视图。为了实现这个平移功能,当用户移动手指时,你需要时刻改变每个视图的frames。当我们提出组合一个view的光栅化图片到它父视图什么地方时,记住这个公式: CompositedPosition.x=View.frame.origin.x-Superview.bounds.origin.x; CompositedPosition.y=View.frame.origin.y-Superview.bounds.origin.y; 我们减少Superview.bounds.origin的值(因为他们总是0)。但是如果他们不为0呢?我们用和前一个图例相同的frames,但是我们改变了紫色视图bounds的origin为{-30,-30}。得到下图: 现在,巧妙的是通过改变这个紫色视图的bounds,它每一个单独的子视图都被移动了。事实上,这正是一个scroll view工作的原理。当你设置它的contentOffset属性时:它改变scroll view.bounds的origin。事实上,contentOffset甚至不是实际存在的。代码看起来像这样: -(void)setContentOffset:(CGPoint)offset { CGRectbounds=[selfbounds]; bounds.origin=offset; [selfsetBounds:bounds]; } 注意:前一个图例,只要足够的改变bounds的origin,button将会超出紫色视图和button组合成的图片的范围。这也是当你足够的移动scroll view时,一个视图会消失! 世界之窗:Content Size 现在,最难的部分已经过去了,我们再看看UIScrollView另一个属性:contentSize。scroll view的content size并不会改变其bounds的任何东西,所以这并不会影响scroll view如何组合自己的子视图。反而,content size定义了可滚动区域。scroll view的默认content size为{w:0,h:0}。既然没有可滚动区域,用户是不可以滚动的,但是scroll view任然会显示其bounds范围内所有的子视图。 当content size设置为比bounds大的时候,用户就可以滚动视图了。你可以认为scroll view的bounds为可滚动区域上的一个窗口: 当content offset为{x:0,y:0}时,可见窗口的左上角在可滚动区域的左上角处。这也是content offset的最小值;用户不能再往可滚动区域的左边或上边移动了。那儿没啥,别滚了! content offset的最大值是content size和scroll view size的差。这也在情理之中:从左上角一直滚动到右下角,用户停止时,滚动区域右下角边缘和滚动视图bounds的右下角边缘是齐平的。你可以像这样记 下content offset的最大值: contentOffset.x=contentSize.width-bounds.size.width; contentOffset.y=contentSize.height-bounds.size.height; 用Content Insets对窗口稍作调整 contentInset属性可以改变content offset的最大和最小值,这样便可以滚动出可滚动区域。它的类型为UIEdgeInsets,包含四个值: {top,left,bottom,right}。当你引进一个inset时,你改变了content offset的范围。比如,设置content inset顶部值为10,则允许content offset的y值达到10。这介绍了可滚动区域周围的填充。 这咋一看好像没什么用。实际上,为什么不仅仅增加content size呢?除非没办法,否则你需要避免改变scroll view的content size。想要知道为什么?想想一个table view(UItableView是UIScrollView的子类,所以它有所有相同的属性),table view为了适应每一个cell,它的可滚动区域是通过精心计算的。当你滚动经过table view的第一个或最后一个cell的边界时,table view将content offset弹回并复位,所以cells又一次恰到好处的紧贴scroll view的bounds。 当你想要使用UIRefreshControl实现拉动刷新时发生了什么?你不能在table view的可滚动区域内放置UIRefreshControl,否则,table view将会允许用户通过refresh control中途停止滚动,并且将refresh control的顶部弹回到视图的顶部。因此,你必须将refresh control放在可滚动区域上方。这将允许首先将content offset弹回第一行,而不是refresh control。 但是等等,如果你通过滚动足够多的距离初始化pull-to-refresh机制,因为table view设置了content inset,这将允许content offset将refresh control弹回到可滚动区域。当刷新动作被初始化时,content inset已经被校正过,所以content offset的最小值包含了完整的refresh control。当刷新完成后,content inset恢复正常,content offset也跟着适应大小,这里并不需要为content size做数学计算。(这里可能比较难理解,建议看看EGOTableViewPullRefresh这样的类库就应该明白了) 如何在自己的代码中使用content inset?当键盘在屏幕上时,有一个很好的用途:你想要设置一个紧贴屏幕的用户界面。当键盘出现在屏幕上时,你损失了几百个像素的空间,键盘下面的东西全都被挡住了。 现在,scroll view的bounds并没有改变,content size也并没有改变(也不需要改变)。但是用户不能滚动scroll view。考虑一下之前一个公式:content offset的最大值并不同于content size和bounds的大小。如果他们相等,现在content offset的最大值是{x:0,y:0}. 现在开始出绝招,将界面放入一个scroll view。scroll view的content size仍然和scroll view的bounds一样大。当键盘出现在屏幕上时,你设置content inset的底部等于键盘的高度。 这允许在content offset的最大值下显示滚动区域外的区域。可视区域的顶部在scroll view bounds的外面,因此被截取了(虽然它在屏幕之外了,但这并没有什么)。 但愿这能让你理解一些滚动视图内部工作的原理,你对缩放感兴趣?好吧,我们今天不会谈论它,但是这儿有一个有趣的小窍门:检查 viewForZoomingInScrollView:方法返回视图的transform属性。你将再次发现scroll view只是聪明的利用了UIView已经存在的属性。 欢迎加群互相学习,共同进步。QQ群:iOS: 58099570 | Android: 572064792 | Nodejs:329118122 做人要厚道,转载请注明出处! 本文转自张昺华-sky博客园博客,原文链接:http://www.cnblogs.com/sunshine-anycall/p/4705205.html ,如需转载请自行联系原作者

优秀的个人博客,低调大师

Android开发学习笔记:数据存取之Preference浅析

一.Preference的简介 Preference(配置)提供了一种轻量级的数据存取方法,主要应用于数据比较少的配置信息。它以“key-value”(是一个Map)对的方式将数据保存在一个XML配置文件中,例如,手机的开机问候语,可以将其以Preference方式来进行配置。也可以保存一些用户个性化设置的字体、颜色、位置等参数信息。 二.Preference存取数据的方法 使用到的接口: SharedPreferences接口和SharedPreferences.Editor接口,它们都是来自于andorid.content包。 ①SharedPreferences接口提供保存数据的方法 我们可以调用Context.getSharedPreferences(String name,int mode)方法得到SharedPreferences接口。该方法的第一个参数是文件名称,第二个参数是操作模式。操作模式有三种: MODE_PRIVATE(私有) MODE_WORLD_READABLE(可读) MODE_WORLD_WRITEABLE(可写) SharedPreferences接口的常用方法 方法名称 方法描述 edit() 返回SharedPreferences的内部接口SharedPreferences.Editor contains(String key) 判断是否包含该键值 getAll() 返回所有配置信息Map getBoolean(String key,Boolean defValue) 获得一个boolean值 getFloat(String key,float defValue) 获得一个float值 getInt(String key,int defValue) 获得一个int值 getLong(String key,long defValue) 获得一个long值 getString(String key,String defValue) 获得一个String值 ②SharedPreferences.Editor接口提供获得数据的方法 SharedPreferences.Editor接口中的getString(String key,String defValue)、getInt(String key,int defValue)等。调用SharedPreferences的edit()方法返回SharedPreferences.Editor内部接口,该接口中提供了保存数据的方法,如putString(String key,String value)、pubInt(String key,int value)等,调用该接口的commit方法可以将数据进行保存。 SharedPreferences.Editor接口常用的方法 方法名称 方法描述 clear() 清楚所有值 commit() 保存 getAll() 返回所有配置信息Map putBoolean(String key,Boolean value) 保存一个boolean值 putFloat(String key,float value) 保存一个float值 putInt(String key,int value) 保存一个int值 putLong(String key,long value) 保存一个long值 putString(String key,String value) 保存一个String值 remove(String key) 删除该键对应的键 下面是保存临时记事本内容的例子 当我们在手机上用记事本编写备忘录,日志的时候,突然要返回接电话,看短信的时候。我们就要停止编写,退出去听接电话,看短信。然而,当我们打完电话或者看完短信回来的时候,发现刚刚编写的内容还在记事本上,这就是Preference的作用了。 创建一个Activity,在其中放置一个EditText保存短信内容,放置一个Button用于发送信息。在onCreate()中通过getSharedPreferences()方法获得SharedPreferences接口,调用接口的getString()方法,获得保存内容,将内容设置到EditText中。 在onStop()方法中保存内容,使用getSharedPreferences().edit()方法获得SharedPreferences.Editor接口,调用SharedPreferences.Editor的putString()方法保存短信内容,调用commit()方法提交内容。 MainActivity.java packagecom.android.preference; importandroid.app.Activity; importandroid.content.SharedPreferences; importandroid.os.Bundle; importandroid.widget.Button; importandroid.widget.EditText; publicclassMainActivityextendsActivity{ privateEditTextmyEditText; privateButtonbtn; privatestaticfinalStringTEMP_INFO="temp_info"; @Override publicvoidonCreate(BundlesavedInstanceState){ super.onCreate(savedInstanceState); setContentView(R.layout.main); myEditText=(EditText)findViewById(R.id.EditText1); btn=(Button)findViewById(R.id.Button1); //获得SharedPreferences实例 SharedPreferencessp=getSharedPreferences(TEMP_INFO,MODE_WORLD_READABLE); //从SharedPreferences获得备忘录的内容 Stringcontent=sp.getString("info_content",""); //在EditText中显示备忘录内容 myEditText.setText(content); } @Override protectedvoidonStop(){ super.onStop(); //获得编辑器 SharedPreferences.Editoreditor=getSharedPreferences(TEMP_INFO,MODE_WORLD_WRITEABLE).edit(); //将EditText中的文本内容添加到编辑器 editor.putString("info_content",myEditText.getText().toString()); //提交编辑器内容 editor.commit(); } } main.xml <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="fill_parent" android:layout_height="fill_parent" > <TextView android:layout_width="fill_parent" android:layout_height="wrap_content" android:text="备忘录" /> <EditText android:text="" android:id="@+id/EditText1" android:layout_width="fill_parent" android:layout_height="wrap_content" /> <Button android:id="@+id/Button1" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="保存" /> </LinearLayout> 效果图: 当我们退出屏幕再返回的时候,发现原来备忘录里编辑了一半的内容还在那里,其实它是把要保存的内容做诶XML文件保存在/data/data/package/shsared_prefs/下面了。 本文转自 lingdududu 51CTO博客,原文链接:http://blog.51cto.com/liangruijun/658521

资源下载

更多资源
Mario

Mario

马里奥是站在游戏界顶峰的超人气多面角色。马里奥靠吃蘑菇成长,特征是大鼻子、头戴帽子、身穿背带裤,还留着胡子。与他的双胞胎兄弟路易基一起,长年担任任天堂的招牌角色。

Nacos

Nacos

Nacos /nɑ:kəʊs/ 是 Dynamic Naming and Configuration Service 的首字母简称,一个易于构建 AI Agent 应用的动态服务发现、配置管理和AI智能体管理平台。Nacos 致力于帮助您发现、配置和管理微服务及AI智能体应用。Nacos 提供了一组简单易用的特性集,帮助您快速实现动态服务发现、服务配置、服务元数据、流量管理。Nacos 帮助您更敏捷和容易地构建、交付和管理微服务平台。

Rocky Linux

Rocky Linux

Rocky Linux(中文名:洛基)是由Gregory Kurtzer于2020年12月发起的企业级Linux发行版,作为CentOS稳定版停止维护后与RHEL(Red Hat Enterprise Linux)完全兼容的开源替代方案,由社区拥有并管理,支持x86_64、aarch64等架构。其通过重新编译RHEL源代码提供长期稳定性,采用模块化包装和SELinux安全架构,默认包含GNOME桌面环境及XFS文件系统,支持十年生命周期更新。

Sublime Text

Sublime Text

Sublime Text具有漂亮的用户界面和强大的功能,例如代码缩略图,Python的插件,代码段等。还可自定义键绑定,菜单和工具栏。Sublime Text 的主要功能包括:拼写检查,书签,完整的 Python API , Goto 功能,即时项目切换,多选择,多窗口等等。Sublime Text 是一个跨平台的编辑器,同时支持Windows、Linux、Mac OS X等操作系统。

用户登录
用户注册