Android 入个门

Android

整理了一些Android很经典的几个问题。其实是选修课作业,顺道吹一吹这门课(

附件:部分源代码

1、Android应用的基本组件有哪些?

  1. Activity:应用表示层(基类Activity)

一个活动表示一个可视化的用户界面,同一应用中的每个Activity是相互独立的。每一个活动都是作为Activity基类的一个子类的实现。应用程序中的每个屏幕都是通过继承和扩展基类Activity来实现的。Activity窗口内的可见内容通过基类View提供。

  1. Service:没有可见的用户界面,但能够长时间运行于后台(基类Service)

一个没有用户界面的在后台运行执行耗时操作的应用组件。其他应用组件能够启动Service,并且当用户切换到另外的应用场景,Service将持续在后台运行。另外,一个组件能够绑定到一个Service与之交互,所有这些活动都是在后台进行。Service与Activity一样都存在于当前进程的主线程中,所以一些阻塞UI的操作不能放在Service里进行。

  1. Broadcast Receiver:用户接收广播通知的组件(基类BroadcastReceiver)

广播接收者仅是接受广播公告并作出相应的反应。许多广播源自于系统,应用程序也可以发起广播。一个应用程序可以有任意数量的广播接收者去反应任何它认为重要的公告。所有的接受者继承自BroadcastReceiver基类。BroadcastReceiver自身并不实现图形用户界面,但是当它收到某个通知后,可以启动 Activity作为响应,或者通过NotificationMananger提醒用户。

  1. Content Provider:应用程序间数据通信、共享(基类ContentProvider)

内容提供者使一个应用程序的指定数据集提供给其他应用程序,继承自ContentProvider 基类并实现了一个标准的方法集,使得其他应用程序可以检索和存储数据。应用程序使用一个ContentResolver对象并调用它的方法。ContentResolver能与任何内容提供者通信,它与提供者合作来管理参与进来的进程间的通信。

  1. Intent:连接组件的纽带通信

Intent在不同的组件之间传递消息,将一个组件的请求意图传给另一个组件。因此, Intent 是包含具体请求信息的对象。针对不同的组件,Intent所包含的消息内容有所不同,且不同组件的激活方式也不同。 Intent是一种运行时绑定机制,它能够在程序运行的过程中连接两个不同的组件。通过Intent,程序可以向 Android表达某种请求或者意愿,Android会根据意愿的内容选择适当的组件来处理请求。

2、简述Android平台用户界面搭建有哪几种方式,并对这几种方式进行比较。

  1. 使用XML布局文件控制UI界面:利用XML布局文件来控制用户界面是开发人员最常使用的方法。XML文件来描述用户界面,并将其保存在资源文件夹/res/layout下。这种方法极大地简化界面设计的过程,将界面视图从Java代码中分离出来,将用户界面中静态部分定义在XML中,代替了写代码,使得程序结构更加清晰、明了。
  2. 在Java代码中控制UI界面:Android允许开发者,完全在Java代码中控制UI界面。一方面不利于高层次解耦,另一方面界面中的控件需要通过new来创建,控件属性的设置还需要调用相应的方法,因此代码显得较为臃肿,对程序开发人员来讲不论设计还是维护都较为繁琐。
  3. 使用XML布局文件和代码混合控制UI界面:使用XML布局文件和Java代码混合控制UI界面,习惯上把变化小、行为比较固定的组件放在XML布局文件中,把变化较多、行为控制比较复杂的组件交给Java代码来管理。

3、Handler消息传递机制是怎样的?试举例说明。

  1. 目标线程调用Looper.prepare()创建Looper对象和消息队列.
  2. 目标线程通过new Handler()创建Handler对象,将Handler、Looper、消息队列三者关联起来。并覆盖其handleMessage函数。
  3. 目标线程调用Looper.loop() 监听消息队列。
  4. 消息源线程调用Handler.sendMessage发送消息。
  5. 消息源线程调用MessageQueue.enqueueMessage将待发消息插入消息队列。
  6. 目标线程的loop() 检测到消息队列有消息插入,将其取出。
  7. 目标线程将取出消息通过Handler.dispatchMessage派发给Handler.handleMessage进行消息处理。

例如:模拟手机客户端下载网络数据并在手机界面显示新数据。

业务逻辑:

  1. UI线程获得用户请求;
  2. 启动子线程完成网络数据下载(网络下载过程通过强制子线程休眠若干秒来模拟);
  3. 子线程将下载的数据返回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
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
public class MainActivity extends AppCompatActivity {
private Button mButton;
private TextView mTextView;
private Handler mHandler;
private Thread mNetAccessThread;
private ProgressDialog mProgressDialog;
private int mDownloadCount = 0;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mButton = (Button) findViewById(R.id.begin);
mTextView = (TextView) findViewById(R.id.dataText);
//设置按钮的点击事件监听器
mButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
showProgressDialog("", "正在下载...");
//启动子线程进行网络访问模拟
mNetAccessThread = new ChildTread();
mNetAccessThread.start();
}
});
//继承Handler类并覆盖其handleMessage方法
mHandler = new Handler() {
//覆盖Handler类的handleMessage方法
//接收子线程传递的数据并在UI显示
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case 1:
mTextView.setText((String) msg.obj);
mTextView.setTextColor(Color.RED);
dismissProgressDialog();
break;
default:
break;
}
}
};
}
class ChildTread extends Thread {
@Override
public void run() {
//休眠6秒,模拟网络访问延迟
try {
Thread.sleep(6000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//将结果通过消息返回主线程
Message msg = new Message();
msg.what = 1;
mDownloadCount++;
msg.obj = new String("第" + mDownloadCount + "次从网上下载的数据");
mHandler.sendMessage(msg);
}
}
/**
* 开启progressDialog.
* @param title 对话框标题.
* @param content 对话框正文.
*/
protected void showProgressDialog(String title, String content) {
mProgressDialog = new ProgressDialog(this);
if (title != null)
mProgressDialog.setTitle(title);
if (content != null)
mProgressDialog.setMessage(content);
mProgressDialog.show();
}
// 关闭progressDialog.
protected void dismissProgressDialog() {
if (mProgressDialog != null) {
mProgressDialog.dismiss();
}
}
}

4、如何显式启动一个Activity?如何隐式启动Activity?

显式启动

显式启动是指一个Activity通过类名明确指明要启动哪个Activity。

  1. 创建Intent对象,并初始化指明要启动的Activity。可以直接用:Intent intent = new Intent(ActivityA.this, ActivityB.class);
  2. 调用启动Activity的方法启动新的Activity,如startActivity(intent),完成新Activity的启动。

隐式启动

隐式启动表示不需指明要启动哪一个Activity,只需要声明一个行为,系统会根据Activity配置,启动能够匹配这个行为的Activity。

隐式启动Activity时,系统会自动启动与Intent上的动作(Action)、附加数据(Category)以及数据(Data)相匹配的Activity。 Action用于描述要完成的动作,Category为被执行动作Action增加的附加信息。通常Action与Category结合使用,Data则用于向Action提供操作的数据。

Intent隐式启动分三个步骤:

  1. 创建Intent对象:Intent intent=new Intent();
  2. 设置用于匹配的Intent属性,Intent属性有:动作属性、动作额外附加的类别属性以及数据属性等,分别需要调用不同的方法来设置:
1
2
3
4
intent.setAction(); //设置动作属性 
intent.addCategory); //设置类别属性
intent.setData(); //设置数据属性
intent.setType(); //设置数据属性
  1. 调用启动Activity方法启动新的Activity,如:startActivity(intent)

5、如何使用Bundle在Activity之间交换数据?试编程加以说明。

  1. Intent 提供了多个方法来设置欲传递的数据,例如:
  • public Intent putExtras (Bundle extras):向Intent中放入需要传递的Bundle数据extras。
  • public Intent putExtra (String name, Xxx value):向Intent中按key-value对的形式存入数据(Xxx指代各种数据类型的名称),key为name,value为val。
  1. Intent也提供了相应的取出传递过来的数据的方法,例如:
  • public Bundle getExtras ():取出Intent所“携带”的数据。
  1. Bundle类也提供了多个方法,来把数据封装到Bundle对象中,例如:
  • public void putSerializable (String key, Serializable value):向Bundle中放入一个可序列化的对象。
  • public void putInt (String key, int value):向Bundle中放入int类型的数据。
  1. Bundle类也提供了从Bundle对象中取出数据的方法:
  • public Serializable getSerializable (String key):从Bundle中取出一个可序列化的对象。
  • public int getInt (String key):从Bundle中取出Int类型的数据。

例如 ActivityA启动ActivityB,并携带key为“name”数据传递给ActivityB。

1
2
3
4
5
Intent intent = new Intent(ActivityA.this,ActivityB.class); 
Bundle bundle = new Bundle();
bundle.putString("name","Tom");
intent.putExtras(bundle);
startActivity(intent);

ActivityB获取传递过来的数据,相应代码如下:

1
2
3
Intent intent = getIntent(); 
Bundle bundle = intent.getExtras();
String name = bundle.getString("name");

6、编程实现进程内服务,并上机调试。

例如 编写一个音乐播放器。继承Service类实现自定义Service,提供在后台播放音乐、暂停音乐、停止音乐的方法。

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
public class MainActivity extends AppCompatActivity implements View.OnClickListener {
private Intent intent;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Button playBtn, stopBtn, pauseBtn, exitBtn;
playBtn = findViewById(R.id.play);
stopBtn = findViewById(R.id.stop);
pauseBtn = findViewById(R.id.pause);
exitBtn = findViewById(R.id.exit);

playBtn.setOnClickListener(this);
stopBtn.setOnClickListener(this);
pauseBtn.setOnClickListener(this);
exitBtn.setOnClickListener(this);
}

@Override
public void onClick(View v) {
int num = -1;
intent = new Intent("com.holger.androidmusic");
intent.setPackage(this.getPackageName());

switch (v.getId()) {
case R.id.play:
Toast.makeText(this, "播放音乐...", Toast.LENGTH_SHORT).show();
num = 1;
break;
case R.id.stop:
Toast.makeText(this, "停止音乐...", Toast.LENGTH_SHORT).show();
num = 2;
break;
case R.id.pause:
Toast.makeText(this, "暂停音乐...", Toast.LENGTH_SHORT).show();
num = 3;
break;
case R.id.exit:
Toast.makeText(this, "退出...", Toast.LENGTH_SHORT);
num = 4;
stopService(intent);
this.finish();
return;
}

Bundle bundle = new Bundle();
bundle.putInt("music", num);
intent.putExtras(bundle);

startService(intent);
}

@Override
public void onDestroy(){
super.onDestroy();
if(intent != null){
stopService(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
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
public class MyService extends Service {
private static final String TAG = "MusicService";
private MediaPlayer mediaPlayer;
private boolean reset = false;

@Override
public IBinder onBind(Intent intent) {
return null;
}

@Override
public void onCreate() {
Log.v(TAG, "onCreate");
if (mediaPlayer == null) {
mediaPlayer = MediaPlayer.create(this, R.raw.music);
mediaPlayer.setLooping(false);
}
}

@Override
public void onDestroy() {
Log.v(TAG, "onDestroy");
if (mediaPlayer != null) {
mediaPlayer.stop();
mediaPlayer.release();
}
}

@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.v(TAG, "onStart");
if (intent != null) {
Bundle bundle = intent.getExtras();
if (bundle != null) {
int num = bundle.getInt("music");
switch (num) {
case 1: play(); break;
case 2: stop(); break;
case 3: pause(); break;
}
}
}
return START_STICKY;
}

public void play() {
Log.v(TAG, "-----------" + reset + "----------");
if (mediaPlayer == null)
return;
if (reset)
mediaPlayer.seekTo(0);
if (!mediaPlayer.isPlaying()) {
mediaPlayer.start();
}
}

public void pause() {
reset = false;
if (mediaPlayer != null && mediaPlayer.isPlaying()) {
mediaPlayer.pause();
}
}

public void stop() {
reset = true;
if (mediaPlayer != null) {
mediaPlayer.stop();
try {
mediaPlayer.prepare();
} catch (IOException ex) {
ex.printStackTrace();
}
}
}
}

在清单文件中声明Service,为其添加label标签,便于在系统中识别Service

1
2
3
<service
android:name=".MyService"
android:label="@string/app_name" />

界面如下:

界面

点击“播放音乐”按钮后,在后台将会运行着名为“Android Music”的服务

“Android Music”的服务

7、编写一APP,实现SQLite数据库的增删改查。

设计一个课程管理APP,实现SQLite数据库的增删改查

在工程中创建MainActivity.java、InsertActivity.java、QueryActivity.java、UpdateActivity.java、DeleteActivity.java以及相应的布局文件activity_main.xml、activity_insert.xml、activity_query.xml、activity_update.xml、activity_delete.xml,以及数据库适配器DBAdapter.java、实体类Course.java。

数据库工具类DBAdapter.java,其中定义了内部类BookDBHelper,它是SQLiteOpenHelper的子类,用于建立、更新和打开数据库。主要代码:

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
public class DBAdapter {
public static final String ID = "_id";
public static final String COURSENAME = "courseName";
public static final String TEACHER = "teacher";
public static final String TIME = "time";
private static final String DB_NAME = "course.db";
private static final String DB_TABLE = "courseinfo";
private static final int DB_VERSION = 1;
private final Context context;
private SQLiteDatabase db;
private CourseDBHelper dbHelper;
public DBAdapter(Context context) {
this.context = context;
}
public void close() { // Close the database
if (db != null) {
db.close();
db = null;
}
}
public void open() throws SQLiteException { // Open the database
dbHelper = new CourseDBHelper(context, DB_NAME, null, DB_VERSION);
try {
db = dbHelper.getWritableDatabase();
} catch (SQLiteException ex) {
db = dbHelper.getReadableDatabase();
}
}
public long insert(Course course) {
ContentValues courseValues = new ContentValues();
courseValues.put(COURSENAME, course.getCourseName());
courseValues.put(TEACHER, course.getTeacher());
courseValues.put(TIME, course.getTime());
return db.insert(DB_TABLE, null, courseValues);
}
public Course[] queryAll() {
Cursor results = db.query(DB_TABLE,
new String[] { ID, COURSENAME, TEACHER, TIME},
null, null, null, null, null);
return ConvertToCourse(results);
}
public Course[] queryOne(long id) {
Cursor results = db.query(DB_TABLE, new String[] { ID, COURSENAME, TEACHER, TIME},
ID + "=" + id, null, null, null, null);
return ConvertToCourse(results);
}
private Course[] ConvertToCourse(Cursor cursor){
int resultCounts = cursor.getCount();
if (resultCounts == 0 || !cursor.moveToFirst()){
return null;
}
Course[] courseList = new Course[resultCounts];
for (int i = 0 ; i<resultCounts; i++){
courseList[i] = new Course();
int newId = cursor.getInt(0);
courseList[i].setID(newId);
String newCourseName = cursor.getString(cursor.getColumnIndex(COURSENAME));
courseList[i].setCourseName(newCourseName);
String newTeacher = cursor.getString(cursor.getColumnIndex(TEACHER));
courseList[i].setTeacher(newTeacher);
float newTime = cursor.getFloat(cursor.getColumnIndex(TIME));
courseList[i].setTime(newTime);
cursor.moveToNext();
}
return courseList;
}
public long deleteAll() {
return db.delete(DB_TABLE, null, null);
}
public long deleteOne(long id) {
return db.delete(DB_TABLE, ID + "=" + id, null);
}
public long updateOne(long id , Course course){
ContentValues courseValues = new ContentValues();
courseValues.put(COURSENAME, course.getCourseName());
courseValues.put(TEACHER, course.getTeacher());
courseValues.put(TIME, course.getTime());
return db.update(DB_TABLE, courseValues, ID + "=" + id, null);
}

// 静态Helper类,用于建立、更新和打开数据库
private static class CourseDBHelper extends SQLiteOpenHelper {
private static final String DB_CREATE = "create table " +
DB_TABLE + " (" + ID + " integer primary key autoincrement, " +
COURSENAME + " text not null, " + TEACHER + " text," + TIME + " float);";
public CourseDBHelper(Context context, String name, CursorFactory factory, int version) {
super(context, name, factory, version);
}
@Override
public void onCreate(SQLiteDatabase _db) {
_db.execSQL(DB_CREATE);
}
@Override
public void onUpgrade(SQLiteDatabase _db, int _oldVersion, int _newVersion) {
_db.execSQL("DROP TABLE IF EXISTS " + DB_TABLE);
onCreate(_db);
}
}
}

其余详细代码文件可详见附件SQLiteDemo工程文件夹。

运行结果如下:

运行结果

8、如何发送广播?如何接收广播?试编程举例说明。

发送广播

  1. 自定义BroadcastReceiver

自定义广播接收器需要继承基类BroadcastReceivre,并实现抽象方法onReceive(context, intent)方法。广播接收器接收到相应广播后,会自动回调onReceive()方法。onReceive()方法中不能执行太耗时的操作,否则将会造成ANR。 一般情况下,根据实际业务需求,onReceive()方法中都会涉及到与其他组件之间的交互,如发送Notification、启动Service等。 自定义如下:

1
2
3
4
5
6
7
8
9
public class MyBroadcastReceiver extends BroadcastReceiver { 
public static final String TAG = "MyBroadcastReceiver";
@Override
public void onReceive(Context context, Intent intent) {
Log.d(TAG, "intent: " + intent);
String name = intent.getStringExtra("name");
//…
}
}

一个BroadcastReceiver对象只有在被调用onReceive(Context, Intent)时才有效,当从该函数返回后,该对象就无效了,从而结束生命周期。

  1. BroadcastReceiver注册类型

BroadcastReceiver总体上可以分为两种注册类型:静态注册和动态注册。

  • 静态注册

直接在AndroidManifest.xml文件中进行注册。规则如下:

1
2
3
4
5
6
7
8
9
 <receiver android:enabled = ["true" | "false"] 
android:exported = ["true" | "false"]
android:icon = "drawable resource"
android:labe l= "string resource"
android:name = "string"
android:permission = "string"
android:process = "string" >
……
</receiver>

其中,需要注意的属性有以下:

  • android:exported 用于标识此BroadcastReceiver能否接收其他App发出的广播,其默认值是由Receiver中有无intent-filter决定的,如果有intent-filter,则默认值为true,否则为false
  • android:name BroadcastReceiver的类名。
  • android:permission 如果设置,具有相应权限的广播发送方发送的广播才能被此BroadcastReceiver所接收。
  • android:process BroadcastReceiver运行所处的进程。默认为App的进程,也可以指定独立的进程。

常见的注册形式如下:

1
2
3
4
5
6
7
8
<receiver android:name=".MyBroadcastReceiver" > 
<intent-filter>
<action android:name="android.net.conn.CONNECTIVITY_CHANGE" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" />
</intent-filter>
</receiver>

其中,intent-filter用于指定此广播接收器接收特定的广播类型。

  • 动态注册

动态注册时,无须在AndroidManifest中注册<receiver/>组件。直接在代码中通过调用Context的registerReceiver()函数,可以在程序中动态注册BroadcastReceiver。 典型的写法示例如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class MainActivity extends Activity { 
public static final String BROADCAST_ACTION = "com.example.corn";
private BroadcastReceiver mBroadcastReceiver;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mBroadcastReceiver = new MyBroadcastReceiver();
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction(BROADCAST_ACTION);
registerReceiver(mBroadcastReceiver, intentFilter);
}
@Override
protected void onDestroy() {
super.onDestroy();
unregisterReceiver(mBroadcastReceiver);
}
}

需要注意的是,Android中所有与观察者模式有关的设计中,一旦涉及到register,必定在相应的时机需要unregister

动态注册方式隐藏在代码中,比较难发现;需要特别注意的是,在退出程序前要记得调用Context.unregisterReceiver()方法。一般在Activity的onStart()方法中进行注册,在onStop()方法中进行注销。注意:如果在Activity.onResume()方法中注册了,就必须在Activity.onPause()方法中注销。