此文假設所有人都會java的語法.
手把手創(chuàng)建項目
- 打開Android studio后, 選擇
file
-new project
-選擇Phone and Tablet
-選中Basic Activity
, 點擊Next
, 輸入名字和選擇api level之后, 點擊Finish
. - 此時android studio 自動創(chuàng)建了一個項目并進行sync 操作. 查看對應的
AndroidManifest.xml
文件, 可以看到已經自動創(chuàng)建了一個MainActivity項并配置完成 - 然后點擊android studio上的
Run
按鈕, 此時就會自動打包并安裝到手機上
初識Android文件結構
Android的文件結構, 主要包含幾個重要部分:
1.gradle文件
包括項目的build.gradle文件, 主要負責配置gradle倉庫和打包腳本, 無需過多關注, 以及組件的build.gradle
文件, 負責配置安卓版本以及依賴包, 這個文件需要注意, 在導入依賴包時必須使用到.
- plugin 一般有兩個值可選: 'com.android.application'表示這是一個應用程序模塊, 'com.android.library'表示這是一個庫模塊
apply plugin: 'com.android.application'
- android 閉包
android {
compileOptions{ // 這里表示使用java 1.8特性
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
compileSdkVersion 28 //這個表示默認打包為sdk 28版本
defaultConfig {
applicationId "com.zzy.xuexiqiangguo" // 包id
minSdkVersion 26 // 最低支持的系統(tǒng)版本
targetSdkVersion 28 // 默認的系統(tǒng)版本
versionCode 1 //包版本
versionName "1.0" //包版本
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" // 這里表示無界面測試依賴包
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
}
- dependencies
dependencies {
implementation fileTree(include: ['*.jar'], dir: 'libs') // 依賴libs/*.jar包
implementation 'com.android.support:appcompat-v7:28.0.0' // 依賴包
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'
implementation project(':rhinojs') // 表示依賴另一個項目
}
-
AndroidManifest.xml
文件, 該文件配置了app的名字, 活動, 權限等數據, 必須關注, 在啟動時會從該文件中查找對應的動作.
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.zzy.xuexiqiangguo">
<uses-permission android:name="android.permission.INTERNET"/> //這里表示請求網絡權限
<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">
<service // 這段表示綁定AccessibilityService
android:name=".RobService"
android:enabled="true"
android:exported="true"
android:label="學習強國"
android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE">
<intent-filter>
<action android:name="android.accessibilityservice.AccessibilityService" />
</intent-filter>
<meta-data
android:name="android.accessibilityservice"
android:resource="@xml/myaccessibility"/>
</service>
<activity android:name=".MainActivity"> // 這里表示下面的是啟動Activity
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
- main/java目錄, 該目錄下存放了android的代碼.
- main/res目錄, 該目錄下存放的是設計/圖片/媒體文件
Android的日志模塊Log
安卓的日志模塊Log, 在這個類中提供了5個不同級別的日志打印方法, 另外提供了logt
這個快捷輸入命令用于添加TAG
變量
- Log.v() 打印最瑣碎的日志信息, 快捷輸入logv
- Log.d() 打印調試信息, 快捷輸入logd
- Log.i() 打印一些比較重要的信息, 快捷輸入logi
- Log.w() 打印一些警告信息, 快捷輸入logw
- Log.e() 打印錯誤信息. 快捷輸入loge
添加了日志打印內容后, 就可以在logcat中看到對應的日志了.
從活動Activity
入手
活動在Android中的意思是指包含用戶界面的組件, 主要用于和用戶交互, 我們打開一個app時看到的每一個界面都是一個活動.
創(chuàng)建布局
- 在res目錄下新建layout目錄后, 右鍵點擊layout-
layout resource file
, 此時彈出新建面板, 輸入文件名后點擊ok - 在Design頁中拖動一個button后加入到layout中, 此時可以實時看到button的顯示. 在這里解釋一下各個屬性
<Button
android:id="@+id/button1" //@+id表示注冊一個新的id到R.id中, 在這里就是注冊R.id.button1
android:layout_width="match_parent" // 這里表示寬度與父節(jié)點一致
android:layout_height="wrap_content" // 這里表示高度適應大小
android:text="Button" /> // text表示button上顯示的文本
活動的基本用法
- 在onCreate() 中加載布局:
setContentView(R.layout.main_activity);
- 使用findViewById方法獲取到對應的View后, 添加事件監(jiān)聽
Button button = (Button) findViewById(R.id.button1);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Toast.makeText(MainActivity.this, "show toast", Toast.LENGTH_SHORT).show();
}
});
在AndroidManifest.xml中注冊
- 以下中intent-filter表示在啟動時打開MainActivity
<activity android:name=".MainActivity" android:label="demo activity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
完成了以上三個步驟之后, 就算是已經完成了一個最簡單的可用的app了.
使用Intent
切換Activity
切換界面, 是程序運行中一個再正常不過的需求
- 使用顯式Intent切換
Intent intent = new Intent(MainActivity.this, secondActivity.class);
startActivity(intent);
- 使用隱式Intent切換, 一般都是使用這個切換方式
Intent intent = new Intent("android.intent.action.MAIN");
intent.addCategory("android.intent.category.LAUNCHER");
startActivity(intent);
- 通過隱式Intent, 還可以用于打開網頁, 打開撥號盤等
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setData(Uri.parse("https://www.baidu.com"));
startActivity(intent);
Intent intent = new Intent(Intent.ACTION_DIAL);
intent.setData(Uri.parse("tel:10086"));
startActivity(intent);
####### 活動間數據傳遞
傳遞數據代碼
Intent intent = new Intent("android.intent.action.MAIN");
intent.addCategory("android.intent.category.LAUNCHER");
intent.putExtra("extra_data", "helloworld");
startActivity(intent);
獲取數據代碼
Intent intent = getIntent();
String data = intent.getExtra("extra_data");
活動的生命周期
返回棧
Android中的活動是可以層疊的. 每當啟動一個新的活動, 就會覆蓋在原活動之上, 然后按back鍵會銷毀最上面的活動, 下面的活動重新顯示出來.-
活動狀態(tài)
每個活動在其生命周期中最多可能會有4種狀態(tài)- 運行狀態(tài)
當一個活動位于棧頂時, 就處于運行狀態(tài). - 暫停狀態(tài)
當一個活動不再處于棧頂, 但活動依然可見時, 就處于暫停狀態(tài), 比如說點擊某個按鈕后彈出對話框, 此時對話框處于運行狀態(tài), 對話框下的活動處于暫停狀態(tài). 處于暫停狀態(tài)下的活動仍然是存活的, 系統(tǒng)也不愿意去回收這種活動. - 停止狀態(tài)
當一個活動不再處于棧頂, 并且不可見時, 就進入停止狀態(tài). 系統(tǒng)會為這種活動保存相應的狀態(tài)和成員變量, 但是當其他地方需要內存時, 停止狀態(tài)的活動可能會被回收. - 銷毀狀態(tài)
當一個活動從返回棧中移除后就變成銷毀狀態(tài).
- 運行狀態(tài)
-
活動的生存期
Activity類提供了7個回調方法, 覆蓋了生命周期中的每一個環(huán)節(jié), 依次為- onCreate() 創(chuàng)建時調用, 這個創(chuàng)建可能是第一次創(chuàng)建, 也有可能是被系統(tǒng)回收后, 從返回棧中取出狀態(tài)重新創(chuàng)建
- onStart() 活動由不可見狀態(tài)變?yōu)榭梢姞顟B(tài)時調用
- onResume() 活動準備好與用戶進行交互時調用, 此時活動一定位于棧頂(比如關閉對話框時調用)
- onPause() 系統(tǒng)準備去啟動或者恢復另一個活動的時候調用
- onStop() 活動由可見變?yōu)椴豢梢姇r調用, 這個跟onPause的區(qū)別在于如果啟動的是一個對話框, 則onPause會執(zhí)行, onStop不會執(zhí)行
- onDestroy() 活動被銷毀時調用
- onRestart() 活動由停止狀態(tài)變?yōu)檫\行狀態(tài)時調用
- 活動回收的處理
前面的生命周期可以看到, 如果活動進入了停止狀態(tài), 是有可能被系統(tǒng)回收的. 那么這時候如果用戶按back鍵返回一個已經被回收的活動, 這時候系統(tǒng)不會執(zhí)行onRestart()方法, 而是會執(zhí)行onCreate()方法重新創(chuàng)建一次.
但是活動是可能存在輸入狀態(tài)的, 比如文本框中的輸入文字, 在系統(tǒng)回收了該活動后, 重新創(chuàng)建出來的活動如果沒有了用戶輸入的內容, 是會嚴重影響用戶體驗的. 所以Android提供了一個onSaveInstanceState(Bundle outState)
方法, 用于在系統(tǒng)回收活動的時候調用該方法保存用戶輸入. 代碼如下
outState.putString("data_key", "helloworld");
現在我們再看onCreate方法, 注意到該方法中有一個參數onCreate(Bundle savedInstanceState)
, 此時我們只需要通過這個bundle恢復之前所保存的數據即可
if (savedInstanceState != null) {
String data = savedInstanceState.getString("data_key");
}
- 活動的啟動模式
活動由幾種啟動模式, 可以在AndroidManifest.xml中通過給<activity>標簽指定android:launchMode
屬性來選中啟動模式- standard
standard是活動默認的啟動模式, 在不進行指定的情況下, 所有活動都是使用這個模式. 在standard模式下, 每當啟動一個新的活動, 它就會在返回棧中入棧, 并處于棧頂的位置, 系統(tǒng)并不關注活動是否在返回棧中已存在. - singleTop
在singleTop模式下, 在啟動活動時系統(tǒng)會判斷返回棧的棧頂是否是該活動, 如果是則認為可以直接使用它. 不會再創(chuàng)建活動實例. - singleTask
在singleTask模式下, 在啟動活動時系統(tǒng)會首先在返回棧中檢查是否已經存在該活動的實例, 如果發(fā)現已存在則直接使用該實例, 并且把該活動之上的所有活動都出棧, 銷毀. 如果沒有則創(chuàng)建一個新的活動實例. - singleInstance
指定為singleInstance模式的活動會啟用一個新的返回棧來管理這個活動. 一般用于在與其他的程序共享一個活動的實例時.
- standard
UI
常用控件
因為篇幅有限, 加上控件的使用大同小異, 就不詳細寫控件的使用了, 只寫常用控件的特性和應用場景, 目的就是讓大家知道有這么個東西, 能解決什么樣的問題, 在關鍵時刻的時候尋找谷哥的幫助時知道用什么關鍵詞.
- TextView 顯示文本信息
- Button 按鈕
- EditText 輸入框
- ImageView 圖片控件
- ProgressBar 進度條
- AlertDialog 對話框
- ProgressDialog 進度條對話框
- RecyclerView 滾動控件
基本布局
UI 就是布局加控件的組合 -- 魯迅.
所以我們接下來還要了解一下基本布局
- 線性布局 LinearLayout, 如名所示, 這個布局會將它所包含的控件在線性上依次排列.
- 相對布局 RelativeLayout, 它可以通過相對定位的方式讓控件出現在布局的任何地方.
- 幀布局 FrameLayout, 這種布局沒有方便的定位方式, 所有的控件都會默認擺放在布局的左上角.
- 百分比布局 PercentFrameLayout 和 PercentRelativeLayout
廣播
-
廣播機制簡介
Android中的每個應用都可以對自己感興趣的廣播進行注冊, 這樣該程序就只會接收到自己所關心的廣播內容. 這些廣播可能是來自于系統(tǒng)的, 也可能是來自于其他應用程序的.- 標準廣播 是一種完全異步執(zhí)行的廣播, 在廣播發(fā)出之后, 所有的廣播接收器幾乎都會在同一時刻接收到這條廣播消息
- 有序廣播 則是一種同步執(zhí)行的廣播, 在廣播發(fā)出之后, 同一時刻只會有一個廣播接收器可以接收到這條消息. 當這條廣播接受器的邏輯處理完成后, 廣播才會繼續(xù)傳遞, 因此前面的廣播接收器可以截斷正在傳遞的廣播, 這樣后面的廣播接收器就無法收到廣播消息了.
-
接收系統(tǒng)廣播
- 動態(tài)注冊監(jiān)聽網絡變化, 先在AndroidManifest.xml中加入
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" ></uses-permission>
- 動態(tài)注冊監(jiān)聽網絡變化, 先在AndroidManifest.xml中加入
private IntentFilter intentFilter;
private NetworkChangeReceiver networkChangeReceiver;
@Override
public void onCreate(Bundle saveedInstanceState) {
super.onCreate(saveedInstanceState);
setContentView(R.layout.main_activity);
intentFilter = new IntentFilter();
intentFilter.addAction("android.net.conn.CONNECTIVITY_CHANGE");
networkChangeReceiver = new NetworkChangeReceiver();
registerReceiver(networkChangeReceiver, intentFilter);
}
@Override
protected void onDestroy() {
super.onDestroy();
unregisterReceiver(networkChangeReceiver);
}
class NetworkChangeReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
}
}
- 靜態(tài)注冊實現開機啟動
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" ></uses-permission>
<receiver
android:name=".BroadcastReceiver"
android:enabled="true"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" />
</intent-filter>
</receiver>
- 發(fā)送廣播
發(fā)廣播的方法跟啟動活動的方法很相似
Intent intent = new Intent("com.zzy.broadcast.mybroadcast");
sendBoardcast(intent);
發(fā)送有序廣播的方法跟發(fā)送標準廣播的方法只需要修改一點sendOrderedBoardcast(intent, null);
這里的第二個參數是一個與權限有關的字符串.
- 發(fā)送本地廣播
前面我們發(fā)送和接收的廣播全部屬于系統(tǒng)全局廣播, 這樣很容易引起安全性問題, 為了能夠解決安全性問題, android引入了一套本地廣播體系, 本地廣播只能夠在應用程序的內部進行傳遞, 并且廣播接收器也只能接收來自本應用程序發(fā)出的廣播. 本地廣播的操作和全局廣播基本是一樣的, 只是通過LocalBroadcastManager
進行一層管理.
LocalBroadcastManager localBroadcastManager = LocalBroadcastManager.getInstance(this);
localBroadcastManager.sendBroadcast(intent);
localBroadcastManager.registerReceiver(localReceiver, intentFilter);
數據持久化
- 文件存儲
多媒體
通知
NotificationManager manager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
//NotificationChannel channelbody = new NotificationChannel(channel,"消息推送",NotificationManager.IMPORTANCE_DEFAULT);
String channelid = "channelid";
String channelname = "channelname";
Notification notification;
Intent intent = new Intent(MainActivity.this, FruitRecycleView.class);
PendingIntent pendingIntent = PendingIntent.getActivity(MainActivity.this, 0, intent, 0); //設置點擊通知的響應
int importance = NotificationManager.IMPORTANCE_LOW;
NotificationChannel mChannel = manager.getNotificationChannel(channelid);;
if (mChannel == null) {
mChannel = new NotificationChannel(channelid, channelname, importance);
mChannel.setDescription("My Channel");
// 設置通知出現時的閃燈(如果 android 設備支持的話)
mChannel.enableLights(true);
mChannel.setLightColor(Color.RED);
// 設置通知出現時的震動(如果 android 設備支持的話)
mChannel.enableVibration(true);
mChannel.setVibrationPattern(new long[]{100, 200, 300, 400, 500, 400, 300, 200, 400});
manager.createNotificationChannel(mChannel);
}
notification = new NotificationCompat.Builder(MainActivity.this, channelid )
.setContentTitle("This is Title")
.setContentText("This is content Text")
.setWhen(System.currentTimeMillis())
.setSmallIcon(R.drawable.logo)
.setLargeIcon(BitmapFactory.decodeResource(getResources(), R.drawable.icon128))
.setAutoCancel(true)
.setContentIntent(pendingIntent)
.build();
manager.notify(1, notification);
從相冊查看相片
if (ContextCompat.checkSelfPermission(cameraActivity.this, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
Log.d(TAG, "onClick: checkpermission failed");
ActivityCompat.requestPermissions(cameraActivity.this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, 1);
} else {
Log.d(TAG, "onClick: start image select");
Intent intent = new Intent("android.intent.action.GET_CONTENT");
intent.setType("image/*");
startActivityForResult(intent, 2);
}
調用相機
File file = new File(getExternalCacheDir(), "image.jpg");
try {
if(file.exists()) {
file.delete();
}
file.createNewFile();
} catch (IOException e) {
e.printStackTrace();
}
// 第二個參數可以是任意唯一的字符串
imageUri = FileProvider.getUriForFile(cameraActivity.this, "com.example.cameratest.provider", file);
Intent intent = new Intent("android.media.action.IMAGE_CAPTURE");
intent.putExtra(MediaStore.EXTRA_OUTPUT, imageUri);
startActivityForResult(intent, 1);
服務
Android多線程
和其他的gui庫一樣, android的UI也是線程不安全的, 也就是說, 如果想要更新UI元素, 就必須在主線程中進行, 否則就會出現異常.
因此, android提供了一套異步消息處理機制, 解決了線程中的通信問題.
寫在UI線程中
private Handler handler = new Handler(){
@Override
public void handleMessage(Message message) {
switch (message.what) {
case 1:
String txt = (String)message.obj;
text.setText(txt);
break;
}
}
};
寫在子線程中
Message message = new Message();
message.obj = "helloworld";
handle.sendMessage(message);
使用AsyncTask
android還提供了另外一些工具以方便在子進程中對UI進行操作.
服務
跟activity類似, 服務也存在onCreate, onDestroy方法, 服務主要用于執(zhí)行一些比較耗時的工作. 另外Service并不是運行在單獨線程中,而是主線程中。所以盡量要避免一些ANR(Application Not Responding)的操作杂抽。
public class MyService extends Service {
String TAG = "MyService";
public MyService() {
}
@Override
public void onCreate(){
super.onCreate();
Log.d("MyService", "onCreate execute");
}
@Override
public int onStartCommand(Intent intent, int flags, int startId){
Log.d("MyService", "onStartCommand execute");
return super.onStartCommand(intent, flags, startId);
}
@Override
public void onDestroy(){
super.onDestroy();
Log.d("MyService", "onDestroy execute");
}
}
活動與服務通信
為了讓活動與服務通信, 需要借助onBind接口, 通過這個接口, 活動可以調用服務的方法獲取狀態(tài), 以進行ui更新操作, 比如說在迅雷中的下載操作需要通過服務來進行, 然而為了實時更新進度條, 這就需要活動通過onBind接口實時獲取到進度之后更新進度條顯示.
public MyBinder myBinder = new MyBinder();
class MyBinder extends Binder{
public void startBind(){
Log.d("MyService", "startBind execute");
}
public int getProgress() {
Log.d(TAG, "getProgress: execute");
return 0;
}
}
@Override
public IBinder onBind(Intent intent) {
// TODO: Return the communication channel to the service.
Log.d("MyService", "onBind execute");
return myBinder;
}
@Override
public boolean onUnbind(Intent intent){
Log.d("MyService", "onUnbind execute");
return true;
}
其中onBind和onUnbind方法是用于在綁定和解除綁定服務的時候調用的, 當活動與服務綁定之后, 就可以調用服務中的方法了.
Button bindService= (Button) findViewById(R.id.bindService);
bindService.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Intent intent = new Intent(MainActivity.this, MyService.class);
bindService(intent, connection, BIND_AUTO_CREATE);
}
});
Button unbindService= (Button) findViewById(R.id.unbindService);
unbindService.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
unbindService(connection);
}
});
private MyService.MyBinder myBinder ;
private ServiceConnection connection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
myBinder = (MyService.MyBinder)iBinder;
myBinder.startBind();
myBinder.getProgress();
}
@Override
public void onServiceDisconnected(ComponentName componentName) {
}
};
服務的生命周期
服務與活動的生命周期有一點重要的區(qū)別, 在于服務存在一個綁定的方法.
- onCreate() 創(chuàng)建時調用
- onStartCommand() 啟動服務時調用
- onBind() 綁定服務時調用, 只要調用方和服務之間的連接沒有斷開, 服務就會一直保持運行狀態(tài). 如果在之前沒有調用過startService()方法, 則還會先調用服務的onCreate()方法
- onUnBind() 取消綁定時調用, 如果是調用onBind()啟動的服務, 并且沒有調用過startService()方法, 則此時還會銷毀服務.
- onDestroy() 銷毀時調用, 注意如果一個服務既調用了startService()也調用了bingService()方法, 則此時必須要同時調用stopService()和unBindService()方法才會銷毀.
使用前臺服務
當系統(tǒng)內存不足時, 還是可能會回收掉后臺運行的服務, 如果希望服務可以一直保持運行狀態(tài)而不會因為系統(tǒng)內存不足而被回收掉, 可以使用前臺服務.
前臺服務和普通服務的區(qū)別在于前臺服務會一直有一個正在運行的圖標在系統(tǒng)的狀態(tài)欄顯示. 比如說音樂播放器的正在播放狀態(tài).
// 在service的oncreate方法中執(zhí)行
Intent intent = new Intent(MainActivity.this, FruitRecycleView.class);
PendingIntent pendingIntent = PendingIntent.getActivity(MainActivity.this, 0, intent, 0); //設置點擊通知的響應
Notification notification = new NotificationCompat.Builder(this)
.setContentTitle("This is Title")
.setContentText("This is content Text")
.setWhen(System.currentTimeMillis())
.setSmallIcon(R.drawable.logo)
.setLargeIcon(BitmapFactory.decodeResource(getResources(), R.drawable.icon128))
.setContentIntent(pendingIntent)
.build();
startForceground(1, notification);