0. Intent

Intent的作用就是Activity之间信息的传播。

  1. explicit intent

    1
    2
    Intent intent = new Intent(this, Target.class);
    startActivity(intent)
  2. Implicit intent

    1
    2
    Intent intent = new Intent(action); //documentation有很多Activity action 可以查
    startActivity(intent)
  3. Pass text in explicit intent

    1
    2
    3
    4
    5
    6
    7
    intent.putExtra("message", value); //Put extra information in intent
    //----------------------------------------------------------------------
    Intent intent = getIntent();
    String string = intent.getStringExtra("message");
    // int intNum = intent.getIntExtra("name", default_value)
    // 如果没有这个name, 那么getIntExtra会返回default_value
    String getString = intent.getExtras.get("message");
  4. Pass text in Implicit intent

    1
    2
    intent.setType("text/plain"); //Type 和 Activity Action 安卓都会在AndroidManifest.xml自动查找匹配Activity用来给用户选择
    intent.putExtra(Intent.EXTRA_TEXT, "messageText");

    更多Activity Action可以在Intent(Android Documentation)查看。

    直接发送Implicit Intent时,如果安卓没有找到对应Activity,那么会报错。

    为了解决这种情况,我们可以Create Chooser

  5. Chooser

    1
    2
    Intent chosenIntent = Intent.createChooser(intent, "Dialog_Message");
    startActivity(choosenIntent);
  6. Implicit Intent和Chooser的区别

    • Implicit Intent
      • 在Activity filter中找到匹配Activity,并且用户选择后直接将Intent发给被选择的Activity。
      • 在Activity filter中没有找到匹配的Activity,报错。
    • Chooser
      • 在Activity filter中找到匹配Activity,并且用户选择后,安卓将chosen activity发给原来的Activity。原Activity再将chosen activity的intent给安卓处理。
      • 在Activity filter中没有找到匹配的Activity,Chooser提示没有匹配应用。

1. Activity Life Cycle

onCreate(): activity被创建后第一个被调用的函数。用于配置文件,环境等……

onStart(): activity被创建后第二个被调用的函数。可以将XML中的View组件都显示到手机上。

onResume(): 当前Activity获得了focus。

onPause(): 当前Activity失去了focus。

onStop(): 当前Activity停止了,(切换到了其他app或者切换到桌面,失去了支配权)

onDestory(): 当前Activity被摧毁。

onRestart(): onStop() —> onStart(),Activity又获得了支配权

HeadFirstAndroidP282

2. User Interface

GUI components和Layout(View Group)都属于View, 所以都拥有View的特点。

那么我们可以对View做哪些操作呢?

  1. 得到View的属性
  2. 大小和位置
  3. Focus handling
  4. Event handling和listener

UI的构成就是由XML结构转化成由ViewGroup为root构成的View的hierarchical tree

3. Adapter和Listener和Handler

Adapter: 相当于MVC中的C,作用为连接UI和数据的桥梁。在Android中,Adapter以一种RecycleView的方式将数据放到UI的组件当中。(这里Adapter相当于一个转接头。比如ArrayAdapter的作用就相当于将Array转接成放置于ListActivity中的TextView列表)

1
ArrayAdapter<String> arrayAdapter = new ArrayAdapter<String>(context, android.R.layout.simple_list_item_1, arrayList);

Listener:用于Listen组件(View),不同的Listener有不同的作用,比如onClickListener就是为了Listen某个组件被点击,又如onItemsSelectedListener就是为了Listen多个组件中哪一个组件被选中的作用。Listener也包括对象和主动者。一般组件就是对象,而实现Listener的一方为主动者。

Handler: 可以理解为用来与Main event thread沟通的独立空间。在main event thread中创建了handler之后,在handler里面的code可以时刻保持与Main event thread的交流。达到改变UI的目的。

4. Fragment

因为Fragment在早期Android不存在,所以为了兼容性,Android添加getSupportFragmentManager()来获取Fragment的方法。

为了让Fragement更加flexible, Fragment必须减少对别的Fragment之间的交流。当需要和外交交流时,最好将包含此Fragment的Activity作为Listener,来处理Fragment与Fragment之间的交流

Fragment通常是在FrameLayout之中进行switch的。

Fragment Transaction: getSupportFragmentManager().beginTranscation()获取。FragmentTransaction常用方法

1
2
3
4
5
6
7
FragmentTransation ft = getSupportFragmentManager().beginTransaction();
ft.replace(R.id.frag_container, fragment); //替换fragment
ft.add(R.id.frag_container, fragment); //添加fragment
ft.remove(fragment); //删除fragment
ft.setTransition(transition); //设置动画
ft.addToBackState(null) //添加transaction进入到back stack
ft.commit(); //apply the change

为Fragment添加Tag, 便于找到最近的一个存在于Transaction中的Fragment:

1
2
fragmentTransaction.replace(R.id.content, new_fragment, "tag");
Fragment latesetFragment = getFragmentManager().findFragmentByTag("tag");

FragmentTransaction也可以添加Listener,效果相当于监听返回键

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
getFragmentManager().addOnBackStackChangedListener(new FragmentManager.OnBackStackChangedListener() { //为Transaction添加Listener
@Override
public void onBackStackChanged() { //当BackStack里面的Transaction改变时
Fragment fragment = getFragmentManager().findFragmentByTag("visible_fragment");//找到BackStack里面最近(Latest)的Transaction(Fragment)
if (fragment instanceof TopFragment) {
currentPosition = 0;
}
if (fragment instanceof PizzaFragment) {
currentPosition = 1;
}
if (fragment instanceof PastaFragment) {
currentPosition = 2;
}
if (fragment instanceof StoresFragment) {
currentPosition = 3;
}
setActionBarTitle(currentPosition); //Update UI
drawerList.setItemChecked(currentPosition, true); //Update UI
}
});

Fragment Life Cycle:

HeadFirstAndroidP283

5. Nested Fragment

嵌套的Fragment需要用ChildFragmentTransaction形成嵌套的Transaction从而避免一次保存了多个Transaction导致程序错误。具体方法如下:

1
FragmentTransaction ft = getChildFragmentManager().beginTransaction();

如果用getFragmentManager,效果相当于没有嵌套Fragment,Fragment仍然隶属于Activity。

onClick 只对Activity生效。在Fragment中,需要自己创建Listener实现点击函数(View.OnClickListener)。

Fragment的onCreateView()步骤在Transaction回复以后。

6. Screen-specified Resource File

HeadFirstAndroidP309.png

7. Action Bar

Action Bar,顾名思义,就是存放Action的Bar。所以我们通常都是将一些Action(例如Edit, Search, Create…)放在Action Bar上面。放在Action Bar的好处就是可以在任何Activity中都可以直接操作这些Action而不需要返回Top Activity了。使得App更加方便,更加人性化。

不同版本的 Android API 对应了不同的Activity,如果要使用ActionBar就不能使用不存在ActionBar的Activity。所以需要extend支持ActionBar的Activity。而且不同的主题支持不同的Activity。这个时候我们就需要使用和Activity匹配的主题。

渲染(Inflate) Action Bar:

  1. 挑选支持Action Bar的Activity和Theme

  2. 创建menu文件夹,创建menu xml文件

  3. 在支持Action Bar的Activity里面Override onCreateOptionMenu(),在其中渲染menu

    1
    2
    3
    4
    5
    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
    getMenuInflater().inflate(R.menu.menu_main, menu);
    return super.onCreateOptionsMenu(menu);
    }
  4. 在支持Action Bar的Activity里面Override onOptionsItemSelected(), 在其中添加按钮事件

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
    switch(item.getItemId()) {
    case R.id.action_create_order:
    // Event when create order item is selected
    return true;
    case R.id.action_settings:
    // Event when settings item is selected
    return true;
    default:
    return super.onOptionsItemSelected(item);
    }
    }

Action Provider: 顾名思义, Provider就是提供者。它们自己就是主角,例如ShareProvider就是他自己来将Intent提供给别的Activity。MenuItem成为Provider有两种方法

  1. 在XML中定义Item为ShareActionProvider:

    1
    2
    3
    <item ...
    android:actionProviderClass="android.widget.ShareActionProvider"
    ... />
  2. 在Activity Code中实现

    1
    2
    3
    4
    ShareActionProvider shareActionProvider = new ShareActionProvider(this); //创建Provider
    shareActionProvider.setShareIntent(intent); //给ShareProvider绑定Intent
    MenuItem shareItem = menu.findItem(R.id.action_share);
    shareItem.setActionProvider(shareActionProvider); //为Share的MenuItem绑定shareActionProvider

Up Navigation: 返回上一个Hierarchy。与Back键不同的是Back键返回的是上一个Activity,而Up Navigation返回的是上一级Hierarchy(Hierarchy在activitymanifest.xml里面设置)。设置方法如下:

1
2
3
4
<activity ...
android:parentActivityName=".MainActivity"
...>
</activity>
1
2
ActionBar actionBar = getActionBar();
actionBar.setDisplayHomeAsUpEnabled(true);

在RunTime的时候更改ActionBar上的Title:

1
2
ActionBar actionBar = getActionBar();
actionBar.setTitle(title);

在RunTime的时候ReCreate MenuItem的Method:

1
invalidateOptionsMenu();

在ReCreate Menu Item的时候,onPrepareOptionsMenu()会被调用,这个时候我们可以Override它来改变Update(recreate) Menu Item之后的样子。

8. Drawer

Drawer的XML:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<androidx.drawerlayout.widget.DrawerLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/drawer_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
>

<FrameLayout
android:id="@+id/content_frame"
android:layout_width="match_parent"
android:layout_height="match_parent"/>

<ListView
android:id="@+id/drawer"
android:layout_width="240dp"
android:layout_height="match_parent"
android:layout_gravity="start"
android:choiceMode="singleChoice"
android:divider="@android:color/transparent"
android:dividerHeight="0dp"
android:background="#ffffff"/>

</androidx.drawerlayout.widget.DrawerLayout>

接下来操作便是在Activity Code里面给ListView绑定Adapter和Listener,从而实现ListView的文字和功能的呈现。

自动关闭DrawerLayout里面的Drawer代码如下:

1
2
DrawerLayout drawerLayout = (DrawerLayout)findViewById(R.id.drawer_layout);
drawerLayout.closeDrawer(R.id.drawer);

设置DrawerListener最好的方法就是使用ActionBarDrawerToggle:

1
2
3
4
5
6
7
8
9
10
11
12
13
ActionBarDrawerToggle drawerToggle = new ActionBarDrawerToggle(this, drawerLayout, R.string.open_drawer, R.string.close_drawer) {
@Override
public void onDrawerClosed(View drawerView) {
super.onDrawerClosed(drawerView);
invalidateOptionsMenu(); //Recreate the menu items
}

@Override
public void onDrawerOpened(View drawerView) {
super.onDrawerOpened(drawerView);
invalidateOptionsMenu(); //Recreate the menu items
}
};

将ActionBarDrawerToggle绑定到drawerLayout上:

1
2
DrawerLayout drawerLayout = (DrawerLayout)findViewById(R.id.drawer_layout);
drawerLayout.setDrawerListener(drawerListener);

在左上角显示Drawer的Toggle:

  1. 首先在onCreate的时候设置显示Toggle。因为我们的DrawerLayout用了ActionBarDrawerToggle当作Listener,所以左上角的Up键会失效而不是返回的按钮:

    1
    2
    3
    4
    5
    protected void onCreate(Bundle savedInstanceState) {
    ...
    getActionBar().setDisplayHomeAsUpEnabled(true);
    getActionBar().setHomeButtonEnabled(true);
    }
  2. 接下来要使用ActionBarDrawerToggle来处理Up Button被点击的事件:

    1
    2
    3
    4
    5
    6
    7
    public boolean onOptionsItemSelected(MenuItem item) {
    if (drawerToggle.onOptionsItemSelected(item)) { //Code to handle up button being clicked
    return true;
    };
    // Code to handle the rest of the action
    ...
    }
  1. 因为安卓自身的问题,我们需要在onCreate完成(即onPostCreate)时同步drawerToggle

    1
    2
    3
    4
    protected void onPostCreate(Bundle savedInstanceState) {
    super.onPostCreate(savedInstanceState);
    drawerToggle.syncState(); //同步drawerToggle按钮
    }
  2. 当Configuration改变时候,同时我们也需要改变drawerToogle的Condiguration

    1
    2
    3
    4
    5
    @Override
    public void onConfigurationChanged(Configuration newConfig) {
    super.onConfigurationChanged(newConfig);
    drawerToggle.onConfigurationChanged(newConfig);
    }

9. SQLite

组成: The SQLite Helper, The SQLite Database, Cursors

SQLite Helper:

SQLiteHelperClass

构造自己的SQLite Class

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class StartBuzzDatabaseHelper extends SQLiteOpenHelper { //我们自己的DatabaseHelper叫做StarBuzzDatabaseHelper

private static final String DB_NAME = "starbuzz";
private static final int DB_VERSON = 1;

StartBuzzDatabaseHelper(Context context) {
super(context,DB_NAME, null, DB_VERSON);
}

@Override
public void onCreate(SQLiteDatabase db) { //在Create Database的时候执行的代码

}

@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { // 在Upgrade Database 的时候执行的代码

}
}

在SQLite里面创建Database, 用SQL Language:

1
2
3
4
5
6
7
8
@Override
public void onCreate(SQLiteDatabase db) { //在Create Database的时候执行的代码
db.execSQL("CREATE TABLE DRINK (" + //创建一个叫DRINK的Database
"_id INTEGER PRIMARY KEY AUTOINCREMENT," + //第一列_id
"NAME TEXT," + //第二列NAME
"DESCRIPTION TEXT," + //第三列 DRSCRIPTION
"IMAGE_RESOURCE_ID INTEGER);"); //第四列IMAGE_RESOURCE_ID
}

在SQLite插入数据,需要使用ContentValues:

1
2
3
4
5
6
7
ContentValues contentValues = new ContentValues();
contentValues.put("NAME", "Latte");
contentValues.put("DESCRIPTION", "Espresso and steamed milk");
contentValues.put("IMAGE_RESOURCE_ID", R.drawable.latte);
db.insert("DRINK", null, contentValues);
//db.insert(String table_name, String nullColumnHack, ContentValues values);
//nullColumnHack通常是null,其作用是当有空行时,会主动在指定的列填充数据达到插入空行的效果

往SQLite更新数据,使用 SQLiteDatabase update()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//单个条件
ContentValues drinkValue = new ContentValues();
drinkValue.put("DESCRIPTION", "Tasty");
db.update("DRINK",
drinkValue,
"NAME = ?",
new String[] ("Latte"));
//多个条件
db.update("DRINK",
drinkValue,
"NAME = ? OR DESCRIPTION = ?",
new String[] {"Latte", "Our best drip coffee"});
//当条件不是String时,我们要转化为String
db.update("DRINK",
drinkValue,
"_id = ?",
new String[] {Integer.toString(1)});

往SQLight删除数据时,使用 SQLiteDatabase delete() method:

1
2
3
db.delete("DRINK",
"NAME = ?",
new String[] {"Latte"});

当我们更新软件版本时需要更新数据库。或者新版本软件有BUG,我们需要将Database回退,我们应该怎么做呢?

这个时候,我们只需要改变SQLiteOpenHelper的Constructor里面的DATABASE_VERSION即可。DATABASE_VERSIO和SQLiteOpenHelper中的method调用关系如下:

SQLVersionRelation

onUpgrade 和 onDowngrade两个method中的oldVersion都是指现已有数据库中的Version号。

1
2
// oldVersion = 现有Database版本号
// newVersion = SQLiteOpenHelper中定义的版本号,即安装完后app中database的版本号

之前的insert, update 和 delete都是改变数据库中的Records。接下来就是如何改变Database的Structure了。

在改变Database的Structure时候,我们要使用SQL Language因为Android并没用帮我们实现这些方法。

1
2
3
db.execSQL("ALTER TABLE DRINK ADD COLUMN FAVORITE NUMBERIC"); //在DRINK表中添加FAVORITE列
db.execSQL("ALTER TABLE DRINK RENAME TO FOOD"); // 将Database名从DRINK改成FOOD
db.execSQL("DROP TABLE DRINK"); //丢弃掉DRINK这张表

在SQLite中,我们可以使用SQLiteDatabase query(…)来获得cursor:

HeadFirstAndroidP477

常见的查询语句:

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
//获取所有rows
Cursor cursor = db.query("DRINK",
new String[] {"NAME", "DESCRIPTION"},
null, null, null, null, null);
//单条件获取
Cursor cursor = db.query("DRINK",
new String[] {"NAME", "DESCRIPTION"},
"NAME = ?",
new String[] {"Latte"},
null, null, null);
//多条件获取
Cursor cursor = db.query("DRINK",
new String[] {"NAME", "DESCRIPTION"},
"NAME = ? OR DESCRIPTION = ?",
new String[] {"Latte", "Our best drip coffee"},
null, null, null);
//如果条件列不是String,必须转化为String
Cursor cursor = db.query("DRINK",
new String[] {"NAME", "DESCRIPTION"},
"_id = ?",
new String[] {Integer.toString(1)},
null, null, null);
//模拟SELECT中ORDER BY的条件
Cursor cursor = db,query("DRINK",
new String[] {"_id", "NAME", "DESCRIPTION"},
null, null, null, null,
"FAVORITE DESC, NAME"); //FAVORITE 列降序排列后NAME升序排列

同时query(…) method支持SQL functions:

HeadFirstAndroidP481

使用方法类似SQL Select语法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//统计行数
Cursor cursor = db.query("DRINK",
new String[] {"COUNT(_id) AS count"},
null, null, null, null, null);
//取平均值
Cursor cursor = db.query("DRINK",
new String[] {"AVG(PRICE) AS price"},
null, null, null, null, null);
//统计FAVORITE和不FAVORITE的数量
Cursor cursor = db.query("DRINK",
"FAVORITE, COUNT(_id) as count",
null, null,
"FAVORITE", //GROUP BY FAVORITE
null, null);

有两种从SQLiteHelper中获得SQLiteDatabase的方法: getReadableDatabase() and getWritableDatabase()。区别如下:

HeadFirstAndroidP485

所以我们最好是只需要Read的时候获取ReadableDatabase,只需要Write的时候用WritableDatabase

在得到Cursors之后,我们需要Navigate to指定的tuple。有四个method可以使用:

1
2
3
4
5
6
7
8
9
10
11
12
if (cursor.moveToFirst()) {
//do something
}
if (cursor.moveToLast()) {
//do something
}
if (cursor.moveToNext()) {
//do something
}
if (cursor.moveToPrevious()) {
//do something
}

最后,我们需要获得cursor里面的value,使用get**(int column index)方法:

1
2
String name = cursor.getString(0); //假设第一列是NAME STRING
int image_id = cursor.getInt(2); //假设第三列是IMAGE_RESOURCE_ID NUMERIC

使用完之后记得close cursor和database

1
2
cursor.close();
db.close();

在使用CursorAdapter时,其绑定的cursor必须要有一列Primary Key来IDentify每一行, 因为这个primary key会用作List的id

创建CursorAdapter我们经常用SimpleCursorAdapter()。构造函数如下:

1
2
3
4
5
6
SimpleCursorAdapter adapter = new SimpleCursorAdapter(Contexe context,
int layout, //希望View应用的Layout
Cursor cursor, //获取数据的cursor
String[] fromColumn, //获取哪集列的数据
String[] toViews, //获取的数据以什么样的View的形式表达
int flag); // generally set to 0

获得cursor后,Database的改动并不会影响到cursor

CursorAdapter changeCursor(newCursor): 使用此方法可以更新View绑定的Adapter

1
2
CursorAdapter listAdapter = ((ListView)findViewById(R.id.list1)).getAdapter();
listAdapter.changeAdapter(newCursor);

在Android Liplop之后,我们需要考虑3种Thread

HeadFirstAndroidP526

我们如果把加载Database放在OnCreate里面,则相当于在Main event thread中加载数据库。可是main thread还要处理屏幕点击。数据库过大会导致app点击卡顿。

所以这个时候我们需要把数据库加载放在自建的Thread中。AsyncTask可以解决这个问题。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
private class MyAsyncTask extends AsyncTask<Params, Progress, Result>
protected void onPreExecute() {
//Code to run before executing the task
//被Main event thread调用,所以可以用来在doInBackground前获取UI信息
}
protected Result doInBackground(Params... params) {
//Code that you want to run in a background thread
//可以在这里call publishProgress(Progress...),从而调用onProgressUpdate(Progress...)
}
protected void onProgressUpdate(Progress... values) {
//Code that you want to run to publish the progress of your task
//当doInBackground call progressUpdate(Progress...) 的时候会被调用
//被Main Event Thread 调用,所以通常用来在UI上显示background task的进度
}
protected void onPostExecute(Result result) {
//Code that you want to run when the task is complete
//在Background Task结束后被调用,被Main Event Thread调用,所以可以在Background Task结束时在UI上显示结果。
}
}

10. Service

有时候,我们想在Bakcground运行一些代码。我们可以自己创建一个线程在Background运行。但是这样做会使得Avtivity里面的code很难阅读,容易出错。所以Service的发明很好的解决了这个问题。

两种Service Type:

  1. Start services

    可以在background一直run,直到task结束。不被activity限制。即使activity onDestory()了,依旧可以在Background运行。

  2. Bound services

    必须与Activity中的component绑定在一起,与component共生共灭。

在Android里面,有两种class对应这两种类型的Service:

  1. IntentService: 对应start srvices
  2. Service: 对应bound services

首先介绍IntentService:

IntentService中的 onHandleIntent(Intent intent) 就是在Background run的code。

如果Service需要与Main event thread进行交流,那么Service中的onStartCommand()在每次启动服务时都会被Main Event Thread 调用。

所以IntentService的具体用法如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class MyService extends IntentService {
public static final String EXTRA_MESSAGE = "message";
private Handler handler;

public MyService() {
super("MyService");
}

@Override
public int onStartCommand(@Nullable Intent intent, int flags, int startId) {
handler = new Handler(); //handler在Main event thread中创建,此handler可以用来与main event thread交流,改变UI
return super.onStartCommand(intent, flags, startId);
}

@Override
protected void onHandleIntent(Intent intent) {
//Code to run in the service
}

}

getApplicationContext()获取的是当前屏幕上的Context。如果想在任意当前屏幕显示Toast,可以使用它:

1
Toast.makeText(getApplicationContext(), "Show text", Toast.LENGTH_SHORT).show();

接下来就是介绍如何使用系统的Notification Service了。

首先,我们需要自己创建一个notification,需要指定Channel ID:

1
2
3
4
5
6
7
8
9
10
public static final String NOTIFICATION_CHANNEL_ID = "channel id"; //指定Channel ID
public static final String NOTIFICATION_CHANNEL_NAME = "channel name"; //指定Channel name
public int importance = NotificationManager.IMPORTANCE_DEFAULT; //指定importance

Notification notification = new NotificationCompat.Builder(this, NOTIFICATION_CHANNEL_ID) //设置Channel ID
.setSmallIcon(R.mipmap.ic_launcher)
.setContentTitle(getString(R.string.app_name))
.setContentIntent(pendingIntent)
.setContentText(text)
.build();

其中我们可以看到notification中的contentIntent设置的是Pending Intent。因为Pending Intent是用处是触发某件事情才会被start。这里的notification也是需要点击才start intent,而不是notification跳出来直接就start intent了。所以我们这里需要用pending intent。

那么如何获取Pending Intent呢?一下是一种方法:

1
2
3
4
5
6
Intent intent = new Intent(this, MainActivity.class); //创建一个Intent
TaskStackBuilder stackBuilder = TaskStackBuilder.build(this); //build一个TaskStackBuilder
stackBuilder.addParentStack(MainActivity.class); //加入MainActivity的回退键历史
stackBuilder.addNextIntent(intent); //添加下一个intent
PendingIntent pendingIntent = stackBuilder.getPendingIntent(0, PendingIntent.FLAG_UPDATE_CURRENT); //从stackBuilder中获取Pending Intent
// 可能TaskStackBuilder里面本身就自带一个pendingIntent,使用FLAG_UPDATE_CURRENT就是用新的intent中的内容替换掉了原来自带的pendding Intent里面的内容,并且stackBuilder返回的pending intent是封装好Parent Stack的pending intent。

最后,我们只需要把notification传送给notification service。再Android O之后,我们需要设置notification Channel,notification才能再指定的Channel里显示:

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
public static final String NOTIFICATION_CHANNEL_ID = "channel id";
public static final String NOTIFICATION_CHANNEL_NAME = "channel name";
public int importance = NotificationManager.IMPORTANCE_DEFAULT;

public static final int NOTIFICATION_ID = 5555; //可以自己定义,给创建的notification设置一个id
...
NotificationManager notificationManager =
(NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE); //获取notification manager

NotificationChannel notificationChannel = new NotificationChannel(NOTIFICATION_CHANNEL_ID, NOTIFICATION_CHANNEL_NAME, importance); //创建notificationChannel

//设置notificationChannel
notificationChannel.enableLights(true);
notificationChannel.enableVibration(true);
notificationChannel.setLightColor(Color.GREEN);
notificationChannel.setVibrationPattern(new long[] {
500,
500,
500,
500,
500
});

notificationManager.createNotificationChannel(notificationChannel); // Register channel with system

notificationManager.notify(NOTIFICATION_ID, notifcation); //将notification传说给notification manager

接下来介绍Service:

Bound Service 流程如下:

HeadFirstAndroidP608

首先我们先创建我们的MyService并继承Service:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class MyService extends Service {

public MyService() {
}

@Override
public IBinder onBind(Intent intent) {
...
}

//onCreate,当此Service被创建的时候调用,这个阶段往往初始化或实例化
public void onCreate() {
...
}
}

当ServiceConnection连接上时,调用onBind返回Binder对象,为了Activity可以拿到MyService,我们需要将MyService的Reference放到Bind对象中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class MyService extends Service {

private fianl IBinder myBinder = new MyBinder(); //创建实例

public class MyBinder extends Binder { //创建一个自己的MyBinder继承Binder
public MyService getMyService() { //存放MyService的Reference
return MyService.this;
}
}

public MyService() {
}

@Override
public IBinder onBind(Intent intent) {
return myBinder; //返回包含MyService的myBinder
}

public void onCreate() {
super.onCreate();
}

}

如何获取系统自带Location Service中的位置信息,并创建Listener取Listen Location 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
private Location lsatLocation = null;
private double distanceMeters = 0;
...
LocationListener listener = new LocationListener() {
@Override
public void onLocationChanged(Location location) {
if (lastLocation == null) {
lastLocation = location;
}
distanceMeters += lastLocation.distanceTo(location); //distanceTo,用来获取两个Location之间的距离
lastLocation = location;
}

@Override
public void onStatusChanged(String provider, int status, Bundle extras) {

}

@Override
public void onProviderEnabled(String provider) {

}

@Override
public void onProviderDisabled(String provider) {

}
};

LocationManager locationManager = (LocationManager)getSystemService(LOCATION_SERVICE);
locationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 1000, 1, listener);//将Listener与Location Service绑定

在Activity中要如何与Service进行绑定呢?

首先,在Activity中创建ServiceConnection对象:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
private boolean bound = false;
private MyService myService = null;
private ServiceConnection serviceConnection = new ServiceConnection() {

@Override
public void onServiceConnected(ComponentName name, IBinder service) {// service就是MyService中onBind返回的myBinder。onServiceConnected在connection连接时调用。

MyService.MyBinder myBinder = (MyService.MyBinder)service; //获取myBinder
myService = myBinder.getMyService(); //从myBinder中获取MyService的reference
bound = true;
}

@Override
public void onServiceDisconnected(ComponentName name) { //onServiceDisconnected在connection关闭的时候调用
bound = false;
}
};

创建完成后,我们需要连接connection,以及使用完毕后关闭connection

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Override
protected void onStart() {
super.onStart();
Intent intent = new Intent(this, OdometerService.class); //构建传送给MyService的Intent
bindService(intent, serviceConnection, BIND_AUTO_CREATE); //连接connection
}

@Override
protected void onStop() {
super.onStop();
if (bound) {
unbindService(serviceConnection);
bound = false;
}
}

如何获取权限呢?

1
2
3
ActivityCompat.requestPermissions(MainActivity.this,
new String[]{Manifest.permission.READ_EXTERNAL_STORAGE},
1);
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
@Override
public void onRequestPermissionsResult(int requestCode,
String permissions[], int[] grantResults) {
switch (requestCode) {
case 1: {

// If request is cancelled, the result arrays are empty.
if (grantResults.length > 0
&& grantResults[0] == PackageManager.PERMISSION_GRANTED) {

// permission was granted, yay! Do the
// contacts-related task you need to do.
} else {

// permission denied, boo! Disable the
// functionality that depends on this permission.
Toast.makeText(MainActivity.this, "Permission denied to read your External storage", Toast.LENGTH_SHORT).show();
}
return;
}

// other 'case' lines to check for other
// permissions this app might request
}
}

11. RecyclerView

RecyclerView的特点:Scrollable。 RecyclerView中看不见的view可以重复使用,而不是像ListView那样一直创建新的view。所以RecyclerView更加节约内存。

RecyclerView的缺点就是自己并没有已经实现的Adapter,我们需要自己创建Apdater继承RecyclerView.Adapter。这也同样是优点,这样我们就可以自定义Adpater中的View了

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
public class CaptionedImageAdapter extends RecyclerView.Adapter {

private String[] captions;
private int[] imageIds;

//在OnBindViewHolder时需要绑定数据,所以在构造Adapter的时候就需要传给Adapter需要的数据
public CaptionedImageAdapter(String[] captions, int[] imageIds) {
this.captions = captions;
this.imageIds = imageIds;
}



@NonNull
@Override
//静态内部类,直接在外部类调用所有这个内部类的属性和方法
//因为onCreateViewHolder需要创建ViewHolder,所以我们需要自己创建ViewHolder
//ViewHolder的作用就是View的Holder,绑定了View信息的Holder
//在ViewHolder中,我们需要自定义ViewHolder绑定的View的类型
//这里我们绑定了CardView
public static ViewHolder extends RecyclerView.ViewHolder {

private CardView cardView;

public ViewHolder(CardView v) {
super(v);
this.cardView = v;
}
}

//创建View
//当RecyclerView构造时,会重复调用onCreateViewHolder,直到所有ViewHolder创建完毕
//onCreateViewHolder第一个参数parent就是RecyclerView本身
public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
CardView cv = (CardView)LayoutInflator.from(parent.getContext()).inflate(R.layout.card_captioned_layout, parent, false);
return new ViewHolder(cv);
}

//将View与数据进行绑定(设置View中的数据)
//当新的item出现在RecyclerView中,就会调用onBindViewHolder用来给View设置信息
//第一个参数改成自己实现的ViewHolder(上面的静态内部类),overload这个方法
public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
CardView cv = holder.cardView; //静态内部类
ImageView imageView = (ImageView)cv.findViewById(R.id.info_image);
Drawable drawable = cv.getResource().getDrawable(imageIds[position]);
imageView.setImageDrawable(drawable);
imageView.setContentDescription(captions[postion]);
TextView textView = cv.findViewById(R.id.info_text);
textView.setText(captions[position]);
}

//返回Dataset中item的数量
@Override
public int getItemCount() {
return captions.length;
}
}

接下来就是如何在recyclerView中绑定Adapter了:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment

RecyclerView recyclerView = (RecyclerView)inflater.inflate(R.layout.fragment_pizza_material, container, false);
String[] pizzaNames = new String[Pizza.pizzas.length];
for (int i = 0; i < Pizza.pizzas.length; i++) {
pizzaNames[i] = Pizza.pizzas[i].getName();
}

int[] pizzaImages = new int[Pizza.pizzas.length];
for (int i = 0; i < Pizza.pizzas.length; i++) {
pizzaImages[i] = Pizza.pizzas[i].getImageResourceId();
}

recyclerView.setAdapter(new CaptionedImageAdapter(pizzaNames, pizzaImages));
//选择recyclerView的样式
LinearLayoutManager layoutManager = new LinearLayoutManager(getActivity());
recyclerView.setLayoutManager(layoutManager);

return recyclerView;
}

在创建好RecyclerView之后,那Recycler View是怎么处理点击事件呢?

因为Recycler View只是一个容器,所以我们如果要处理点击事件,我们只要在出现View的时候给View绑定上Listener就好了。

那么我们就需要在Apdater中的onBindViewHolder给View绑定上Listener就ok了

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
public class CaptionedImageAdapter extends RecyclerView.Adapter {

private Listener listener;

...

//在创建Adapter的时候需要实现的Listener
//增加Adpater的移植性
public static interface Listener {
public void onCLick(int position);
}

//注册Listener
public public void setListener(Listener listener) {
this.listener = listener;
}

...

@Override
public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, final int position) {
...
cardView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if(listener != null) {
interface.onClick();
}
}
});

}

...
}

12. ViewPager

ViewPager的Adapter:

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
public class TabsPagerAdapter extends FragmentPagerAdapter {

private String[] tabs = {"All", "Anal"};

public TabsPagerAdapter(@NonNull FragmentManager fm) {
super(fm);
}

//getItem返回每个Tab的Fragment,position是从左到右的index
@NonNull
@Override
public Fragment getItem(int position) {
switch (position) {
case 0:
return new AllVideosFragment();
case 1:
return new AnalVideosFragment();
default:
return null;
}
}

//返回tab的个数
@Override
public int getCount() {
return 2;
}

//为tabs添加title
@Nullable
@Override
public CharSequence getPageTitle(int position) {
return tabs[position];
}
}

在Activity中绑定FragmentPagerAdapter:

1
2
3
4
5
6
ViewPager viewPager = (ViewPager)findViewById(R.id.view_pager);
viewPager.setAdapter(new TabsPagerAdapter(getSupportFragmentManager()));

//找到tabLyout,然后绑定上viewPager
TabLayout tabLayout = (TabLayout) findViewById(R.id.tab_layout);
tabLayout.setupWithViewPager(viewPager);