Android面试题2
Android 四大组件
一.四大组件:Android四大组件分别为activity、service、content provider、broadcast receiver。
一、android四大组件详解
1、activity
(1)一个Activity通常就是一个单独的屏幕(窗口)。
(2)Activity之间通过Intent进行通信。
(3)android应用中每一个Activity都必须要在AndroidManifest.xml配置文件中声明,否则系统将不识别也不执行该Activity。
2、service
(1)service用于在后台完成用户指定的操作。service分为两种:
(a)started(启动):当应用程序组件(如activity)调用startService()方法启动服务时,服务处于started状态。
(b)bound(绑定):当应用程序组件调用bindService()方法绑定到服务时,服务处于bound状态。
(2)startService()与bindService()区别:
(a)started service(启动服务)是由其他组件调用startService()方法启动的,这导致服务的onStartCommand()方法被调用。当服务是started状态时,其生命周期与启动它的组件无关,并且可以在后台无限期运行,即使启动服务的组件已经被销毁。因此,服务需要在完成任务后调用stopSelf()方法停止,或者由其他组件调用stopService()方法停止。
(b)使用bindService()方法启用服务,调用者与服务绑定在了一起,调用者一旦退出,服务也就终止,大有“不求同时生,必须同时死”的特点。
(3)开发人员需要在应用程序配置文件中声明全部的service,使用标签。
(4)Service通常位于后台运行,它一般不需要与用户交互,因此Service组件没有图形用户界面。Service组件需要继承Service基类。Service组件通常用于为其他组件提供后台服务或监控其他组件的运行状态。
3、content provider
(1)android平台提供了Content Provider使一个应用程序的指定数据集提供给其他应用程序。其他应用可以通过ContentResolver类从该内容提供者中获取或存入数据。
(2)只有需要在多个应用程序间共享数据是才需要内容提供者。例如,通讯录数据被多个应用程序使用,且必须存储在一个内容提供者中。它的好处是统一数据访问方式。
(3)ContentProvider实现数据共享。ContentProvider用于保存和获取数据,并使其对所有应用程序可见。这是不同应用程序间共享数据的唯一方式,因为android没有提供所有应用共同访问的公共存储区。
(4)开发人员不会直接使用ContentProvider类的对象,大多数是通过ContentResolver对象实现对ContentProvider的操作。
(5)ContentProvider使用URI来唯一标识其数据集,这里的URI以content://作为前缀,表示该数据由ContentProvider来管理。
4、broadcast receiver
(1)你的应用可以使用它对外部事件进行过滤,只对感兴趣的外部事件(如当电话呼入时,或者数据网络可用时)进行接收并做出响应。广播接收器没有用户界面。然而,它们可以启动一个activity或serice来响应它们收到的信息,或者用NotificationManager来通知用户。通知可以用很多种方式来吸引用户的注意力,例如闪动背灯、震动、播放声音等。一般来说是在状态栏上放一个持久的图标,用户可以打开它并获取消息。
(2)广播接收者的注册有两种方法,分别是程序动态注册和AndroidManifest文件中进行静态注册。
(3)动态注册广播接收器特点是当用来注册的Activity关掉后,广播也就失效了。静态注册无需担忧广播接收器是否被关闭,只要设备是开启状态,广播接收器也是打开着的。也就是说哪怕app本身未启动,该app订阅的广播在触发时也会对它起作用。
二、android四大组件总结:
(1)4大组件的注册
4大基本组件都需要注册才能使用,每个Activity、service、Content Provider都需要在AndroidManifest文件中进行配置。AndroidManifest文件中未进行声明的activity、服务以及内容提供者将不为系统所见,从而也就不可用。而broadcast receiver广播接收者的注册分静态注册(在AndroidManifest文件中进行配置)和通过代码动态创建并以调用Context.registerReceiver()的方式注册至系统。需要注意的是在AndroidManifest文件中进行配置的广播接收者会随系统的启动而一直处于活跃状态,只要接收到感兴趣的广播就会触发(即使程序未运行)。
(2)4大组件的激活
内容提供者的激活:当接收到ContentResolver发出的请求后,内容提供者被激活。而其它三种组件activity、服务和广播接收器被一种叫做intent的异步消息所激活。
(3)4大组件的关闭
内容提供者仅在响应ContentResolver提出请求的时候激活。而一个广播接收器仅在响应广播信息的时候激活。所以,没有必要去显式的关闭这些组件。Activity关闭:可以通过调用它的finish()方法来关闭一个activity。服务关闭:对于通过startService()方法启动的服务要调用Context.stopService()方法关闭服务,使用bindService()方法启动的服务要调用Contex.unbindService()方法关闭服务。
(4)android中的任务(activity栈)
(a)任务其实就是activity的栈,它由一个或多个Activity组成,共同完成一个完整的用户体验。栈底的是启动整个任务的Activity,栈顶的是当前运行的用户可以交互的Activity,当一个activity启动另外一个的时候,新的activity就被压入栈,并成为当前运行的activity。而前一个activity仍保持在栈之中。当用户按下BACK键的时候,当前activity出栈,而前一个恢复为当前运行的activity。栈中保存的其实是对象,栈中的Activity永远不会重排,只会压入或弹出。
(b)任务中的所有activity是作为一个整体进行移动的。整个的任务(即activity栈)可以移到前台,或退至后台。
(c)Android系统是一个多任务(Multi-Task)的操作系统,可以在用手机听音乐的同时,也执行其他多个程序。每多执行一个应用程序,就会多耗费一些系统内存,当同时执行的程序过多,或是关闭的程序没有正确释放掉内存,系统就会觉得越来越慢,甚至不稳定。为了解决这个问题,Android引入了一个新的机制,即生命周期(Life Cycle)。
Activity
1、概念:
android 中,Activity 相当于一个页面,可以在Activity中添加Button、CheckBox 等控件,一个android 程序有多个Activity组成。
2、生命周期:
3、四种启动模式
Standard 模式 : standard 模式是android 的默认启动模式,在这种模式下,activity可以有多个实例,每次启动Activity,无论任务栈中是否已经存在这个activity的实例,系统都会创建一个新的activity实例。
SingleTop 模式: 栈顶模式,当一个singleTop模式的activity 已经位于栈顶时,再去启动它时,不在创建实例,如果不在栈顶,就会创建实例。
SingleTask 模式 : 单任务模式,如果启动的activity 已经存在于 任务栈中,则会将activity移动到栈顶,并将上面的activity出栈,否则创建新的实例
SingleInstance 模式 :单实例模式,一个activity 一个栈。
4、三种跳转方式
显示启动 :
Intrent 内部直接声明要启动的activity所对应的的class
Intent intent = new Intent(MainActivity.this, SecondActivity.class);
startActivity(intnet);
隐式启动
进行三个匹配,一个是activity,一个是category,一个是data,全部或者部分匹配,应用于广播原理
- 清单文件中 里配置activity属性,activity的名字要和跳转内容一样
<activity android:name="com.exanple.android.tst.secondActivity"android:label = @string/title><intent=filter><action android:name="com.exanple.android.tst.secondActivity/><category android:name="android.intent.category.DEFAULT"/><intent-filter/>
</activity>
12345678
- 在需要跳转的地方
Intent intent = new Intent("com.example.android.tst.secondActivity");
startActivity(intnet);
12
跳转后再返回,能获取返回值
Intent in = new Intent(MainActivity.this,OtehrActivity.class);
in.putExtra("a",a);
startActivityForResult(in,1000);
123
在OTherActivity中设置返回值
Intent int = new Intent();
int.putExtra("c",c);
setResult(1001,int);
finish();
1234
在MainActivity中获取返回值
@Override
protected void onActivityResult(int requestCode, int resultCode ,Intent data) {super.onActivityResult(requestCode,resultCode,data);if(requestCode == 1000){if(resultCode == 1001){int c = data.getExtra("c",0);}}
}
123456789
Service
定义一个Server
项目内Server包 右键 –> New –> Service –> Service 或者直接创建Class类,继承Service并重写IBinder方法
public class MyService extends Service{public MyService(){}@Overridepublic IBinder onBind(Intent intent) {return null;}@Overridepublic void onCreate() {super.onCreate();}@Overridepublic int onStartCommand(Intent intent, int flags, int startId) {return super.onStartCommand(intent, flags, startId);}@Overridepublic void onDestroy() {// TODO Auto-generated method stubsuper.onDestroy();}
}
123456789101112131415161718192021222324252627
重写Service的 onCreate()、onStartCommand()和onDestory()方法。其中 onCreate() 方法在服务创建的时候调用、onStartCommand() 方法会在每次服务启动的时候调用、onDestory() 方法会在服务销毁的时候调用。
通常情况下,如果我们希望服务一旦启动就立刻去执行任务,就可以将逻辑卸载onStartCommand() 方法里。
另外需要注意的是,每个服务都需要在Androidmanifest.xml 中进行注册才能生效:
<application....>...<serviceandroid:name=".MyService"android:enabled="true"android:exported="true"></service>
</application>
123456789
启动和停止服务
启动服务:
Intent startIntent = new Intent(this, MyService.class);
startService(startIntent); //启动服务
12
停止服务:
Intent stopIntent = new Intent(this, MyService.class);
stopService(stopIntent); //停止服务
12
使用前台服务
前台服务与普通服务的最大区别在于,它会一直有一个正在运行的图标在系统的状态栏中,下拉状态栏后可以看到更加详细的内容,非常类似于通知的效果。
public class MyService extends Service{Intent intent = new Intent(this, MainActivity.class);PendingIntent pi = PendingIntent.getActivity(this, 0 , intent, 0);Notification notification = new NotificationCompat.Builder(this).setContentTitle(" this is content titile").setContentText("this is content text").setWhen(System.currentTimeMillis()).setSmallIcon(R.mipmap.ic_launcher);.setLargeIcon(BitmapFactory.decodeResource(getResource(),R.mipmap.ic_launcher)).setContentIntent(pi).build();startForeground(1,notification);
}
1234567891011121314
构造一个Notification对象后并没有使用NotificationManager 来讲通知显示出来,而是调用了startForeground()方法,该方法会将MyService变成一个前台服务,并在系统状态栏中显示出来。
使用IntentService
服务中的代码都默认运行在主线程中,如果直接在服务中执行耗时操作很容易出现ANR(Application not Responding)
所以这个时候需要用到Android多线程编程技术,我们应该在服务的每个具体的方法里启动一个子线程,然后在这里去处理那些耗时的操作:
public class MyService extends Service{...@Overridepublic int onStartCommand(Intent intent , int flags, int startId){new Thread(new Runnable(){public void run(){//处理具体的逻辑}}).start();return super.onStartCommand(intent, flags, startId);}
}
123456789101112
但是,这种服务一旦启动之后,就会一直处于运行状态,必须调用stopService()或者stopSelf()方法才能让服务停止下来,所以,如果想要实现让一个服务在执行完毕后自动停止的功能,就可以这样写:
public class MySerivce extends Servcie{...@Overridepublic int onStartCommand(Intent intent, int flats , int startId){new Thread(new Runnable(){public void run(){//处理具体的逻辑stopSelf();}});}
}
123456789101112
虽说这样的写法并不复杂,但是总会有一些程序员忘记开启线程或者忘记调用stopSelf() 方法。为了简单创建一个异步、会自动停止的服务。Android专门提供了一个IntentService类
public class MyIntentService extends IntentService{public MyIntentService(){super("MyIntentService"); //调用父类的有参构造方法}@Overrideprotected void onHandleIntent(Intent intent){ //打印当前的线程IDLog.e("mylog","Thread id is” + Thread.cuttentThread().getId();}@Overridepublic void onDestory(){super.onDestory();Log.e("mylog","on Destory executed");}
}
123456789101112131415
首先这里提供一个无参的构造方法,并且必须在其内部调用父类的有参构造方法。然后要在子类中去实现onHandleIntent() 这个抽象方法,在这个方法中可以去处理一些逻辑,而且不用担心ANR,因为这个方法已经是在子线程中运行了。
IntentService线程的调用:
Intent intent = new Intent(this, MyIntentService.class);
startServcie(intent);
12
如此,线程就会自动启动并执行逻辑,执行完毕后自动关闭。这就是IntentService 的好处,能够自动开启和关闭;
Content Provider
对于每一个应用程序来说,如果想要访问内容提供器中共享的数据,就一定要借助ContentResolver 类,可以通过Context中的getContentResolver() 方法获取该类的实例。ContentResolver中提供了一系列的方法用于对数据进行CRUD操作,其中insert() 方法用于添加数据,update() 方法用于更新数据,delete() 方法用于删除数据,query() 方法用于查询数据。
不同于SQLiteDatabase,ContentResolver 中的增删改查都是接收一个URl参数,这个参数被称为内容URL。内容URL给内容提供器中的数据建立了唯一标识符,它主要由两部分组成:authority 和 path 。authority 是用于对不同的应用程序做区分的,一般为了避免冲突,都会采用程序包名的方式进行命名。path则是用于对同一应用程序中不同的表做区分,通常都会添加到authority后面:
content://com.example.app.provider/table1
content://com.example.app.provider/table2
12
在使用内容URL作为参数的时候,需要将URL转换成URL对象:
Uri uri = Uri.parse("content://com.example.app.provider/table1")
1
现在我们就可以使用这个uri对象来查询talbe1表中的数据了:
Cursor cursor = getContentResolver().query(uri,projection,selection,selectionArgs,sortOrder
);
1234567
对应参数的解释:
query()方法参数 | 对应SQL部分 | 描述 |
---|---|---|
uri | from table_name | 指定查询某个应用程序下的某个表 |
projection | select column1, column2 | 指定查询的列名 |
selection | where column=value | 指定where约束条件 |
selectArgs | – | 为where中的占位符提供具体的值 |
orderBy | order by column1, column2 | 指定查询结果的排序方式 |
查询完之后,就可以从游标中取值了:
if(cursor != null){while(cursor.moveToNext()) {String column1 = cursor.getString(cursor.getColumnIndex("column1"));int column2 = cursor.getInt(cursor.getColumnIndex("column2"));}cursor.close();
}
1234567
增删改查
添加数据
ContentValues values = new ContentValues();
values.put(“column1”, "text");
values.put("column2", 1);
getContentResolver().insert(uri, values);
1234
更新数据
ContentValues valuse = new ContentValues();
valuse.put("column1", "");
getContentResolver().update(uri, values, "column1 = ? and column2 = ?", new String[]{"text", 1});
123
删除数据
getContentResolver().delete(uri , "column2 = ?", new String[]{ "1"});
1
实例.
读取系统联系人
读取系统联系人需要声明权限,如果系统是6.0以后的,需要申请运行时权限
if(ContextCompat.checkSelfPermission(this, Manifest.permission.READ_CONTACTS) != PackageManager.PERMISSION_GRANTED) {ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.READ_CONTACTS}, 1);}else {readContacts(); //读取联系人}
private void readContacts(){Cursor cursor = null;try{//查询联系人数据cursor = getContentResolver().query(ContactsContract.CommonDataKinds.Phone.CONTENT_URI,null,null,null,null);if(cursor!=null){while(cursor.moveToNext()){//获取联系人姓名String name = cursor.getString(cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME));//获取联系人电话号码String number = cursor.getString(cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER));list.add(name+"\n"+number);}}}catch(Exception e){e.printStackTrace()}finally{if(cursor != null){cursor.close();}}
}@Override
public void onRequestPermissionResult(int requestCode, String[] permissions, int[] grantResults){switch(requestCode){case 1:if(grantResults.length >0 && grantResults[0] == PackageManager.PERMISSION_GRANTED){readContacts();}else {//您拒绝了权限}}
}
创建自己的内容提供器
创建自己的内容提供器,需要去继承 ContentProvider 类,ContentProvider 类中有6个抽象方法,我们在使用子类继承它的时候,需要将这6个方法全部重写。
public class MyProvider extends ContentProvider{@Overridepublic boolean onCreate() {return false;}@Overridepublic Cursor query(Uri uri, String[] projection, Stirng selection, String[] selectionArgs, String sortOrder){return null;}@Overrridepublic Uri insert(Uri uri , ContentValues values){return null;}@Overridepublic int update(Uri uri, ContentValuse values, String selection, String[] selectionArgs){return 0;}@Overridepublic int delete(Uri uri, String selection, String[] selectionArgs){return 0;}@Overridepublic String getType(Uri uri){return null;}
}
URI 的主要格式有以下两种
content://com.example.app.provider/table1
content://com.example.app.provider/table1/1* : 表示匹配任意长度的任意字符
# : 表示匹配任意长度的数字//一个能够匹配任意表的内容URI格式就可以写成:
content://com.example.app.provider/*
//一个能够匹配表中任意一行数据的内容URI格式就可以写成:
content://com.example.app.provider/table1/#
12345678910
Broadcast Receiver
android 广播分为两个角色:广播发送者、广播接收者
android 广播:
1),用于不同组件间的通信(含:应用内/不同应用之间)
2),用于多线程通信
3),与android系统的通信
自定义广播接收者
- 继承BroadcastReceive 基类
- 必须重写抽象方法onReceive()方法
1,广播接收器收到相应广播后,会自动调用onReceive() 方法
2,一般情况下,onReceive方法会会涉及与其他组件之间的交互,如 发送Notiotification,启动server等
3,默认情况下,广播接收器运行在UI线程,因此,onReceive方法不能执行耗时操作,否则将导致ANR
123
广播接收器注册
注册的方式有两种:静态注册、动态注册
静态注册
- 注册方式:在AndroidManifest.xml 里通过<receive 标签声明
- 属性说明
<receiverandroid:enable="true"/"false"//此broadcastReceiver 是否接受其他应用发出的广播//默认值时由receiver 中d有无inter-filter决定,如果有,默认true,否则默认falseandroid:exported="true"/"false"android:icon="drawable resource"android:label="string resource"//继承BroadcastReceiver子类的类名android:name=".mBroadcastReceiver"
//具有相应权限的广播发送者发送的广播才能被此BroadcastReceiver所接收;android:permission="string"
//BroadcastReceiver运行所处的进程
//默认为app的进程,可以指定独立的进程
//注:Android四大基本组件都可以通过此属性指定自己的独立进程android:process="string" >//用于指定此广播接收器将接收的广播类型
//本示例中给出的是用于接收网络状态改变时发出的广播<intent-filter><action android:name="android.net.conn.CONNECTIVITY_CHANGE" /></intent-filter></receiver>
12345678910111213141516171819202122
注册示例:
<receiver //此广播接收者类是mBroadcastReceiverandroid:name=".mBroadcastReceiver" >//用于接收网络状态改变时发出的广播<intent-filter><action android:name="android.net.conn.CONNECTIVITY_CHANGE" /></intent-filter>
</receiver>
12345678
当此APP首次启动时,系统会自动实例化mBroadcastReceiver类,并注册到系统中。
动态注册
- 注册方式:在代码中调用Context.registerReceiver() 方法
- 具体代码如下:
// 1. 实例化BroadcastReceiver子类 & IntentFiltermBroadcastReceiver mBroadcastReceiver = new mBroadcastReceiver();IntentFilter intentFilter = new IntentFilter();// 2. 设置接收广播的类型intentFilter.addAction(android.net.conn.CONNECTIVITY_CHANGE);// 3. 动态注册:调用Context的registerReceiver()方法registerReceiver(mBroadcastReceiver, intentFilter);//动态注册广播后,需要在相应位置记得销毁广播
unregisterReceiver(mBroadcastReceiver);
12345678910111213
特别注意
动态广播最好在onResume中注册, onPause注销
原因:
1,对于动态广播,有注册必然得有注销,否则会导致内存泄漏
2,onPause在App死亡前一定会被执行,从而保证app死亡前一定会被注销,从而防止内存泄漏
两种注册方式的区别
广播的发送
广播的发送 = 广播发送者 将此广播的意图(intent)通过 sendBroasdcast() 方法发送出去
广播的类型
- 普通广播 系统广播 有序广播 粘性广播 App 应用内广播
特别注意:
对于不同注册方式的广播接收器回调OnReceive(Context context,Intent intent)中的context返回值是不一样的:
- 对于静态注册(全局+应用内广播),回调onReceive(context,
intent)中的context返回值是:ReceiverRestrictedContext; - 对于全局广播的动态注册,回调onReceive(context, intent)中的context返回值是:Activity
Context; - 对于应用内广播的动态注册(LocalBroadcastManager方式),回调onReceive(context,
intent)中的context返回值是:Application Context。 - 对于应用内广播的动态注册(非LocalBroadcastManager方式),回调onReceive(context,
intent)中的context返回值是:Activity Context;
Android 五大存储
SharedPreferences 方式
SharedPreferences 是使用键值对的方式进行存储数据的。
想要使用SharedPreferences 来存储数据,首先主要获取到SharedPreferences 对象。Android提供了三种方法用于获取SharedPreferences对象:
1,Context类中的getSharedPreferences()方法
//此方法接收两个参数,一个参数用于指定SharedPreferences文件的名称,如果指定的文件不存在则会创建一个,SharedPreferences文件都是存放在/data/data/<package name>/shared_prefs/目录下
//第二个参数用于指定操作模式,目前只有MODE_PRIVATE这种模式,和直接传入0效果相同
SharedPreferences.Editor editor = getSharedPreferences("data",MODE_PRIVATE).edit();
editor.putString("name", "Tom");
editor.putInt("age",13);
editor.putBoolean("married",false);
editor.apply();
2,Activity类中的getPreferences()方法
//这个方法和Context中的getSharedPreferences()方法很类似,不过它只接收一个操作模式,因为使用这个方法时会自动将当前活动的类名作为SharedPreferences的文件名
1
3,PreferencesManager类中的getDefaultSharedPreferences()方法
//这是一个静态方法,它接收一个Context参数,并自动使用当前应用程序的包名作为前缀来命名SharedPreferences文件
得到了SharedPreferences对象后, 就可以开始想SharedPreferences文件中存储数据了,主要可以分为三步:
(1)调用SharedPreferences对象的edit()方法来获取一个SharedPreferences.Editor对象
(2)向SharedPreferences.Editor 对象中添加数据,比如添加一个布尔值,可以使用putBoolean() 方法
(3)调用apply()方法的添加的数据提交,从而完成数据存储操作
SharedPreferences中读取数据
SharedPreferences pref = getSharedPreferences("data",MODE_PRIVATE );
String name = pref.getString("name","");
int age = pref.getInt("age",0);
boolean married = pref.getBoolean("married", false);
文件存储方式
SQList 存储方式
Android 为了让我们能够更加方便的管理数据库,专门提供了一个SQLiteOpenHelper 帮助类,借助这个类可以非常简单的将数据库进行创建好升级。
SQLiteOpenHelper 中有两个非常重要的实例方法,getReadableDatabase() 和 getWritableDatabase() 。这两个方法可以创建或者打开一个现有的数据库(如果数据库存在则直接打开,否则创建一个新的数据库),并返回一个可对数据库进行读写操作的对象。不同的是,当数据库不可写入(如磁盘空间已满),getReadableDatabase方法返回的对象将以只读的方式打开数据库,而getWeitableDatabase则出现异常
例子(在指定路径下创建数据库文件 .db )
public class MainActivity extends Activity {public static final String PATH_ONE = "KogBill";public static final String PATH_NAME = "KogBill.db";private SQLiteDatabase db; //声明SQLiteDatabase ,该对象可以操作数据库String path = Environment.getExternalStorageDirectory().getAbsolutePath();String path1 = path + File.separator + PATH_ONE; //需要创建的路径String path2 = path + File.separator + PATH_ONE + File.separator + PATH_NAME; //需要创建的文件@Overrideprotected void onCreate(Bundle savedInstanceState){super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);File f = new File(path1);if( !f.exists()){ //创建数据库文件路径f.mkdirs();} //实例化MySQLiteHelper ,创建指定目录下数据库文件,并创建表MySQLiteHelper mSQL = new MySQLiteHelper(MainActivity.this, path2);db = mSQL.getWritableDatabase();}class MySQLiteHelper extends SQLiteOpenHelper{private static final int DATABASE_VERSION = 1;//数据库版本号private static final String CREATE_TABLE = "create table kog_bill ("+ "_id integer primary key autoincrement,"+ "date text, "+ "breakfast text, "+ "lunch text,"+ "dinner text,"+ "happy text,"+ "other text,"+ "spare text)";//方便创建实例,简化构造方法,方法内调用4参数构造方法//参数 name 可以是 数据库名称,也可以数据库文件路径(即可以指定数据库文件路径)public MySQLiteHelper(Context context, String name) {this(context, name, null, DATABASE_VERSION);}//必须要实现的方法public MySQLiteHelper(Context context, String name, CursorFactory factory, int version) {super(context, name, factory, version);}@Overridepublic void onCreate(SQLiteDatabase db) {// 第一次创建数据库时 才会调用Log.e("mylog", "创建数据库表");db.execSQL(CREATE_TABLE);}@Overridepublic void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {}}
}
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758
根据上述代码,便获得db对象,通过db(SQLiteDatabase)可进行数据库的操作,如 db.query() db.delete()
如果我们想在创建一个数据库表,参照上述代码,可以在SQLiteOpenHelper的onCreate方法中加入语句:
@Override
public void onCreate(SQLiteDatebase db) {db.execSQL(CREATE_TABLE);db.execSQL(CREATE_BOOK); //新创建一个数据库表
}
12345
然后重新运行一下,发现并没有创建成功,因为KogBill.db数据库已经存在,所以MySQLiteHelper 中的onCreate方法都不会执行,解决这个办法的方法很简单,只需要将db文件删除,重新运行,便可成功,但是原来数据库中的数据都会被删除。所以需要用到MySQLiteHelper中的update方法。
class MySQLiteHelper extends SQLiteOpenHelper{.....@Overridepublic void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion){db.execSQL("drop table if exists book"); //如果已经存在就删除,防止重复创建onCreate(db); // 再次执行onCreate 方法}
}
12345678
但是onUpgrade方法默认是不执行的,如何让onUpgrade方法执行,需要用到MySQLiteHelper 构造参数中的版本号:
private static final int DATABASE_VERSION = 1;// 将版本号 由 1 改为2
1
这里将数据库版本号由1改为2,表示对数据库的升级
数据库的增删改查
添加数据
ContentValues values = new ContentValues();values.put("date", str1.isEmpty()?"0.0":str1);values.put("breakfast", ""+str2);values.put("lunch", ""+str3);values.put("dinner", ""+str4);values.put("happy", ""+str5);values.put("other", ""+str6);values.put("spare", ""+str7);long ii = db.insert("kog_bill", "", values);values.clear();if(ii != -1) {Utils.showToast("保存成功!", MainActivity.this);}else {Utils.showToast("保存失败!", MainActivity.this);}
123456789101112131415
更新数据
ContentValues valus = new ContentValues();
valuse.put("other","12");
db.update("kogBill", values, "_id=?",new String[]{id});
123
删除数据
db.delete("kogBill", "_id=?",new String[]{id});
1
查询数据
db.query("kog_bill", new String[]{"_id","date","breakfast","lunch","dinner","happy","other","spare"}, null, null, null, null, "date desc");
12
使用SQL操作数据库
虽然Android 已经给我们提供了非常方便的API用于操作数据库,不过总会有些人不习惯去使用这些辅助行的方法,而是更加青睐于直接使用SQL来操作数据库,当然Android也是提供的。
添加数据
db.execSQL("insert into kogBill ("date","breakfest","lunch","dinner","happy","other","spare") values (?,?,?,?,?,?,?)", new String[]{"1921-1-1",“123”,“1”,“1”,“11”,“2”,“3”});
1
更新数据
db.execSQL("update kogBill set other = ? where _id = ? ", new String[]{"12",id});
1
删除数据
db.execSQL("delete from kogBill where _id = ?”, new String[]{id});
1
使用 LitePal 操作数据库
假设编译环境为AndroidStudio。
1,引进包
dependencies{compile fileTree(dir:'libs', include:['*.jar'])compile 'com.android.support:appcompat-v7:23.2.0'testCompile 'junt:junt:4.12'compile 'org.litepal.android:core:1.3.2' //引入litepal包
}
123456
2,配置litepal.xml 文件
右键app/src/main 目录->New -> Directory ,创建一个assets目录,然后在 assets目录下再新建一个litepal.xml 文件,接着编辑文件中的内容
<?xml version='1.0' encoding="utf-8"?>
<litepal><dbname value = "BookStore"></dbname><version value="1"></version><list></list>
</listepal>
123456
其中,<dbname 标签用来指定数据库名,<version 用来指定数据库版本号,<list 标签用来指定所有映射模型。
最后还需要在配置以下 LitePalApplication, 修改AndroidManifest.xml 中的代码
<manifest xmlns:android="http://schemas.android.com/apk/res/android"package="com.example.litepaltest" ><applicationandroid:name="org.litepal.LitePalApplication" //配置 LitePalApplicationandroid:allowBackup="true".....</application>
</manifest>
12345678
以上,LitePal的配置工作已经结束了,接下来使用LitePal。
首先将需要实现 javabean类 对应 数据库表.
然后将javabean类添加到映射模型列表中,修改litepal.xml 中的代码
<litepal><dbname value="kogBill" ></dbname><version value="1"></version><list><mapping class="com.example.litepaltest.book"></mapping> //javabean类的路径</list>
123456
这样所有工作就已经完成,现在只要进行任意一次数据库的操作,数据库db文件就会自动创建,比如:
Connector.getDatabase();
1
操作数据
如果需要对某个表进行数据操作,需要让其对应的javaBean类继承DataSupport
public class Book extends DataSupport { //让对应的类继承DataSupport...
}
123
接下来,进行添加数据的操作:
Book book = new Book();
book.setName("...");
book.setAuthor("...");
book.setPages(234);
book.setPrice(12,21);
book.setPress("unkow");
book.save(); //执行sava 就可以插入数据了
1234567
执行更新数据:
Book book = new Book();
book.setPrice(11.11);
book.setPress("Anchor");
book.updateAll("name = ? and authro = ?","..","...");
1234
删除数据:
DataSupport.deleteAll(Book.class, "price<?","13");
1
查询数据:
//查询所有
List<Book> books = DataSupport.findAll(Book.class);
// 查询第一条
List<Book> books = DataSupport.findFirst(Book.class);
//查询最后一条
List<Book> books = DataSupport.findLast(Book.class);
//查询那几列的数据
List<Book> books = DataSupport.select("name","author).find(Book.class);
//条件查询, 页面大于400
List<Book> books = DataSupport.where("pages >?","400").find(Book.class);
//将 price 降序排序
List<Book> books = DataSupport.order(price desc").find(Book.class);
//查询前3条
List<Book> books = DataSupport.limit(3).find(Book.class);
//从下表1开始,往后查询3条
List<Book> boods = DataSupport.limit(3).offset(1),find(Book.class)
12345678910111213141516
当然这些方法也可以组合起来使用:
List<Book> books = DataSupport.select("name","author","pages").where("pages>?”,"400").order("pages").limit(10).offset(10).find(Book.class);
1234567
如果有些特殊查询,使用上述方法无法查询时,可以使用如下语句:
Cursor c = DataSupport.findBySQL("select * from Book where pages > ? and price < ?”, "400","20”);
12
内容提供器(Conent Provider)方式
网络存储方式
Android 六大布局
LinearLayout 线性布局
线性布局,如名字所描述的那样,这个布局将它所包含的控件在线性方向上一次排列,方向分为 水平方向和数值方向。
属性 android:orientation = “vertical” | “horizontal” 竖直或水平,默认水平
属性 android:layout_gravity = “top” | “center” | “bottom” 内部的布局方式
属性 android:gravity = “top”|"center”|“bottom” 相对于父容器的对齐方式
属性 android:layout_weidht 使用比例方式执行控件的大小,在手机屏幕适配方面起到非常重要的作用
TableLayout 表格布局
表格布局与HTML中的table td tr标签类似
<table><tr><td></td></tr>
</table>1234
如何确定行与列
- 如果在TableLayout下添加组件,这个组件会占满整行
- 如果想把多个组件放在同一行,需要添加TableRow的容器,然后把组件放进去
- TableRow中的组件个数决定的该行的列数,而列的宽度由列中最宽的单元格决定
- TableRow嗯layout_width属性默认是fill-parent,修改无效。但是layout_height默认是wrapcontent,可以修改
- 整个表格的宽度取决于父容器的宽度(占满父容器)
重要的属性:
- android:collapaseColumns:设置需要被隐藏的列的序号
- android:shrinkColumns:设置允许被收缩的列的序号
- android:stretchCoumns:设置运行被拉伸嗯列的序号
这三个属性都是从0开始算的
shrinkColumns= "2" //对应第三行
shrinkColumns = '"0,2" //设置多个都生效
shrinkColumns = "" //所有列都生效
123
- android:layout_column=“2”: 表示跳过第二个,直接显示第三个,从1开始
- android:layout_span=“4”:表示合并*4个单元格,也就说这个组件占4个单元格
FrameLayout 帧布局
RelativeLayout 相对布局
GridLayout 网格布局
AbsoluteLayout 绝对布局
序列化(使用Intent传递对象)
序列化的作用:
- ①永久性保存对象,保存对象的字节序列到本地文件中;
- ②通过序列化对象在网络中传递对象;
- ③通过序列化在进程间传递对象;
两种序列化(使用Intent传递对象)的方法:
- 实现Serializable接口
(1)是Java SE本身就支持的。
(2)序列化的方法:只需让一个类实现Serializable这个接口即可。(implement Serializable)
(3)获取传递的序列化对象:调用getSerializableExtra() 方法来获取通过参数传递过来的序列化对象。
(4)Serializable是序列化的意思,表示将一个对象转换成可存储或可传输状态,序列化后的对象可以在网络上进行传输,也可以存储到本地。 - 实现Parcelable接口
(1)是Android特有的功能,效率比实现Serializable接口更高效,可用于Intent数据传递,也可用于进程间通信(IPC)。
(2)序列化的方法:首先需要实现Parcelable接口,然后重写describeContents()和writeToParcel()两个方法。其中describeContents()方法直接返回0即可,而writeToParcel()方法中我们需要调用Parcel的writeXXX()方法,将类中的字段一一写出。注意,字符串数据就调用writeString()方法,整型数据就调用writeInt()方法,以此类推。
(3)获取传递的序列化对象:调用getParcelableExtra() 方法来获取传递过来的对象。
(4)除了Serializable之外,使用Parcelable也可以实现相同的效果,不过不同于将对象进行序列化,Parcelable方式的实现原理是将一个完整的对象进行分解,而分解后的每一部分都是Intent所支持的数据类型,这样就可以实现传递对象的功能了。
二者的不同:
- Parcelable效率更高且消耗内存小,推荐使用Parcelable提高性能。
- 尽管Serializable效率低,但Parcelable不能使用在要将数据存储在磁盘上的情况,因为Parcelable在外界有变化的情况下不能很好地保证数据的持续性。尽管Serializable效率低点,但此时还是建议使用Serializable。
- Serializable使用了反射,在序列化的时候会产生大量的临时变量,从而引起频繁的GC。
总结:Android上应该尽量采用Parcelable,效率至上,效率远高于Serializable。(建议在网络传输时采用Serializable,在Android程序内使用Parcelable)
线程间通讯
我们知道线程是CPU调度的最小单位。在Android中主线程是不能够做耗时操作的,子线程是不能够更新UI的。而线程间通信的方式有很多,比如广播,Eventbus,接口回调,在Android中主要是使用Handler。Handler通过调用sendmessage()方法,将发送的消息Message保存到Messagequeue中,而looper对象不断的调用loop()方法,从messageueue中取出message,交给Handler处理,从而完成线程间通信。
数据加密
- 1、Base64:通常将数据转换为二进制数据。
- 2、DES加密:由于DES数据加密算法中密钥长度较小(56位),已经不适应当今分布式开放网络对数据加密安全性的要求。
- 3、AES加密:AES是一个迭代的、对称密钥分组的密码,AES加密数据块分组长度必须为128bit,密钥长度可以是128bit、195bit、256bit中的任意一个。
- 4、RSA加密:RSA算法可以实现非对称加密,公钥发布供任何人使用,私钥则为自己所有,供解密之用。
- 5、MD5(信息-摘要算法5)算法:用于确保信息传输完整一致,是计算机广泛使用的杂凑算法之一。MD5算法将数据(如汉字)运算为另一固定长度值。
注:采用DES与RSA相结合的应用,使它们的优缺点正好互补,即DES加密速度快,适合加密较长的报文,可用其加密明文;RSA加密速度慢,安全性好,应用于DES 密钥的加密,可解决DES 密钥分配的问题。目前这种RSA和DES结合的方法已成为EMAIL保密通信标准。
数据持久化
(1)瞬时数据:指那些存储在内存中,有可能因为程序关闭或其他原因导致内存被回收而丢失的数据。
(2)数据持久化:指将那些内存中的瞬时数据保存到设备中,保证即使在手机关机的情况下,这些数据仍然不会丢失。Android系统的数据持久化主要有三种方式:即文件存储、SharedPreference存储、数据库存储,此外还可以将数据存入SD卡中。
-
①文件存储
文件存储不对存储的内容进行任何的格式化处理,所有数据都是原封不动地保存到文件当中,因此适合用于存储一些简单的文本数据或二进制数据。//1、将数据存储到文件 String data = "XXX"; //Context类提供了一个openFileOutput()方法,用于将数据存储到指定的文件中 //该方法有两个参数,第一个参数是文件名(不可以包含路径,因为存储路径默认) //第二个参数是文件的操作模式,有两种模式:MODE_PRIVATE和MODE_APPEND //MODE_PRIVATE:默认操作模式,表示当指定同样文件名的时候,所写入的内容会覆盖原文件中的内容。 //MODE_APPEND:表示如果文件已存在,就往文件里追加内容,不存在就创建新文件。 FileOutputStream out = openFileOutput("data", Context.MODE_PRIVATE); BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(out)); writer.writer(data);//2、从文件中读取数据 //Context类还提供了一个openFileInput()方法,用于从文件中读取数据 //系统回到默认目录下加载文件,并返回一个FileInputStream对象,在通过Java流的方式将数据解析出来 1234567891011121314
-
②SharedPreferences存储
详见:Android学习笔记:SharedPreferences
SharedPreferencds文件是使用XML格式来对数据进行管理的。 -
③数据库存储-SQLite(下见72)
SQLite是一款轻量级的关系数据库,它的运行速度非常快,占用资源很少,通常只需要几百KB的内存。此外,SQLite不仅支持标准的SQL语法,还遵循了数据库的ACID事务。
Android的SQLite主要用于较大的数据持久化保存,以达到节省客户流量的作用。 -
④数据库存储-LitePal
LitePal项目主页地址:https://github.com/LitePalFramework/LitePal
LitePal是一款开源的Android数据库框架,采用了对象关系映射(ORM)模式。1)修改LitePal数据库时,只需修改内容,然后将版本号加1即可。2)LitePal进行表管理操作时不需要模型类有任何的继承结构,但是进行CRUD(指计算机处理时的增加(Create)、读取查询(Retrieve)、更新(Update)、删除(Delete)几个单词的首字母)操作时,必须要继承自DataSupport类。3)对于LitePal,对象是否已存储就是根据调用model.isSaved()方法的结果来判断的,返回true就表示已存储,返回false就表示未存储。4)LitePal仍支持使用原生的SQL来进行查询。
动画(Animation)
Android中共有3种动画,分别是:Tween Animation(补间动画)、Frame Animation(帧动画)、Property Animation(属性动画)。
- 1、Tween Animation(补间动画)
补间动画是通过特定的对象让视图组件实现旋转、平移、缩放、改变透明等一系列动画效果,该动画不会改变原有控件的位置。根据动画效果的不同,补间动画又可以分为旋转动画、平移动画、缩放动画、透明度渐变动画4种。 - 2、Frame Animation(帧动画)
帧动画是通过加载一系列的图片资源,按照顺序播放排列好的图片。 - 3、Property Animation(属性动画)
属性动画和补间动画的使用基本没有区别,但是属性动画可以改变控件的属性。
运行时权限
Android将所有权限归成了两类,一类是普通权限,一类是危险权限。
- 普通权限(Normal Permission):指那些不会直接威胁到用户的安全和隐私的权限,对于这部分权限的申请,无需用户进行授权,只需在AndroidManifest.xml中简单声明即可,在安装程序时Android会自动进行授权,无需每次使用时都检查权限,而且用户不能取消以上授权。
- 危险权限(Dangerous Permission):表示那些可能会触及用户隐私或者对设备安全性造成影响的权限,如获取设备联系人信息等,对于这部分权限申请,必须要由用户手动点击授权才可以,否则程序就无法使用相应的功能。
Android中的所有危险权限,一共是9组24个权限。我们在进行运行时权限处理时使用的是权限名,但是用户一旦同意授权了,那么该权限所对应的权限组中所有的其他权限也会同时被授权。
Android在6.0及以上系统在使用危险权限的时候都必须进行运行时权限处理。
Android 6.0系统默认认为targetSdkVersion小于23的应用授予了所申请的所有权限,所以如果App的targetSdkVersion小于23,在运行时也不会崩溃。
XML与JSON的区别
(1)XML是重量级的,JSON是轻量级的;
(2)XML在传输的过程中比较占带宽,JSON占带宽少,易于压缩;
(3)XML与JSON都用在项目交互下,XML多用作配置文件,JSON用于数据交互;
(4)XML可以通过SAX、DOM、Pull等方式解析,JSON可通过json-lib、Jackson、JsonObject、Gson、FastJson等方式解析;
(5)JSON语义较差,看起来不如XML直观;
视频播放方式
- 1、使用自带的播放器
使用Intent设置ACTION_VIEW来调用系统的播放器。这种方式播放视频,主要是指定Action为ACTION_VIEW、Data为Uri、Type为MIME类型(MIME类型就是设定某种扩展名的文件用一种应用程序来打开的方式类型,当扩展名文件被访问时,浏览器会自动使用指定应用程序来打开)。 - 2、使用VideoView控件播放视频
VideoView控件需要与MediaController类相结合来播放视频。 - 3、使用MediaPlayer与SurfaceView播放视频
可以直接从内存或者DMA等硬件接口中取得图像数据,是个非常重要的绘图容器。它可以在主线程之外的线程向屏幕绘图,这样可以避免画图任务繁重时造成主线程阻塞,从而提高了程序的反应速度。
使用MediaPlayer与SurfaceView播放视频的步骤如下:
(1)创建MediaPlyer对象,并让其加载指定的视频文件。
(2)在布局文件中定义SurfaceView组件,或在程序中创建SurfaceView组件,并为SurfaceView的SurfaceHolder添加Callback监听器。
(3)调用MediaPlayer对象的setDisplay()方法将所播放的视频图像输出到指定的SurfaceView组件。
(4)调用MediaPlayer对象的start()、stop()、pause()方法控制视频的播放状态。
进程、线程与消息通信
(1)不要通过Intent在Android基础组件之间传递大数据(binder transaction缓存为1MB),可能导致OOM。
(2)在Application的业务初始化代码加入进程判断,确保只在自己需要的进程初始化。特别是后台进程减少不必要的业务初始化。
(3)新建线程时,必须通过线程池提供(AsyncTask或者ThreadPoolExecutor或者其他形式自定义的线程池),不允许在应用中自行显式创建线程。
说明:使用线程池的好处是减少在创建和销毁线程上所花的时间以及系统资源的开销,解决资源不足的问题。如果不使用线程池,有可能造成系统创建大量同类线程而导致消耗完内存或者“过度切换”的问题。另外,创建匿名线程不便于后续的资源使用分析,对性能分析等会造成困扰。
(4)线程池不允许使用Executors去创建,而是通过ThreadPoolExecutor的方式,这样的处理方式可以避免资源耗尽的风险。
Executors返回的线程池对象的弊端如下:
- FixedThreadPool和SingleThreadPool:允许的请求队列长度为Integer.MAX_VALUE,可能会堆积大量的请求,从而导致OOM。
- CachedThreadPool和ScheduledThreadPool:允许的创建线程数量为Integer.MAX__VALUE,可能会创建大量的线程,从而导致OOM。
(5)子线程中不能更新界面,更新界面必须在主线程中进行,网络操作不能在主线程中调用。
(6)不要在非UI线程中初始化ViewStub,否则会返回null。
Activity的四种状态
running / paused / stopped / killed
running: 表明Activity处于活动(完全可见)状态,用户可以与屏幕进行交互,此时,Activity处于栈顶。
paused: 表明Activity处于失去焦点的状态(例如:被非全屏的的Activity覆盖),此时用户无法与该Activity进行交互。
stopped: 表明activity处于不可见的状态(例如:被另一个Activity完全覆盖)。
killed: 表明Activity被系统回收了,