文章原载于我的个人博客,欢迎来访。
整理了一些Android很经典的几个问题。
其实是选修课作业,顺道吹一吹这门课(附件:部分源代码
一个活动表示一个可视化的用户界面,同一应用中的每个Activity是相互独立的。每一个活动都是作为Activity基类的一个子类的实现。应用程序中的每个屏幕都是通过继承和扩展基类Activity来实现的。Activity窗口内的可见内容通过基类View提供。
一个没有用户界面的在后台运行执行耗时操作的应用组件。其他应用组件能够启动Service,并且当用户切换到另外的应用场景,Service将持续在后台运行。另外,一个组件能够绑定到一个Service与之交互,所有这些活动都是在后台进行。Service与Activity一样都存在于当前进程的主线程中,所以一些阻塞UI的操作不能放在Service里进行。
广播接收者仅是接受广播公告并作出相应的反应。许多广播源自于系统,应用程序也可以发起广播。一个应用程序可以有任意数量的广播接收者去反应任何它认为重要的公告。所有的接受者继承自BroadcastReceiver基类。BroadcastReceiver自身并不实现图形用户界面,但是当它收到某个通知后,可以启动 Activity作为响应,或者通过NotificationMananger提醒用户。
内容提供者使一个应用程序的指定数据集提供给其他应用程序,继承自ContentProvider 基类并实现了一个标准的方法集,使得其他应用程序可以检索和存储数据。应用程序使用一个ContentResolver对象并调用它的方法。ContentResolver能与任何内容提供者通信,它与提供者合作来管理参与进来的进程间的通信。
Intent在不同的组件之间传递消息,将一个组件的请求意图传给另一个组件。因此, Intent 是包含具体请求信息的对象。针对不同的组件,Intent所包含的消息内容有所不同,且不同组件的激活方式也不同。 Intent是一种运行时绑定机制,它能够在程序运行的过程中连接两个不同的组件。通过Intent,程序可以向 Android表达某种请求或者意愿,Android会根据意愿的内容选择适当的组件来处理请求。
Looper.prepare()
创建Looper对象和消息队列.new Handler()
创建Handler对象,将Handler、Looper、消息队列三者关联起来。并覆盖其handleMessage
函数。Looper.loop()
监听消息队列。Handler.sendMessage
发送消息。MessageQueue.enqueueMessage
将待发消息插入消息队列。loop()
检测到消息队列有消息插入,将其取出。Handler.dispatchMessage
派发给Handler.handleMessage
进行消息处理。例如:模拟手机客户端下载网络数据并在手机界面显示新数据。
业务逻辑:
代码如下
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();
}
}
}
显式启动是指一个Activity通过类名明确指明要启动哪个Activity。
Intent intent = new Intent(ActivityA.this, ActivityB.class);
startActivity(intent)
,完成新Activity的启动。隐式启动表示不需指明要启动哪一个Activity,只需要声明一个行为,系统会根据Activity配置,启动能够匹配这个行为的Activity。
隐式启动Activity时,系统会自动启动与Intent上的动作(Action)、附加数据(Category)以及数据(Data)相匹配的Activity。 Action用于描述要完成的动作,Category为被执行动作Action增加的附加信息。通常Action与Category结合使用,Data则用于向Action提供操作的数据。
Intent隐式启动分三个步骤:
Intent intent=new Intent();
intent.setAction(); //设置动作属性
intent.addCategory); //设置类别属性
intent.setData(); //设置数据属性
intent.setType(); //设置数据属性
public Intent putExtras (Bundle extras)
:向Intent中放入需要传递的Bundle数据extras。public Intent putExtra (String name, Xxx value)
:向Intent中按key-value对的形式存入数据(Xxx指代各种数据类型的名称),key为name,value为val。public Bundle getExtras ()
:取出Intent所“携带”的数据。public void putSerializable (String key, Serializable value)
:向Bundle中放入一个可序列化的对象。public void putInt (String key, int value)
:向Bundle中放入int类型的数据。public Serializable getSerializable (String key)
:从Bundle中取出一个可序列化的对象。public int getInt (String key)
:从Bundle中取出Int类型的数据。例如 ActivityA启动ActivityB,并携带key为“name”数据传递给ActivityB。
Intent intent = new Intent(ActivityA.this,ActivityB.class);
Bundle bundle = new Bundle();
bundle.putString("name","Tom");
intent.putExtras(bundle);
startActivity(intent);
ActivityB获取传递过来的数据,相应代码如下:
Intent intent = getIntent();
Bundle bundle = intent.getExtras();
String name = bundle.getString("name");
例如 编写一个音乐播放器。继承Service类实现自定义Service,提供在后台播放音乐、暂停音乐、停止音乐的方法。
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);
}
}
}
在布局中添加三个按钮,用于控制音乐播放、暂停与停止
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
<service
android:name=".MyService"
android:label="@string/app_name" />
界面如下:
点击“播放音乐”按钮后,在后台将会运行着名为“Android Music”的服务
例 设计一个课程管理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的子类,用于建立、更新和打开数据库。主要代码:
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工程文件夹。
运行结果如下:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3QizxtdX-1595863946493)(https://i.loli.net/2020/07/16/C5ghqGTxWRmrUH4.png)]
自定义广播接收器需要继承基类BroadcastReceivre,并实现抽象方法onReceive(context, intent)
方法。广播接收器接收到相应广播后,会自动回调onReceive()
方法。onReceive()
方法中不能执行太耗时的操作,否则将会造成ANR。 一般情况下,根据实际业务需求,onReceive()
方法中都会涉及到与其他组件之间的交互,如发送Notification、启动Service等。 自定义如下:
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)
时才有效,当从该函数返回后,该对象就无效了,从而结束生命周期。
BroadcastReceiver总体上可以分为两种注册类型:静态注册和动态注册。
直接在AndroidManifest.xml
文件中进行注册。规则如下:
……
receiver>
其中,需要注意的属性有以下:
android:exported
用于标识此BroadcastReceiver能否接收其他App发出的广播,其默认值是由Receiver中有无intent-filter
决定的,如果有intent-filter
,则默认值为true
,否则为false
。android:name
BroadcastReceiver的类名。android:permission
如果设置,具有相应权限的广播发送方发送的广播才能被此BroadcastReceiver所接收。android:process
BroadcastReceiver运行所处的进程。默认为App的进程,也可以指定独立的进程。常见的注册形式如下:
<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中注册
组件。直接在代码中通过调用Context的registerReceiver()
函数,可以在程序中动态注册BroadcastReceiver。 典型的写法示例如下:
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()
方法中注销。
原文链接 作者:Holger