文章目錄
-
- 序言
-
- 建立項目的小問題
- build.gradle
-
- 外層gradle
- app gradle
- 日志工具
- 活動
-
- 建立和使用
- Toast
- menu
- Activity跳轉
-
- 顯示Intent
- 隐式Intent
- 向下傳遞資料
- 傳回資料給上一級
- 活動生命周期
-
- 活動狀态
- 活動的生命期
- 活動啟動模式
- 活動的應用
-
- 找到界面對應的活動
- 快速直接退出程式
- 啟動程式的好技巧
- balabala
- OK,THANKS FOR READING.BYE BYE~
有些as的使用技巧、活動和部分控件的使用。
出自《第一行代碼(第二版)》,用于自己學(chao)習(shu)記錄
序言
建立項目的小問題
關于AS的一些小問題,如果如下報錯
Error:Execution failed for task ':app:preDebugAndroidTestBuild'.
> Conflict with dependency 'com.android.support:support-annotations' in project ':app'. Resolved versions for app (26.1.0) and test app (27.1.1) differ. See https://d.android.com/r/tools/test-apk-dependency-conflicts.html for details.
在
build.gradleModule:app
檔案中的
dependencies{…}
中添加
androidTestCompile('com.android.support:support-annotations:26.1.0') {
force = true
}
即可,如果版本是其他,按照版本修改相應數值即可。
build.gradle
AS中是用gradle建構項目的,gradle是一個先進的項目建構工具。使用基于Groovy的領域特定語言(DSL)聲明項目配置,摒棄了基于XML的繁瑣配置。
項目中有兩個build.gradle檔案,一個是外層的,一個是内層app目錄下的。
外層gradle
buildscript {
repositories {
google()
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:3.0.0'
}
}
allprojects {
repositories {
google()
jcenter()
}
}
task clean(type: Delete) {
delete rootProject.buildDir
}
repositories中聲明了google()和jcenter(),jcenter()是一個代碼托管倉庫,裡面有很多優秀的安卓開源項目,聲明後可以輕松引用倉庫中的開源項目。dependencies中聲明了一個gradle插件,其實gradle不是專門為Android項目開發的,Java和C++中也可以使用,是以需要使用專門的插件。
app gradle
apply plugin: 'com.android.application'
android {
compileSdkVersion 26
defaultConfig {
applicationId "com.example.k.androidpractie"
minSdkVersion 15
targetSdkVersion 26
versionCode 1
versionName "1.0"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
}
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation 'com.android.support:appcompat-v7:26.1.0'
implementation 'com.android.support.constraint:constraint-layout:1.1.3'
testImplementation 'junit:junit:4.12'
androidTestImplementation 'com.android.support.test:runner:1.0.2'
androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
androidTestCompile('com.android.support:support-annotations:26.1.0') {
force = true
}
}
versionCode表示項目版本号,versionName表示項目的版本名,在後面會說到。
Buildype包用于指定生成安裝檔案的相關配置,通常包括debug和release,debug是測試版安裝檔案,release則為正式版安裝檔案,debug可以忽略不寫的。
minifyEnabled指定是否混淆,proguardFiles則用于指定混淆規則,後面有兩個檔案,一個是SDK目錄下的,有所有項目的混淆規則,第二個檔案是目前項目根目錄下的,可以編寫目前項目特有的混淆規則。
dependencies可以指定目前項目所有的依賴關系。通常一個AS項目一共有3種依賴方式:本地依賴、庫依賴和遠端依賴。本地依賴是對本地Jar包或目錄添加依賴,庫依賴是對庫子產品添加依賴,遠端依賴則可以對jcenter等倉庫中的項目添加依賴。
日志工具
Android中的日志工具類是Log(android.util.Log),提供了五個相關方法:
- Log.v():列印最為繁瑣、意義最小的日志,對應級别是verbose,級别最低
- Log.d():列印一些調試資訊,有助于調試或者分析程式,對應級别是debug,比verbose高
- Log.i():列印一些比較重要的資料,對應級别是info,比debug高
- Log.w():列印一些警告資訊,提示程式這裡可能會有一些潛在風險,最好修複一下,對應級别是warn,比info進階
- Log.e(),列印錯誤資訊,比如程式進入到catch中了,級别是error
測試一下,在MainAvtivity.java中的onCreate()方法中添加一行
Log.d("MainActivity","this is a dubug message");
就可以輸入debug資訊了,如下圖所示,其中第一個參數是tag,一般寫目前類名,便于過濾,第二個參數就是需要輸出的資訊。

為了友善使用日志工具,可以在每個類開頭設定一個變量
private static final String TAG="...";
之後Log中的第一個參數直接TAG即可。
logcat可以使用過濾功能,AS預設提供了兩個過濾器,也可以自定義。點選
Edit Fliter Configuration
,寫好tag就行,點選OK,可以看到就隻顯示我們需要的log了。
等等,日志大概常用的就是這些,剩下的自己捉摸吧。
活動
建立和使用
在此手動建立一個活動。
建立一個項目,不要選擇
Empty Activity
,選擇
Add No Activity
。
進入後,右鍵
app-java-com.example.k.androidpractice_1 -> New -> Activity -> Empty Activity
,同時取消Generate Layout File(自動建立布局檔案)和Launcher Activity(設定為目前項目主活動)。
之後建立一個布局檔案
右鍵
app/res -> New -> Dictory
,建立一個名為layout的檔案夾,然後右鍵layout檔案夾,
New -> Layout resource file
,xml名為
first_layout
,預設為LinearLayout。
在xml中添加一個Button,修改代碼如下
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical" android:layout_width="match_parent"
android:layout_height="match_parent">
<Button
android:id="@+id/Button_1"
android:text="this is a button"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</LinearLayout>
切換到design
之後需要在Activity中加載布局,在FirstActivity中添加如下一行
setContentView(R.layout.first_layout);
設定布局,傳入布局id即可。
在AndroidManifest.xml中注冊Activity,其實AS已經幫我們注冊了FirstActivity這個活動,代碼如下
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.k.androidpractice_1">
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity android:name=".FirstActivity"></activity>
</application>
</manifest>
其中android:name中應為
com.example.k.androidpractice_1.FirstActivity
,因為外部有package,這裡就簡寫了。
但是還是需要設定主活動,否則程式無法知道首先啟動哪個Activity。
将Activity标簽修改如下
<activity android:name=".FirstActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
注意:如果沒有指定一個Activity為主活動,程式還是可以運作的,隻不過無法看到圖示,一般是作為第三方服務提供的,比如支付寶的支付服務。
之後運作可以看到程式正常運作了。
Toast
Toast是一種很友好的提示方式,可以以短小的資訊提示使用者某些資訊,在此對按鈕進行監聽以顯示Toast。
修改MainActivity代碼如下
Button Button=findViewById(R.id.Button_1);
Button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Toast.makeText(FirstActivity.this,"this is a message",Toast.LENGTH_SHORT).show();
}
});
menu
設定菜單大概需要三步:
- 建立menu檔案夾和編寫main.xml中的item
- 重寫onCreateOptionsMenu(),設定我們寫好的main.xml
- 重寫onOptionsItemSelected(),對菜單選項進行監聽響應
在res檔案夾下建立檔案夾menu,右鍵menu檔案夾,New -> Menuresourcefile,輸入main點ok。在main.xml中添加如下代碼
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item android:id="@+id/Add_Item"
android:title="Add"/>
<item android:id="@+id/Remove_Item"
android:title="Remove"/>
</menu>
這裡建立了兩個菜單選項Add和Remove指定其id和顯示内容。
需要重寫
onCreateOptionsMenu()
方法,按
ctrl+O
重寫方法,找到我們需要的,點選OK。
修改如下
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.main,menu);
return true;
}
其中getMenuInflater()方法獲得MenuInflater對象,調用inflater()方法建立菜單。
之後需要定義菜單的響應事件,即監聽菜單,需要重寫onOptionsItemSelected()方法
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()){
case R.id.Add_Item:
Toast.makeText(this,"這是Add",Toast.LENGTH_SHORT).show();
break;
case R.id.Remove_Item:
Toast.makeText(this,"這是Remove",Toast.LENGTH_SHORT).show();
break;
default:break;
}
return true;
}
如果想結束一個Activity,在代碼中可以直接通過finish()方法實作,即按傳回鍵的功能
Activity跳轉
在程式啟動之後指揮進入到主活動,如何跳轉到其他活動呢?需要使用intent。
顯示Intent
建立一個Activity,叫SecondActivity,勾選generate不選launcher。
在activity_second.xml中添加Button控件
<Button
android:id="@+id/Button2"
android:layout_height="wrap_content"
android:layout_width="match_parent"
android:text="這是second activity"
/>
修改FirstActivity中對Button的監聽,Intent一個重載是第一個參數為啟動活動的上下文,第二個參數為需要啟動的目标活動
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.first_layout);
Button Button=findViewById(R.id.Button_1);
Button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent=new Intent(FirstActivity.this,SecondActivity.class);
startActivity(intent);
}
});
}
隐式Intent
即為不明确啟動某個Activity,而是指定一系列action或者category等資訊,然後由系統找到合适的Activity啟動。
修改AndroidManifest.xml中的SecondActivity标簽
<activity android:name=".SecondActivity">
<intent-filter>
<action android:name="con.example.k.androidpractice_1.ACTION_START"/>
<category android:name="android.intent.category.DEFAULT"/>
</intent-filter>
</activity>
其中action指明了該活動可以響應的action,category具體包含了一些附加資訊。
修改FirstActivity中的按鈕監聽
Button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent=new Intent("con.example.k.androidpractice_1.ACTION_START");
startActivity(intent);
}
});
隻有當action和category同時比對的時候才能正确響應活動,由于此時的category是DEFAULT,是以不寫的話會預設為DEFAULT。
每個Intent隻可以指定一個action,但是可以指定多個category。
然後一個例子,啟動浏覽器,代碼如下
Button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent=new Intent(Intent.ACTION_VIEW);
intent.setData(Uri.parse("http://www.baidu.com"));
startActivity(intent);
}
});
如下所示,可以打開網頁了,要注意網址一定要正确,比如必須要加http://,否則報錯。其中INTENT.ACTION_VIEW是系統中的常量,值為android.intent.action.VIEW。
通過setData()傳入資料,标簽中可以添加标簽,更加精确配置響應什麼類型的資料,主要可以配置如下内容
- android:sheme 資料的協定部分,如http
- android:host 資料的主機部分,如www.baidu.com
- android:port 資料的端口部分,一般跟在主機名後
- android:path 用于指定主機名和端口之後的部分,如一段網址中跟在域名之後的内容
- android:mimeType 指定可處理資料的類型,允許使用通配符方式指定。
隻有标簽中指定的内容和Intent中攜帶的資料一緻的時候才能正确響應。
一個簡單示例,以響應http協定。建立Third_Activity,添加一個Button3,修改AndroidManifest.xml
<activity android:name=".ThirdActivity">
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<data android:scheme="http"/>
</intent-filter>
</activity>
之後啟動程式,點選按鈕,可以看到已經識别到我們自定義的Activity了。
向下傳遞資料
大緻思想就是先将資料存儲到Intent中,再目标活動中取出資料即可。主要用到了
putExtra()和getStringExtra()
方法。
filename:FirstActivity.java
Button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
String data="this is a data string";
Intent intent=new Intent(FirstActivity.this,SecondActivity.class);
intent.putExtra("extra_data",data);
startActivity(intent);
}
});
filename:SecondActivity.java
setContentView(R.layout.activity_second);
Intent intent=getIntent();
String data=intent.getStringExtra("extra_data");
Toast.makeText(SecondActivity.this,data,Toast.LENGTH_SHORT);
可以看到由Toast生成了,說明成功了。
傳回資料給上一級
大緻步驟為三步:
- 調用startActivityForResult()啟動Intent
- 在待銷毀活動中建立一個Intent,把資料放入其中,調用setResult()傳回結果
- 在上一級活動中重寫onActivityResult()方法,從Intent中擷取資料
需要使用到這麼一個方法,startActivityForResult(),這個方法在銷毀活動的時候會傳回一個結果給上一級,有兩個參數,第一個參數為Intent,第二個參數為一個請求碼,保證其唯一即可。
在待銷毀活動中建立一個Intent,不需要有參數,隻是用來傳遞資料,通過putExtra把資料傳入Intent中,調用setResult()傳回Intent,有倆各個參數,第一個一般為RESULT_OK或者RESULT_CANCELED,之後調用finish()銷毀。同時需要重寫上一級活動中的onActivityResult()方法以得到資料,因為活動銷毀後會調用上一級活動的這個方法。
修改代碼
filename:FirstActivity.java
...
Button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
String data="this is a data string";
Intent intent=new Intent(FirstActivity.this,SecondActivity.class);
startActivityForResult(intent,1);
}
});
...
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
switch (requestCode){
case 1:
if (resultCode==RESULT_OK){
String string=data.getStringExtra("data_return");
Toast.makeText(FirstActivity.this, string, Toast.LENGTH_SHORT).show();
}
break;
}
}
filename:SecondActivity.java
...
Button2.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent=new Intent();
intent.putExtra("data_return","hello,firstactivity");
setResult(RESULT_OK,intent);
finish();
}
});
可以看到成功擷取到了資料。
由于可能會有多個Activity傳回到同一個活動,是以需要在onActivityResult()中首先判斷requestCode的值确定其來自于哪個活動(即為最開始的請求碼),然後根據resultCode判斷是否成功,之後從data中擷取資料。
==============
如果是通過傳回鍵傳回的呢,重寫待銷毀活動的onBackPressed()方法即可。
活動生命周期
安卓中是通過任務來管理活動的,一個任務是一組存放在棧裡的活動的集合,即傳回棧。每當啟動一個新的活動的時候,活動入棧處于棧頂位置,按下傳回鍵或者調用finish()方法銷毀一個方法的時候,棧頂的活動會出棧。系統總是顯示棧頂的活動給使用者。
活動狀态
每個活動在生命周期最多有四個可能的活動狀态
- 運作狀态:處于傳回棧棧頂的活動處于運作狀态
- 暫停狀态:活動不處于棧頂但是仍然可見的時候,處于暫停狀态,不是所有活動都必須占滿全部螢幕,比如彈出的對話框就隻占據部分螢幕。
- 停止狀态:活動不處于棧頂且完全不可見的時候就處于停止狀态,系統會保留相應狀态和成員變量,但是不可靠,如果記憶體不夠用的時候,這部分将被回收。
- 銷毀狀态:當一個活動從棧頂移除後變成銷毀狀态,系統會優先回收這種狀态的活動。
活動的生命期
Activity類中定義了七個回調方法,覆寫了生命周期中的每一個環節
- onCreate():在活動第一次被建立的時候調用,實作布局的加載、事件綁定等
- onStart():在活動由不可見變為可見的時候調用
- onResume():在活動準備好和使用者進行互動的時候調用,此時活動一定處于棧頂,且處于運作狀态
- onPause():在系統準備啟動或恢複另一個活動時調用。一般在這裡需要釋放一些消耗cpu的資源,儲存一些關鍵資料,要快,否則可能會影響新活動
- onStop():在活動完全不可見的時候調用。若啟動新活動是類似對話框的活動,使用onPause(),onStop()不會執行
- onDestroy():在活動被銷毀之前調用,之後活動會變為銷毀狀态
- onRestart():活動由停止狀态變為運作狀态的時候調用該方法,即活動被重新啟動
還可以分為三種生存期
- 完整生存期:即onCreate()和onDestroy()之間所經曆的是完整生命期,在onCreate()中進行初始化操作,在onDestroy()中進行記憶體的釋放
- 可見生存期:在onStart()和onStop()之間所經曆的是可見生命期。在這個期間活動都是可見的,即便某些無法互動的時候也是可見的。應在onStart()中完成資源的加載,onStop()中進行資源的釋放
- 前台生存期:在onResume()和onPause()之間所經曆的是前台生存期。在這個期間活動總是處于運作狀态,可以和使用者進行互動
當一個活動進入停止狀态的時候,可能會被回收。如在活動A上啟動活動B,此時A進入停止狀态,但是由于記憶體不足,A被回收,從活動B傳回之後,仍會正常顯示活動A,隻不過不會調用onRestart()方法,而是會調用onCreate()方法。比如正在文本框中輸入了文字,然後啟動了另一個活動,傳回之後,打的字沒了,就是說這個活動被重新建立了。
Activity類中提供一個onSaveInstanceState()回調方法,可以保證在活動被回收之前一定會被調用
可以在此時臨時儲存資料避免資料消失的問題。重寫該方法,參數是一個Bundle,Bundle提供了一系列方法用于儲存資料,如putString(),putInt()等,這些方法都有兩個參數,第一個是鍵值,第二個是儲存的内容值。
protected void onSaveInstanceState(Bundle outState){
super.onSaveInstanceState(outState);
String tempData="this is s temp data";
outState.putString("data_key",tempData);
}
如何取出資料呢?onCreate()的參數也為Bundle,在該方法中從Bundle中取出資料
protected void onCreate(Bundle savedInstancedState){
super.onCreate(saveInstanceState);
Log.d(TAG,"onCreate);
if (savedInstancedState != null){
String tempData=savedInstancedState.getString("data_key");
Log.d(TAG,tempData);
}
}
Intent和Bundle有些類似,當然Intent可以和Bundle結合使用,把資料先儲存到Bundle中,在把Bundle傳入Intent中。
活動啟動模式
在實際項目中需要根據需求合理設定不同的啟動模式,一共有四種standard、singleTop、singleTask和singleInstance。
通過AndroidManifest.xml中中的android:launchMode屬性進行設定。
- standard:每當standard模式啟動的活動,不會在意是否已啟動,即不會在意是否處于傳回棧,每次啟動都會建立一個新的活動,而且打開幾個活動就需要按幾次傳回
- singleTop:在啟動的時候如果發現傳回棧棧頂就是該活動,則不會建立新的活動,不管啟動幾次活動都隻需要按一次傳回,因為棧頂始終是該活動。但是如果該活動不處于棧頂位置,再次啟動的話則仍會建立新的執行個體
- singleTask:使用singleTop可以解決重複建立棧頂活動的問題,但是如果該活動不處于棧頂,則會建立很多活動。而singleTask模式會在傳回棧中先檢查是否存在待啟動活動的執行個體,不存在的建立一個執行個體,如果存在則将其置為棧頂,并彈出所有該活動之上的活動
- singleInstance:啟動活動的時候會直接建立一個新的傳回棧。假設有一個活動是需要共享的,如果使用前三種啟動模式,均會建立新的執行個體,由于需要共享,是以不可能建立執行個體,是以使用單獨的傳回棧管理這個活動。(eg:設定second為singleInstance模式,在first啟動second,在second啟動third,則first和third處于同一個棧,second處于一個棧,此時棧頂活動為third,按傳回會回到first而不是second,因為first和third處于同一個棧,再次按傳回之後才會回到second,因為該棧已空,再次傳回後會退出
活動的應用
找到界面對應的活動
在實際中可能并不确定哪個界面對應的是哪個Activity。這時候需要進行一點修改就好了。
建立一個名為BaseActivity的普通java class,不需要注冊為Activity,編寫代碼如下
public class BaseActivity extends AppCompatActivity{
protected void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
Log.d("BaseActivity",getClass().getSimpleName());
}
}
之後将項目中Activity類中繼承的AppCompatActivity改為繼承BaseActivity,此時項目中的Activity類依然繼承AppCompatActivity,因為BaseActivity繼承于AppCompatActivity。
之後再運作程式,每當啟動一個活動日志就會列印出該活動對應的類名。
快速直接退出程式
當啟動多個程式的時候需要直接退出程式,直接按傳回鍵的話可能需要多次,要想一次退出的話可以使用一個集合作為Activity管理器來儲存目前所有活動進行處理。
public class ActivityCollector{
public static List<Activity> acticities=new ArrayList<>();
public static void addActivity(Activity activity){
acticities.add(activity);
}
public static void removeActivity(Activity activity){
activities.remove(activity);
}
public static void finishAll(){
for (Activity activity:activities){
if (!activity.isFinishing()){
activity.finish();
}
}
activities.clear();
}
}
之後需要修改一下上面的BaseActivity代碼
public class BaseActivity extends AppCompatActivity{
protected void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
Log.d("BaseActivity",getClass().getSimpleName());
ActivityCollector.addActivity(this);
}
protected void onDestroy(){
super.onDestroy();
ActivityCollector.removeActivity(this);
}
}
之後如果需要一次退出程式的話隻需要調用ActivityCollector.finishAll()方法即可。
啟動程式的好技巧
在啟動另一個活動的時候,需要傳遞參數的話,如果不是我們寫的活動,不太容易知道都需要什麼參數之類的,是以修改一下代碼,結構就會很清晰明了。
filename:SecondActivity.java
public class SecondActivity extends BaseAvtivity{
public static void actionStart(Context context,String Data1,String Data2){
Intent intent=new Intent(context,SecondActivity.class);
intent.putExtra("param1",Data1);
intent.putExtra("param2",Data2);
context.startActivity(intent);
}
...
}
然後
filename:FirstActivity.java
public void onClick(View v){
SecondActivity.actionActivity(FirstActivity.this,"123","456");
}
這樣就很清楚了。
balabala
安卓隻寫過幾個小程式,雖然現在隻把活動看完了,但是感覺真的認識了不少東西和方法,比如生命周期生存期,之前隻是知道現象但是具體不知道是什麼,現在有了一個系統的總結。幾句這樣,明天繼續。