此文假设所有人都会java的语法.
手把手创建项目
- 打开Android studio后, 选择
file
-new project
-选择Phone and Tablet
-选中Basic Activity
, 点击Next
, 输入名字和选择api level之后, 点击Finish
. - 此时android studio 自动创建了一个项目并进行sync 操作. 查看对应的
AndroidManifest.xml
文件, 可以看到已经自动创建了一个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 // 最低支持的系统版本
targetSdkVersion 28 // 默认的系统版本
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时看到的每一个界面都是一个活动.
创建布局
- 在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" // 这里表示宽度与父节点一致
android:layout_height="wrap_content" // 这里表示高度适应大小
android:text="Button" /> // text表示button上显示的文本
活动的基本用法
- 在onCreate() 中加载布局:
setContentView(R.layout.main_activity);
- 使用findViewById方法获取到对应的View后, 添加事件监听
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键会销毁最上面的活动, 下面的活动重新显示出来.-
活动状态
每个活动在其生命周期中最多可能会有4种状态- 运行状态
当一个活动位于栈顶时, 就处于运行状态. - 暂停状态
当一个活动不再处于栈顶, 但活动依然可见时, 就处于暂停状态, 比如说点击某个按钮后弹出对话框, 此时对话框处于运行状态, 对话框下的活动处于暂停状态. 处于暂停状态下的活动仍然是存活的, 系统也不愿意去回收这种活动. - 停止状态
当一个活动不再处于栈顶, 并且不可见时, 就进入停止状态. 系统会为这种活动保存相应的状态和成员变量, 但是当其他地方需要内存时, 停止状态的活动可能会被回收. - 销毁状态
当一个活动从返回栈中移除后就变成销毁状态.
- 运行状态
-
活动的生存期
Activity类提供了7个回调方法, 覆盖了生命周期中的每一个环节, 依次为- onCreate() 创建时调用, 这个创建可能是第一次创建, 也有可能是被系统回收后, 从返回栈中取出状态重新创建
- onStart() 活动由不可见状态变为可见状态时调用
- onResume() 活动准备好与用户进行交互时调用, 此时活动一定位于栈顶(比如关闭对话框时调用)
- onPause() 系统准备去启动或者恢复另一个活动的时候调用
- onStop() 活动由可见变为不可见时调用, 这个跟onPause的区别在于如果启动的是一个对话框, 则onPause会执行, onStop不会执行
- onDestroy() 活动被销毁时调用
- onRestart() 活动由停止状态变为运行状态时调用
- 活动回收的处理
前面的生命周期可以看到, 如果活动进入了停止状态, 是有可能被系统回收的. 那么这时候如果用户按back键返回一个已经被回收的活动, 这时候系统不会执行onRestart()方法, 而是会执行onCreate()方法重新创建一次.
但是活动是可能存在输入状态的, 比如文本框中的输入文字, 在系统回收了该活动后, 重新创建出来的活动如果没有了用户输入的内容, 是会严重影响用户体验的. 所以Android提供了一个onSaveInstanceState(Bundle outState)
方法, 用于在系统回收活动的时候调用该方法保存用户输入. 代码如下
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模式下, 每当启动一个新的活动, 它就会在返回栈中入栈, 并处于栈顶的位置, 系统并不关注活动是否在返回栈中已存在. - singleTop
在singleTop模式下, 在启动活动时系统会判断返回栈的栈顶是否是该活动, 如果是则认为可以直接使用它. 不会再创建活动实例. - singleTask
在singleTask模式下, 在启动活动时系统会首先在返回栈中检查是否已经存在该活动的实例, 如果发现已存在则直接使用该实例, 并且把该活动之上的所有活动都出栈, 销毁. 如果没有则创建一个新的活动实例. - singleInstance
指定为singleInstance模式的活动会启用一个新的返回栈来管理这个活动. 一般用于在与其他的程序共享一个活动的实例时.
- standard
UI
常用控件
因为篇幅有限, 加上控件的使用大同小异, 就不详细写控件的使用了, 只写常用控件的特性和应用场景, 目的就是让大家知道有这么个东西, 能解决什么样的问题, 在关键时刻的时候寻找谷哥的帮助时知道用什么关键词.
- TextView 显示文本信息
- Button 按钮
- EditText 输入框
- ImageView 图片控件
- ProgressBar 进度条
- AlertDialog 对话框
- ProgressDialog 进度条对话框
- RecyclerView 滚动控件
基本布局
UI 就是布局加控件的组合 -- 鲁迅.
所以我们接下来还要了解一下基本布局
- 线性布局 LinearLayout, 如名所示, 这个布局会将它所包含的控件在线性上依次排列.
- 相对布局 RelativeLayout, 它可以通过相对定位的方式让控件出现在布局的任何地方.
- 帧布局 FrameLayout, 这种布局没有方便的定位方式, 所有的控件都会默认摆放在布局的左上角.
- 百分比布局 PercentFrameLayout 和 PercentRelativeLayout
广播
-
广播机制简介
Android中的每个应用都可以对自己感兴趣的广播进行注册, 这样该程序就只会接收到自己所关心的广播内容. 这些广播可能是来自于系统的, 也可能是来自于其他应用程序的.- 标准广播 是一种完全异步执行的广播, 在广播发出之后, 所有的广播接收器几乎都会在同一时刻接收到这条广播消息
- 有序广播 则是一种同步执行的广播, 在广播发出之后, 同一时刻只会有一个广播接收器可以接收到这条消息. 当这条广播接受器的逻辑处理完成后, 广播才会继续传递, 因此前面的广播接收器可以截断正在传递的广播, 这样后面的广播接收器就无法收到广播消息了.
-
接收系统广播
- 动态注册监听网络变化, 先在AndroidManifest.xml中加入
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" ></uses-permission>
- 动态注册监听网络变化, 先在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) {
}
}
- 静态注册实现开机启动
<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>
- 发送广播
发广播的方法跟启动活动的方法很相似
Intent intent = new Intent("com.zzy.broadcast.mybroadcast");
sendBoardcast(intent);
发送有序广播的方法跟发送标准广播的方法只需要修改一点sendOrderedBoardcast(intent, null);
这里的第二个参数是一个与权限有关的字符串.
- 发送本地广播
前面我们发送和接收的广播全部属于系统全局广播, 这样很容易引起安全性问题, 为了能够解决安全性问题, android引入了一套本地广播体系, 本地广播只能够在应用程序的内部进行传递, 并且广播接收器也只能接收来自本应用程序发出的广播. 本地广播的操作和全局广播基本是一样的, 只是通过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方法, 服务主要用于执行一些比较耗时的工作. 另外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接口, 通过这个接口, 活动可以调用服务的方法获取状态, 以进行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) {
}
};
服务的生命周期
服务与活动的生命周期有一点重要的区别, 在于服务存在一个绑定的方法.
- onCreate() 创建时调用
- onStartCommand() 启动服务时调用
- onBind() 绑定服务时调用, 只要调用方和服务之间的连接没有断开, 服务就会一直保持运行状态. 如果在之前没有调用过startService()方法, 则还会先调用服务的onCreate()方法
- onUnBind() 取消绑定时调用, 如果是调用onBind()启动的服务, 并且没有调用过startService()方法, 则此时还会销毁服务.
- onDestroy() 销毁时调用, 注意如果一个服务既调用了startService()也调用了bingService()方法, 则此时必须要同时调用stopService()和unBindService()方法才会销毁.
使用前台服务
当系统内存不足时, 还是可能会回收掉后台运行的服务, 如果希望服务可以一直保持运行状态而不会因为系统内存不足而被回收掉, 可以使用前台服务.
前台服务和普通服务的区别在于前台服务会一直有一个正在运行的图标在系统的状态栏显示. 比如说音乐播放器的正在播放状态.
// 在service的oncreate方法中执行
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);