目录
Android Programming
须知
这篇文档是一个萌新初步学习了Android Programming之后做的总结,如果你也是从零开始学习Android编程,你需要知道:
-
Android官方文档和csdn大佬的博客是你永远应该优先考虑参考的对象
-
你必须同步阅读菜鸟教程或通过其他方式(b站大学)对android programming有一个初步的了解,包括Android资源组织,Java基本语法和活动、服务、广播分别是什么。
-
你需要提前配置好Java编程环境,然后再下载Android Studio IDE,这一切我是在window操作系统完成的
Android Studio(IDE)
版本选择
在官网首页下最新版,需要配置Java环境(推荐jre 8和jdk 11)
存在的问题
-
MainActivity默认是.kt文件,直接删除或者后缀改成.java
-
res目录下缺少layout目录(layout相当于一个屏幕画板),在res上右键新建文件夹(directory)命名为layout,在新建的layout目录上右键新建Layout Resourse File,一般主画板通用命名为activity_main.xml。可选LinearLayout(线性布局)和RelativeLayout(相对布局),常用RelativeLayout。
-
当然res下同样缺少menu(菜单)目录,如果程序中需要用到,可以提前在res中新建menu文件夹,再新建menu_main.xml文件
-
新版本默认的SDK(30以上)和Android(10以上)版本都很高,有可能遇到版本问题
-
debug示例:无报错信息一直闪退
android:theme="@style/Theme.something” <!--改成--> android:theme="@style/Theme.AppCompat”
第一个Android程序:Hello World
android studio上实现Hello world 史上最全,最简单明了的教程_android studio hello world 在哪-CSDN博客
Android Hello World实例 |菜鸟教程 (runoob.com)
活动 Activity
概念
描述UI以及用户与机器屏幕的交互
回调方法
回调 | 描述 |
---|---|
onCreate() | 这是第一个回调,在活动第一次创建时调用 |
onDestroy() | 当活动被系统销毁之前调用 |
注意事项
一个应用程序可以有1个或多个活动,而没有限制,每个为应用程序所定义的活动都需要在AndroidManifest.xml中声明。应用的主要活动需要在清单中声明,且中需要包含MAIN动作和LAUNCHER类别
菜鸟教程实操
你需要跟着教程一步步实现Activity的基本服务
Android 活动(Activity) |菜鸟教程 (runoob.com)
服务Service
概述
处理与应用程序相关联的后台操作
服务是在后台运行的组件,即使应用被销毁也依然可以工作。服务包含两种状态
*状态* | *描述* |
---|---|
Started | Android的应用程序组件,如活动,通过startService()启动了服务,则服务是Started状态。一旦启动,服务可以在后台无限期运行,即使启动它的组件已经被销毁。 |
Bound | 当Android的应用程序组件通过bindService()绑定了服务,则服务是Bound状态。Bound状态的服务提供了一个客户服务器接口来允许组件与服务进行交互,如发送请求,获取结果,甚至通过IPC来进行跨进程通信。 |
服务的生命周期
-
onCreate,onStart,onDestroy
-
onCreate,onBind,onUnbind,[onRebind,onDestroy
要创建一个服务,你需要一个继承自Service基类或者它已知子类的Java类,显然你不需要重载所有回调方法
*回调* | *描述* |
---|---|
onStartCommand() | 其他组件(如活动)通过调用startService()来请求启动服务时,系统调用该方法。如果你实现该方法,你有责任在工作完成时通过stopSelf()或者stopService()方法来停止服务。 |
onBind | 当其他组件想要通过bindService()来绑定服务时,系统调用该方法。如果你实现该方法,你需要返回IBinder对象来提供一个接口,以便客户来与服务通信。你必须实现该方法,如果你不允许绑定,则直接返回null。 |
onUnbind() | 当客户中断所有服务发布的特殊接口时,系统调用该方法。 |
onRebind() | 当新的客户端与服务连接,且此前它已经通过onUnbind(Intent)通知断开连接时,系统调用该方法。 |
onCreate() | 当服务通过onStartCommand()和onBind()被第一次创建的时候,系统调用该方法。该调用要求执行一次性安装。 |
onDestroy() | 当服务不再有用或者被销毁时,系统调用该方法。你的服务需要实现该方法来清理任何资源,如线程,已注册的监听器,接收器等。 |
菜鸟教程实例
你需要跟着教程一步步实现Service的基本服务,同时也会学到关于menu,layout和button的小知识
Android 服务(Service) |菜鸟教程 (runoob.com)
广播与接收器 Broadcast Receiver
概念
广播用来处理Android操作系统和应用程序之间的通信
广播接收器用于响应来自其他应用程序或者系统的广播消息
- 创建和注册广播接收器
- 创建和广播意图(Intent)
静态注册广播接收器
以注册一个接收开机广播的Receiver为例
<receiver android:name=”.My Receiver”
android:enabled="true"
android:exported="true"
android:permission="android.permission.RECEIVE_BOOT_COMPLETED">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED">
</action>
</intent-filter>
</receiver>
上方授予权限(Permission)
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
package com.smali.secretchallenge;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.util.Log;
import android.widget.Toast;
import java.time.LocalDate;
public class SecretBootReceiver extends BroadcastReceiver {
private static final String TAG = "SecretBootReceiver";
@Override
public void onReceive(Context context, Intent intent){
if (Intent.ACTION_BOOT_COMPLETED.equals(intent.getAction())) {
Log.d(TAG, "已经启动");
Toast.makeText(context, "检测到意图", Toast.LENGTH_LONG).show();
context.startService(new Intent(context,SecretService.class));
}
}
}
动态注册广播接收器
1.一篇很详细的博客:Android广播(接收,发送,标准,有序,全局,本地,实战,填坑)_android 广播-CSDN博客
2.官方文档:广播概览 | Background work | Android Developers (google.cn)
静态注册注意事项!!!
Android 8.0后,静态注册的接收器无法接收到隐式广播,但是我们发出的广播默认都为隐式的,因此需要调用setPackage方法指定此广播发送给应用程序,让其变成显示广播(tips:如果用户自定义广播,多用动态法注册)
//发送广播
Intent intent = new Intent("com.example.viewmodeltest.MY_BROADCAST_RECEIVER");
intent.setPackage(getPackageName());
sendOrderedBroadcast(intent, null);
});
adb命令行
adb命令能够帮助你模拟开机自启动的情况
AVD(安卓虚拟机)启动后,在Android Studio下方的window终端输入
adb root
adb shell am broadcast -a android.intent.action.BOOT_COMPLETED -p com.smali.secretchallenge
权限 Permission
//请求获得权限
//Manifest.permission.ACCESS_FINE_LOCATION(精确位置权限)可替换成任意权限名
if (checkSelfPermission(Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
//请求权限
requestPermissions(new String[]{Manifest.permission.ACCESS_FINE_LOCATION},0);
}
else {
Toast.makeText(this,"已经获得权限",Toast.LENGTH_LONG).show();
}
从接收器启动服务
context.startService(new Intent(context,SecretService.class));
位置服务
```
if (checkSelfPermission(android.Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED ){
Toast.makeText(this, "服务未获得位置权限", Toast.LENGTH_LONG).show();
//未获得权限的操作
//...
// 例如ActivityCompat.requestPermissions(this, new String[]{android.Manifest.permission.ACCESS_FINE_LOCATION}, REQUEST_CODE_LOCATION);
}
else {
Toast.makeText(this, "服务获得位置权限", Toast.LENGTH_LONG).show();
//获得位置权限的操作
//...
}
```
```
//获取位置服务
LocationManager locationManager = (LocationManager) getSystemService(Context.LOCATION_SERVICE);
LocationListener locationListener=new LocationListener() {
@Override
public void onLocationChanged(@NonNull Location location) {
double lat = location.getLatitude();//获取纬度
double lng = location.getLongitude();//获取经度
Log.d("Location", "Latitude: " + lat + ", Longitude: " + lng);
}
@Override
public void onStatusChanged(String provider, int status, Bundle extras) {
}
@Override
public void onProviderEnabled(String provider) {
}
@Override
public void onProviderDisabled(String provider) {
}
};
locationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER,0,0,locationListener);
Snackbar消息
构建并显示弹出式消息 | Android 开发者 | Android Developers (google.cn)
为什么要用
- 轻量级的消息,快速
- 可以结合按钮处理点击事件
需要导入的库
import com.google.android.material.snackbar.Snackbar;
在build.gradle中
implementation 'com.google.android.material:material:1.9.0'
必须要注意的是:
版本,版本,还是tmd版本
在老版本中,有个叫design的库也在干和material同样的事情,chatGPD这个老东西往往会给你推荐这个库,但是如果你用的是androidX库的话和design是互相冲突的,因为我是一直致力于使用新版本这里也不再介绍design的用法了。
使用CoordinatorLayout
如果
Snackbar
附加到派生自View
类的任何对象,它会提供基本的功能:弹出弹窗。不过如果Snackbar
附加到CoordinatorLayout
,Snackbar
会获得额外的功能
- 用户可以通过滑动把
Snackbar
关闭- 当
Snackbar
出现时,布局会移动其他界面元素。例如,如果布局具有FloatingActionButton
,则当显示Snackbar
时,布局会将按钮向上移动,而不是在按钮上绘制Snackbar
。
如果你已经使用了其他的布局对象,可以将现有的布局元素封装再CoordinatorLayout中
<!-和Android官方唯一的不同:没用design库--->
<androidx.coordinatorlayout.widget.CoordinatorLayout
android:id="@+id/myCoordinatorLayout"
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<!-- Here are the existing layout elements-->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<!-- ...Toolbar, other layouts, other elements... -->
</LinearLayout>
</android.support.design.widget.CoordinatorLayout>
显示消息
Snackbar mySnackbar = Snackbar.make(view, stringId, duration);
mySnackbar.show();
view:可以传递窗口上任意的View类对象或者是
View view=findViewById(R.id.myCoordinatorLayout);
如果您使用的是CoordinatorLayout
stringId:这里以String值传递您想要打印的消息
duration
处理 Handler
延时启动
处理Handler
和运行Runnable
Android中Handler延迟执行、定时任务_handle明明设置了隔20秒执行一次,但是过了60多秒才执行-CSDN博客
线程Thread
Android开发中Thread线程的基本使用(总结)_android中实现线程tread的两种方法是什么-CSDN博客
通知 Nortification
通知为低权限应用提供了在后台发出消息的能力
Android开发之Notification(实现消息弹窗、提示音以及点击事件)_android通知栏弹出消息-CSDN博客
创建通知 | Android 开发者 | Android Developers (google.cn)
步骤
-
创建通知隧道
channel
private void createNotificationChannel() { // Create the NotificationChannel, but only on API 26+ because // the NotificationChannel class is not in the Support Library. CharSequence name = getString(R.string.channel_name); String description = getString(R.string.channel_description); int importance = NotificationManager.IMPORTANCE_HIGH; NotificationChannel channel = new NotificationChannel("channel_one", name, importance); channel.setDescription(description); NotificationManager notificationManager = getSystemService(NotificationManager.class); notificationManager.createNotificationChannel(channel); }
-
创建通知
//通过点击可以跳转到你的MainActivity Intent intent = new Intent(SecretService.this, MainActivity.class); intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); PendingIntent pendingIntent = PendingIntent.getActivity(SecretService.this, 0, intent, PendingIntent.FLAG_IMMUTABLE); builder = new NotificationCompat.Builder(SecretService.this, "channel_one")//隧道名要和你定义的隧道相同 .setContentTitle("Location") .setContentText(msg) .setSmallIcon(R.drawable.ic_launcher_background) .setPriority(NotificationCompat.PRIORITY_MAX) // Set the intent that fires when the user taps the notification. .setContentIntent(pendingIntent) .setAutoCancel(true); NotificationManagerCompat notificationManager = NotificationManagerCompat.from(SecretService.this); notificationManager.notify(1,builder.build());
-
发送通知
NotificationManagerCompat notificationManager = NotificationManagerCompat.from(SecretService.this); notificationManager.notify(1,builder.build());
前台服务 ForeService
支持直接从后台启动?
可能的解决方案:JobScheduler或
WorkManager,ForeServive
Task 2
task内容:在处理Button
点击事件时,在屏幕上打印editText
的内容
Dialog
对话框是提示用户做出决定或输入更多信息的小窗口。对话框不会填满屏幕,通常用于需要用户执行操作才能继续的模态事件。
对话框 | Android 开发者 | Android Developers
Handler异步通信
Android异步通信:手把手教你使用Handler消息传递机制(含实例讲解)_android sendmessage 异步-CSDN博客
EditText
android 如何获取输入框内容 - CSDN文库
如何获取输入框内容 1 获取 EditText 控件对象:2.使用 findViewById (),对象转换为 String:3.使用 toString () 方法将 Editable 对象转换为 String。
消息,处理和消息队列
完全解析Android:Looper与Handler机制 - 掘金 (juejin.cn)
package com.smali.secretchallenge2;
import android.app.Dialog;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.util.Log;
import android.view.View;
import android.widget.EditText;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.app.AppCompatActivity;
public class MainActivity extends AppCompatActivity {
public Handler mhandler;
public EditText mEditText;
public String inputText;
class Mhandler extends Handler {
@Override
public void handleMessage(Message msg){
switch (msg.what){
case 1:
AlertDialog.Builder builder = new AlertDialog.Builder(MainActivity.this);
builder.setMessage(msg.obj.toString());
final AlertDialog dialog = builder.create();
dialog.show();
break;
}
}
}
public void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//获取到EditText
mEditText=findViewById(R.id.editText1);
//实例化Handler
mhandler=new Mhandler();
}
public void showDialog(View view){
inputText=mEditText.getText().toString();
Log.d("文本内容",inputText);
//启动一个新线程
new Thread(){
@Override
public void run(){
Message msg=Message.obtain();
msg.what=1;
inputText=mEditText.getText().toString();
msg.obj=inputText;
mhandler.sendMessage(msg);
//下次检查时退出线程
Thread.currentThread().interrupt();
}
}.start();
}
}
存在的问题
现在的实现每次点击都会启动一个额外的线程,虽然也不是不行,但是还是不太规范,正确的做法是用循环器Looper
在一个线程中循环启动Handler
,这样只需要在主活动的onCreate
方法中启动一次线程Thread
。
在处理点击事件时,向消息队列中发送一个消息即可。