
文章目录
??简述
PictureSelector相机和相册选择分开,全部代码,拿去可用。
流程,Android 通过webView的loadUrl一个H5页面。H5页面点击file,webview通过setWebChromeClient重写onShowFileChooser【当H5点击file时候会自动执行Android中onShowFileChooser方法】。然后后面再对点击的不同执行其他操作,是拍照、选择相册还是其他的。废话不多说,直接看gif动图和代码。
??动图
这就是简单实现的内容,细节方面因为前端只能接受一张照片,所以还没有做处理。两张以及两张以上可以在前端做一个循环,因为Android中回调的是一个数组形式的。大致实现了,细节方面可以再自己优化优化。
gif动图。。。。。。
??核心代码
??重写onShowFileChooser
webView.setWebChromeClient(new WebChromeClient() {
/**
* API >= 21(Android 5.0.1)回调此方法
*/
@Override
public boolean onShowFileChooser(WebView webView, ValueCallback<Uri[]> valueCallback, WebChromeClient.FileChooserParams
fileChooserParams) {
Log.e("file回调", "运行方法 onShowFileChooser");
// (1)该方法回调时说明版本API >= 21,此时将结果赋值给 mUploadCallbackAboveL,使之 != null
mUploadCallbackAboveL = valueCallback;
showPopwindow();
return true;
}
});
??PictureSelector点击直接激活相机
private void SelectorPhoto() {
PictureSelector.create(this)
.openCamera(SelectMimeType.ofImage())
.imageEngine(GlideEngine.createGlideEngine())
.forResult(new OnResultCallbackListener<LocalMedia>() {
@Override
public void onResult(List<LocalMedia> result) {
mUploadCallbackAboveL.onReceiveValue(new Uri[]{Uri.parse(result.get(0).getPath())});
}
@Override
public void onCancel() {
}
});
}
??弹窗代码
private void showPopwindow() {
View parent = ((ViewGroup) this.findViewById(android.R.id.content)).getChildAt(0);
View popView = inflate(this, R.layout.camera_pop_menu, null);
Button btnCamera = (Button) popView.findViewById(R.id.btn_camera_pop_camera);
Button btnAlbum = (Button) popView.findViewById(R.id.btn_camera_pop_album);
Button btnCancel = (Button) popView.findViewById(R.id.btn_camera_pop_cancel);
int width = getResources().getDisplayMetrics().widthPixels;
int height = getResources().getDisplayMetrics().heightPixels;
final PopupWindow popWindow = new PopupWindow(popView,width,height);
popWindow.setFocusable(true);
popWindow.setOutsideTouchable(false);// 设置同意在外点击消失
View.OnClickListener listener = new View.OnClickListener() {
public void onClick(View v) {
switch (v.getId()) {
case R.id.btn_camera_pop_camera:
// takePhoto();
SelectorPhoto();
break;
case R.id.btn_camera_pop_album:
InitSelectorMethold();
break;
case R.id.btn_camera_pop_cancel:
break;
}
popWindow.dismiss();
}
};
btnCamera.setOnClickListener(listener);
btnAlbum.setOnClickListener(listener);
btnCancel.setOnClickListener(listener);
ColorDrawable dw = new ColorDrawable(0x30000000);
popWindow.setBackgroundDrawable(dw);
popWindow.showAtLocation(parent, Gravity.BOTTOM | Gravity.CENTER_HORIZONTAL, 0, 0);
}
??private String url = “http://10.1.192.88:8080/pictures.html”;
因为想要更接近项目环境,为了更好的体现出webview,所以这个是使用http-server【小型服务器(本地)】,手机和电脑要在同一个局域网下。当然你也可以不用。使用本地文件。
??全部代码
??MainActivity.java
package com.example.app7;
import android.content.Intent;
import android.graphics.drawable.ColorDrawable;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.util.Log;
import android.view.Gravity;
import android.view.View;
import android.view.ViewGroup;
import android.webkit.ValueCallback;
import android.webkit.WebChromeClient;
import android.webkit.WebSettings;
import android.webkit.WebView;
import android.widget.Button;
import android.widget.PopupWindow;
import androidx.annotation.RequiresApi;
import androidx.appcompat.app.AppCompatActivity;
import com.luck.picture.lib.PictureSelector;
import com.luck.picture.lib.config.PictureConfig;
import com.luck.picture.lib.entity.LocalMedia;
import com.luck.picture.lib.listener.OnResultCallbackListener;
import java.util.List;
import static android.view.View.inflate;
public class MainActivity extends AppCompatActivity {
private Uri imageUri;
private int REQUEST_CODE = 1234;
private android.webkit.ValueCallback<Uri[]> mUploadCallbackAboveL;
private android.webkit.ValueCallback<Uri> mUploadCallbackBelow;
private WebView webView;
private String url = "http://10.1.192.88:8080/pictures.html";
// todo private AlertDialog.Builder PictureSelector;
private Object PictureMimeType;
private com.luck.picture.lib.config.PictureMimeType SelectMimeType;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initWebView();
}
//初始化webView
private void initWebView() {
//从布局文件中扩展webView
webView = (WebView) this.findViewById(R.id.webview);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
webView.setWebContentsDebuggingEnabled(true);
}
initWebViewSetting();
}
//初始化webViewSetting
@RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN)
private void initWebViewSetting() {
WebSettings settings = webView.getSettings();
settings.setUseWideViewPort(true);
settings.setLoadWithOverviewMode(true);
settings.setDomStorageEnabled(true);
settings.setDefaultTextEncodingName("UTF-8");
settings.setAllowContentAccess(true); // 是否可访问Content Provider的资源,默认值 true
settings.setAllowFileAccess(true); // 是否可访问本地文件,默认值 true
// 是否允许通过file url加载的Javascript读取本地文件,默认值 false
settings.setAllowFileAccessFromFileURLs(false);
// 是否允许通过file url加载的Javascript读取全部资源(包括文件,http,https),默认值 false
settings.setAllowUniversalAccessFromFileURLs(false);
//开启JavaScript支持
settings.setJavaScriptEnabled(true);
// 支持缩放
settings.setSupportZoom(true);
//加载地址
webView.loadUrl(url);
webView.setWebChromeClient(new WebChromeClient() {
/**
* API >= 21(Android 5.0.1)回调此方法
*/
@Override
public boolean onShowFileChooser(WebView webView, ValueCallback<Uri[]> valueCallback, WebChromeClient.FileChooserParams
fileChooserParams) {
Log.e("file回调", "运行方法 onShowFileChooser");
// (1)该方法回调时说明版本API >= 21,此时将结果赋值给 mUploadCallbackAboveL,使之 != null
mUploadCallbackAboveL = valueCallback;
// InitSelectorMethold();
// takePhoto();
// AlertDialog.Builder dialog=new AlertDialog.Builder(MainActivity.this);
// dialog.setTitle("这是一个弹框提示");
// dialog.setMessage("嗯,这是消息内容");
// dialog.setCancelable(false);
//
// dialog.setPositiveButton("OK", new DialogInterface.OnClickListener() {
// @Override
// public void onClick(DialogInterface dialogInterface, int i) {
// Toast.makeText(MainActivity.this, "确定,成功了", Toast.LENGTH_SHORT).show();
// InitSelectorMethold();
// }
//
//
// });
// dialog.setNegativeButton("cancel", new DialogInterface.OnClickListener() {
// @Override
// public void onClick(DialogInterface dialogInterface, int i) {
// Toast.makeText(MainActivity.this,"取消,失败了",Toast.LENGTH_SHORT).show();
// takePhoto();
//
// }
//
//
// });
//
// dialog.show();
showPopwindow();
return true;
}
});
}
private void showPopwindow() {
View parent = ((ViewGroup) this.findViewById(android.R.id.content)).getChildAt(0);
View popView = inflate(this, R.layout.camera_pop_menu, null);
Button btnCamera = (Button) popView.findViewById(R.id.btn_camera_pop_camera);
Button btnAlbum = (Button) popView.findViewById(R.id.btn_camera_pop_album);
Button btnCancel = (Button) popView.findViewById(R.id.btn_camera_pop_cancel);
int width = getResources().getDisplayMetrics().widthPixels;
int height = getResources().getDisplayMetrics().heightPixels;
final PopupWindow popWindow = new PopupWindow(popView,width,height);
popWindow.setFocusable(true);
popWindow.setOutsideTouchable(false);// 设置同意在外点击消失
View.OnClickListener listener = new View.OnClickListener() {
public void onClick(View v) {
switch (v.getId()) {
case R.id.btn_camera_pop_camera:
// takePhoto();
SelectorPhoto();
break;
case R.id.btn_camera_pop_album:
InitSelectorMethold();
break;
case R.id.btn_camera_pop_cancel:
break;
}
popWindow.dismiss();
}
};
btnCamera.setOnClickListener(listener);
btnAlbum.setOnClickListener(listener);
btnCancel.setOnClickListener(listener);
ColorDrawable dw = new ColorDrawable(0x30000000);
popWindow.setBackgroundDrawable(dw);
popWindow.showAtLocation(parent, Gravity.BOTTOM | Gravity.CENTER_HORIZONTAL, 0, 0);
}
private void SelectorPhoto() {
PictureSelector.create(this)
.openCamera(SelectMimeType.ofImage())
.imageEngine(GlideEngine.createGlideEngine())
.forResult(new OnResultCallbackListener<LocalMedia>() {
@Override
public void onResult(List<LocalMedia> result) {
mUploadCallbackAboveL.onReceiveValue(new Uri[]{Uri.parse(result.get(0).getPath())});
}
@Override
public void onCancel() {
}
});
}
private void InitSelectorMethold() {
// 进入相册 以下是例子:用不到的api可以不写
PictureSelector.create(this)
.openGallery(PictureConfig.TYPE_IMAGE)//全部.PictureMimeType.ofAll()、图片.ofImage()、视频.ofVideo()、音频.ofAudio()
// .theme()//主题样式(不设置为默认样式) 也可参考demo values/styles下 例如:R.style.picture.white.style
.maxSelectNum(4)// 最大图片选择数量 int
.minSelectNum(2)// 最小选择数量 int
// .imageSpanCount(4)// 每行显示个数 int
.selectionMode(PictureConfig.MULTIPLE)// 多选 or 单选 PictureConfig.MULTIPLE or PictureConfig.SINGLE
.previewImage(true)// 是否可预览图片 true or false
.previewVideo(true)// 是否可预览视频 true or false
.enablePreviewAudio(true) // 是否可播放音频 true or false
// .isCamera(true)// 是否显示拍照按钮 true or false
// .imageFormat(PictureMimeType.PNG)// 拍照保存图片格式后缀,默认jpeg
.isZoomAnim(true)// 图片列表点击 缩放效果 默认true
.sizeMultiplier(0.5f)// glide 加载图片大小 0~1之间 如设置 .glideOverride()无效
.setOutputCameraPath("/CustomPath")// 自定义拍照保存路径,可不填
.enableCrop(true)// 是否裁剪 true or false
.compress(true)// 是否压缩 true or false
.glideOverride(100, 100)// int glide 加载宽高,越小图片列表越流畅,但会影响列表图片浏览的清晰度
.withAspectRatio(1, 1)// int 裁剪比例 如16:9 3:2 3:4 1:1 可自定义
.hideBottomControls(true)// 是否显示uCrop工具栏,默认不显示 true or false
.isGif(true)// 是否显示gif图片 true or false
.freeStyleCropEnabled(true)// 裁剪框是否可拖拽 true or false
// .circleDimmedLayer()// 是否圆形裁剪 true or false
// .showCropFrame()// 是否显示裁剪矩形边框 圆形裁剪时建议设为false true or false
// .showCropGrid()// 是否显示裁剪矩形网格 圆形裁剪时建议设为false true or false
// .openClickSound()// 是否开启点击声音 true or false
// .selectionMedia()// 是否传入已选图片 List<LocalMedia> list
// .previewEggs()// 预览图片时 是否增强左右滑动图片体验(图片滑动一半即可看到上一张是否选中) true or false
// .cropCompressQuality()// 裁剪压缩质量 默认90 int
.minimumCompressSize(10)// 小于100kb的图片不压缩
.synOrAsy(true)//同步true或异步false 压缩 默认同步
// .cropWH()// 裁剪宽高比,设置如果大于图片本身宽高则无效 int
// .rotateEnabled() // 裁剪是否可旋转图片 true or false
// .scaleEnabled()// 裁剪是否可放大缩小图片 true or false
// .videoQuality()// 视频录制质量 0 or 1 int
.videoMaxSecond(15)// 显示多少秒以内的视频or音频也可适用 int
.videoMinSecond(10)// 显示多少秒以内的视频or音频也可适用 int
// .recordVideoSecond()//视频秒数录制 默认60s int
.isDragFrame(false)// 是否可拖动裁剪框(固定)
.imageEngine(GlideEngine.createGlideEngine())
.forResult(PictureConfig.CHOOSE_REQUEST);//结果回调onActivityResult code
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (resultCode == RESULT_OK) {
switch (requestCode) {
case PictureConfig.CHOOSE_REQUEST:
// 结果回调
List<LocalMedia> selectList = PictureSelector.obtainMultipleResult(data);
//
// for (int i = 0; i < 2; i++) {
// Uri urt = Uri.parse(selectList.get(i).getPath());
//
// Log.e("这是一个循环测试,看看数据什么样的", "onActivityResult: "+urt);
// }
mUploadCallbackAboveL.onReceiveValue(new Uri[]{Uri.parse(selectList.get(0).getPath())});
break;
default:
break;
}
}
}
}
??GlideEngine.java
package com.example.app7;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.PointF;
import android.graphics.drawable.Drawable;
import android.view.View;
import android.widget.ImageView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.graphics.drawable.RoundedBitmapDrawable;
import androidx.core.graphics.drawable.RoundedBitmapDrawableFactory;
import com.bumptech.glide.Glide;
import com.bumptech.glide.request.RequestOptions;
import com.bumptech.glide.request.target.BitmapImageViewTarget;
import com.bumptech.glide.request.target.ImageViewTarget;
import com.luck.picture.lib.engine.ImageEngine;
import com.luck.picture.lib.listener.OnImageCompleteCallback;
import com.luck.picture.lib.tools.MediaUtils;
import com.luck.picture.lib.widget.longimage.ImageSource;
import com.luck.picture.lib.widget.longimage.ImageViewState;
import com.luck.picture.lib.widget.longimage.SubsamplingScaleImageView;
public class GlideEngine implements ImageEngine {
/**
* 加载图片
*
* @param context
* @param url
* @param imageView
*/
@Override
public void loadImage(@NonNull Context context, @NonNull String url, @NonNull ImageView imageView) {
Glide.with(context)
.load(url)
.into(imageView);
}
/**
* 加载网络图片适配长图方案
* # 注意:此方法只有加载网络图片才会回调
*
* @param context
* @param url
* @param imageView
* @param longImageView
* @param callback 网络图片加载回调监听 {link after version 2.5.1 Please use the #OnImageCompleteCallback#}
*/
@Override
public void loadImage(@NonNull Context context, @NonNull String url,
@NonNull ImageView imageView,
SubsamplingScaleImageView longImageView, OnImageCompleteCallback callback) {
Glide.with(context)
.asBitmap()
.load(url)
.into(new ImageViewTarget<Bitmap>(imageView) {
@Override
public void onLoadStarted(@Nullable Drawable placeholder) {
super.onLoadStarted(placeholder);
if (callback != null) {
callback.onShowLoading();
}
}
@Override
public void onLoadFailed(@Nullable Drawable errorDrawable) {
super.onLoadFailed(errorDrawable);
if (callback != null) {
callback.onHideLoading();
}
}
@Override
protected void setResource(@Nullable Bitmap resource) {
if (callback != null) {
callback.onHideLoading();
}
if (resource != null) {
boolean eqLongImage = MediaUtils.isLongImg(resource.getWidth(),
resource.getHeight());
longImageView.setVisibility(eqLongImage ? View.VISIBLE : View.GONE);
imageView.setVisibility(eqLongImage ? View.GONE : View.VISIBLE);
if (eqLongImage) {
// 加载长图
longImageView.setQuickScaleEnabled(true);
longImageView.setZoomEnabled(true);
longImageView.setPanEnabled(true);
longImageView.setDoubleTapZoomDuration(100);
longImageView.setMinimumScaleType(SubsamplingScaleImageView.SCALE_TYPE_CENTER_CROP);
longImageView.setDoubleTapZoomDpi(SubsamplingScaleImageView.ZOOM_FOCUS_CENTER);
longImageView.setImage(ImageSource.bitmap(resource),
new ImageViewState(0, new PointF(0, 0), 0));
} else {
// 普通图片
imageView.setImageBitmap(resource);
}
}
}
});
}
/**
* 加载网络图片适配长图方案
* # 注意:此方法只有加载网络图片才会回调
*
* @param context
* @param url
* @param imageView
* @param longImageView
* @ 已废弃
*/
@Override
public void loadImage(@NonNull Context context, @NonNull String url,
@NonNull ImageView imageView,
SubsamplingScaleImageView longImageView) {
Glide.with(context)
.asBitmap()
.load(url)
.into(new ImageViewTarget<Bitmap>(imageView) {
@Override
protected void setResource(@Nullable Bitmap resource) {
if (resource != null) {
boolean eqLongImage = MediaUtils.isLongImg(resource.getWidth(),
resource.getHeight());
longImageView.setVisibility(eqLongImage ? View.VISIBLE : View.GONE);
imageView.setVisibility(eqLongImage ? View.GONE : View.VISIBLE);
if (eqLongImage) {
// 加载长图
longImageView.setQuickScaleEnabled(true);
longImageView.setZoomEnabled(true);
longImageView.setPanEnabled(true);
longImageView.setDoubleTapZoomDuration(100);
longImageView.setMinimumScaleType(SubsamplingScaleImageView.SCALE_TYPE_CENTER_CROP);
longImageView.setDoubleTapZoomDpi(SubsamplingScaleImageView.ZOOM_FOCUS_CENTER);
longImageView.setImage(ImageSource.bitmap(resource),
new ImageViewState(0, new PointF(0, 0), 0));
} else {
// 普通图片
imageView.setImageBitmap(resource);
}
}
}
});
}
/**
* 加载相册目录
*
* @param context 上下文
* @param url 图片路径
* @param imageView 承载图片ImageView
*/
@Override
public void loadFolderImage(@NonNull Context context, @NonNull String url, @NonNull ImageView imageView) {
Glide.with(context)
.asBitmap()
.load(url)
.override(180, 180)
.centerCrop()
.sizeMultiplier(0.5f)
.apply(new RequestOptions().placeholder(R.drawable.picture_image_placeholder))
.into(new BitmapImageViewTarget(imageView) {
@Override
protected void setResource(Bitmap resource) {
RoundedBitmapDrawable circularBitmapDrawable =
RoundedBitmapDrawableFactory.
create(context.getResources(), resource);
circularBitmapDrawable.setCornerRadius(8);
imageView.setImageDrawable(circularBitmapDrawable);
}
});
}
/**
* 加载gif
*
* @param context 上下文
* @param url 图片路径
* @param imageView 承载图片ImageView
*/
@Override
public void loadAsGifImage(@NonNull Context context, @NonNull String url,
@NonNull ImageView imageView) {
Glide.with(context)
.asGif()
.load(url)
.into(imageView);
}
/**
* 加载图片列表图片
*
* @param context 上下文
* @param url 图片路径
* @param imageView 承载图片ImageView
*/
@Override
public void loadGridImage(@NonNull Context context, @NonNull String url, @NonNull ImageView imageView) {
Glide.with(context)
.load(url)
.override(200, 200)
.centerCrop()
.apply(new RequestOptions().placeholder(R.drawable.picture_image_placeholder))
.into(imageView);
}
private GlideEngine() {
}
private static GlideEngine instance;
public static GlideEngine createGlideEngine() {
if (null == instance) {
synchronized (GlideEngine.class) {
if (null == instance) {
instance = new GlideEngine();
}
}
}
return instance;
}
}
??activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity"
android:orientation="vertical">
<ImageView
android:id="@+id/mPictre"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<WebView
android:id="@+id/webview"
android:layout_width="match_parent"
android:layout_height="match_parent">
</WebView>
</LinearLayout>
??camera_pop_menu.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="fill_parent"
android:orientation="vertical" >
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_margin="10dp"
android:orientation="vertical" >
<Button
android:text="拍照"
android:id="@+id/btn_camera_pop_camera"
android:layout_width="match_parent"
android:layout_height="45dp"
android:textSize="18sp" />
<Button
android:text="选相册"
android:id="@+id/btn_camera_pop_album"
android:layout_width="match_parent"
android:layout_height="45dp"
android:textSize="18sp" />
<Button
android:text="选文件"
android:id="@+id/btn_camera_pop_cancel"
android:layout_width="match_parent"
android:layout_height="45dp"
android:layout_marginTop="10dp"
android:textSize="18sp" />
</LinearLayout>
</RelativeLayout>
??AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="com.example.app7">
<!-- 联网权限开启 -->
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission
android:name="android.permission.WRITE_MEDIA_STORAGE"
tools:ignore="ProtectedPermissions" />
<uses-permission
android:name="android.permission.WRITE_SETTINGS"
tools:ignore="ProtectedPermissions" />
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.VIBRATE" />
<uses-permission android:name="android.permission.BLUETOOTH" />
<queries package="${applicationId}">
<intent>
<action android:name="android.media.action.IMAGE_CAPTURE"></action>
</intent>
<intent>
<action android:name="android.media.action.ACTION_VIDEO_CAPTURE"></action>
</intent>
</queries>
<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/Theme.WebViewClient"
android:usesCleartextTraffic="true">
<activity
android:name=".MainActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
除了上面的之外,PictureSelector还有很多方法,例如下面的部分
更多详情,请看使用教程
https://github.com/LuckSiege/PictureSelector
应用基础知识
您可以使用 Kotlin、Java 和 C++ 语言编写 Android 应用。Android SDK 工具会将您的代码连同任何数据和资源文件编译成一个 APK(Android 软件包),即带有 .apk 后缀的归档文件。一个 APK 文件包含 Android 应用的所有内容,它也是 Android 设备用来安装应用的文件。
每个 Android 应用都处于各自的安全沙盒中,并受以下 Android 安全功能的保护:
??Android 操作系统是一种多用户 Linux 系统,其中的每个应用都是一个不同的用户;
??默认情况下,系统会为每个应用分配一个唯一的 Linux 用户 ID(该 ID 仅由系统使用,应用并不知晓)。系统会为应用中的所有文件设置权限,使得只有分配给该应用的用户 ID 才能访问这些文件;
??每个进程都拥有自己的虚拟机 (VM),因此应用代码独立于其他应用而运行。
??默认情况下,每个应用都在其自己的 Linux 进程内运行。Android 系统会在需要执行任何应用组件时启动该进程,然后当不再需要该进程或系统必须为其他应用恢复内存时,其便会关闭该进程。
Android 系统实现了最小权限原则。换言之,默认情况下,每个应用只能访问执行其工作所需的组件,而不能访问其他组件。这样便能创建非常安全的环境,在此环境中,应用无法访问其未获得权限的系统部分。不过,应用仍可通过一些途径与其他应用共享数据以及访问系统服务:
??可以安排两个应用共享同一 Linux 用户 ID,在此情况下,二者便能访问彼此的文件。为节省系统资源,也可安排拥有相同用户 ID 的应用在同一 Linux 进程中运行,并共享同一 VM。应用还必须使用相同的证书进行签名。
??应用可以请求访问设备数据(如用户的联系人、短信消息、可装载存储装置(SD 卡)、相机、蓝牙等)的权限。用户必须明确授予这些权限。如需了解详细信息,请参阅使用系统权限。
本文档的其余部分将介绍以下概念:
??用于定义应用的核心框架组件
??用来声明组件和应用必需设备功能的清单文件。
??与应用代码分离并允许应用针对各种设备配置适当优化其行为的资源。
应用组件
应用组件是 Android 应用的基本构建块。每个组件都是一个入口点,系统或用户可通过该入口点进入您的应用。有些组件会依赖于其他组件。
共有四种不同的应用组件类型:
??Activity
??服务
??广播接收器
??内容提供程序
每种类型都有不同的用途和生命周期,后者会定义如何创建和销毁组件。以下部分将介绍应用组件的四种类型。
Activity
Activity 是与用户交互的入口点。它表示拥有界面的单个屏幕。例如,电子邮件应用可能有一个显示新电子邮件列表的 Activity、一个用于撰写电子邮件的 Activity 以及一个用于阅读电子邮件的 Activity。尽管这些 Activity 通过协作在电子邮件应用中形成一种紧密结合的用户体验,但每个 Activity 都独立于其他 Activity 而存在。因此,其他应用可以启动其中任何一个 Activity(如果电子邮件应用允许)。例如,相机应用可以启动电子邮件应用内用于撰写新电子邮件的 Activity,以便用户共享图片。Activity 有助于完成系统和应用程序之间的以下重要交互:
??追踪用户当前关心的内容(屏幕上显示的内容),以确保系统继续运行托管 Activity 的进程。
??了解先前使用的进程包含用户可能返回的内容(已停止的 Activity),从而更优先保留这些进程。
??帮助应用处理终止其进程的情况,以便用户可以返回已恢复其先前状态的 Activity。
??提供一种途径,让应用实现彼此之间的用户流,并让系统协调这些用户流。(此处最经典的示例是共享。)
您需将 Activity 作为 Activity 类的子类来实现。如需了解有关 Activity 类的更多信息,请参阅 Activity 开发者指南。
服务
服务是一个通用入口点,用于因各种原因使应用在后台保持运行状态。它是一种在后台运行的组件,用于执行长时间运行的操作或为远程进程执行作业。服务不提供界面。例如,当用户使用其他应用时,服务可能会在后台播放音乐或通过网络获取数据,但这不会阻断用户与 Activity 的交互。诸如 Activity 等其他组件可以启动服务,使该服务运行或绑定到该服务,以便与其进行交互。事实上,有两种截然不同的语义服务可以告知系统如何管理应用:已启动服务会告知系统使其运行至工作完毕。此类工作可以是在后台同步一些数据,或者在用户离开应用后继续播放音乐。在后台同步数据或播放音乐也代表了两种不同类型的已启动服务,而这些服务可以修改系统处理它们的方式:
??音乐播放是用户可直接感知的服务,因此,应用会向用户发送通知,表明其希望成为前台,从而告诉系统此消息;在此情况下,系统明白它应尽全力维持该服务进程运行,因为进程消失会令用户感到不快。
??通常,用户不会意识到常规后台服务正处于运行状态,因此系统可以更自由地管理其进程。如果系统需要使用 RAM 来处理用户更迫切关注的内容,则其可能允许终止服务(然后在稍后的某个时刻重启服务)。
绑定服务之所以能运行,原因是某些其他应用(或系统)已表示希望使用该服务。从根本上讲,这是为另一个进程提供 API 的服务。因此,系统会知晓这些进程之间存在依赖关系,所以如果进程 A 绑定到进程 B 中的服务,系统便知道自己需使进程 B(及其服务)为进程 A 保持运行状态。此外,如果进程 A 是用户关心的内容,系统随即也知道将进程 B 视为用户关心的内容。由于存在灵活性(无论好坏),服务已成为非常有用的构建块,并且可实现各种高级系统概念。动态壁纸、通知侦听器、屏幕保护程序、输入方法、无障碍功能服务以及众多其他核心系统功能均可构建为在其运行时由应用实现、系统绑定的服务。
您需将服务作为 Service 的子类来实现。如需了解有关 Service 类的更多信息,请参阅服务开发者指南。
注意:如果您的应用面向 Android 5.0(API 级别 21)或更高版本,请使用 JobScheduler 类来调度操作。JobScheduler 的优势在于,它能通过优化作业调度来降低功耗,以及使用 Doze API,从而达到省电目的。如需了解有关使用此类的更多信息,请参阅 JobScheduler 参考文档。
广播接收器
借助广播接收器组件,系统能够在常规用户流之外向应用传递事件,从而允许应用响应系统范围内的广播通知。由于广播接收器是另一个明确定义的应用入口,因此系统甚至可以向当前未运行的应用传递广播。例如,应用可通过调度提醒来发布通知,以告知用户即将发生的事件。而且,通过将该提醒传递给应用的广播接收器,应用在提醒响起之前即无需继续运行。许多广播均由系统发起,例如,通知屏幕已关闭、电池电量不足或已拍摄照片的广播。应用也可发起广播,例如,通知其他应用某些数据已下载至设备,并且可供其使用。尽管广播接收器不会显示界面,但其可以创建状态栏通知,在发生广播事件时提醒用户。但广播接收器更常见的用途只是作为通向其他组件的通道,旨在执行极少量的工作。例如,它可能会根据带 JobScheduler 的事件调度 JobService 来执行某项工作。
广播接收器作为 BroadcastReceiver 的子类实现,并且每条广播都作为 Intent 对象进行传递。如需了解详细信息,请参阅 BroadcastReceiver 类。
内容提供程序
内容提供程序管理一组共享的应用数据,您可以将这些数据存储在文件系统、SQLite 数据库、网络中或者您的应用可访问的任何其他持久化存储位置。其他应用可通过内容提供程序查询或修改数据(如果内容提供程序允许)。例如,Android 系统可提供管理用户联系人信息的内容提供程序。因此,任何拥有适当权限的应用均可查询内容提供程序(如 ContactsContract.Data),以读取和写入特定人员的相关信息。我们很容易将内容提供程序看作数据库上的抽象,因为其内置的大量 API 和支持时常适用于这一情况。但从系统设计的角度看,二者的核心目的不同。对系统而言,内容提供程序是应用的入口点,用于发布由 URI 架构识别的已命名数据项。因此,应用可以决定如何将其包含的数据映射到 URI 命名空间,进而将这些 URI 分发给其他实体。反之,这些实体也可使用分发的 URI 来访问数据。在管理应用的过程中,系统可以执行以下特殊操作:
??分配 URI 无需应用保持运行状态,因此 URI 可在其所属的应用退出后继续保留。当系统必须从相应的 URI 检索应用数据时,系统只需确保所属应用仍处于运行状态。
??这些 URI 还会提供重要的细粒度安全模型。例如,应用可将其所拥有图像的 URI 放到剪贴板上,但将其内容提供程序锁定,以便其他应用程序无法随意访问它。当第二个应用尝试访问剪贴板上的 URI 时,系统可允许该应用通过临时的 URI 授权来访问数据,这样便只能访问 URI 后面的数据,而非第二个应用中的其他任何内容。
内容提供程序也适用于读取和写入您的应用不共享的私有数据。
内容提供程序作为 ContentProvider 的子类实现,并且其必须实现一组标准 API,以便其他应用能够执行事务。如需了解详细信息,请参阅内容提供程序开发者指南。
Android 系统设计的独特之处在于,任何应用都可启动其他应用的组件。例如,当您想让用户使用设备相机拍摄照片时,另一个应用可能也可执行该操作,因而您的应用便可使用该应用,而非自行产生一个 Activity 来拍摄照片。您无需加入甚至链接到该相机应用的代码。只需启动拍摄照片的相机应用中的 Activity 即可。完成拍摄时,系统甚至会将照片返回您的应用,以便您使用。对用户而言,这就如同相机是您应用的一部分。
当系统启动某个组件时,它会启动该应用的进程(如果尚未运行),并实例化该组件所需的类。例如,如果您的应用启动相机应用中拍摄照片的 Activity,则该 Activity 会在属于相机应用的进程(而非您的应用进程)中运行。因此,与大多数其他系统上的应用不同,Android 应用并没有单个入口点(即没有 main() 函数)。
由于系统在单独的进程中运行每个应用,且其文件权限会限制对其他应用的访问,因此您的应用无法直接启动其他应用中的组件,但 Android 系统可以。如要启动其他应用中的组件,请向系统传递一条消息,说明启动特定组件的 Intent。系统随后便会为您启动该组件。
启动组件
在四种组件类型中,有三种(Activity、服务和广播接收器)均通过异步消息 Intent 进行启动。Intent 会在运行时对各个组件进行互相绑定。您可以将 Intent 视为从其他组件(无论该组件是属于您的应用还是其他应用)请求操作的信使。
您需使用 Intent 对象创建 Intent,该对象通过定义消息来启动特定组件(显式 Intent)或特定的组件类型(隐式 Intent)。
对于 Activity 和服务,Intent 会定义要执行的操作(例如,查看或发送某内容),并且可指定待操作数据的 URI,以及正在启动的组件可能需要了解的信息。例如,Intent 可能会传达对 Activity 的请求,以便显示图像或打开网页。在某些情况下,您可以通过启动 Activity 来接收结果,这样 Activity 还会返回 Intent 中的结果。例如,您可以发出一个 Intent,让用户选取某位联系人并将其返回给您。返回 Intent 包含指向所选联系人的 URI。
对于广播接收器,Intent 只会定义待广播的通知。例如,指示设备电池电量不足的广播只包含指示“电池电量不足”的已知操作字符串。
与 Activity、服务和广播接收器不同,内容提供程序并非由 Intent 启动。相反,它们会在成为 ContentResolver 的请求目标时启动。内容解析程序会通过内容提供程序处理所有直接事务,因此通过提供程序执行事务的组件便无需执行事务,而是改为在 ContentResolver 对象上调用方法。这会在内容提供程序与请求信息的组件之间留出一个抽象层(以确保安全)。
每种组件都有不同的启动方法:
??如要启动 Activity,您可以向 startActivity() 或 startActivityForResult() 传递 Intent(当您想让 Activity 返回结果时),或者为其安排新任务。
??在 Android 5.0(API 级别 21)及更高版本中,您可以使用 JobScheduler 类来调度操作。对于早期 Android 版本,您可以通过向 startService() 传递 Intent 来启动服务(或对执行中的服务下达新指令)。您也可通过向将 bindService() 传递 Intent 来绑定到该服务。
??您可以通过向 sendBroadcast()、sendOrderedBroadcast() 或 sendStickyBroadcast() 等方法传递 Intent 来发起广播。
??您可以通过在 ContentResolver 上调用 query(),对内容提供程序执行查询。
如需了解有关 Intent 用法的详细信息,请参阅 Intent 和 Intent 过滤器文档。以下文档将为您详细介绍如何启动特定组件:Activity、服务、BroadcastReceiver 和内容提供程序。
以上Android小知识复制于Android开发者文档,侵权请联系删除
Android开发者文档
在刷题之前先介绍一下牛客。Leetcode有的刷题牛客都有,除此之外牛客里面还有招聘(社招和校招)、一些上岸大厂的大佬的面试经验。
牛客是可以伴随一生的编程软件(完全免费),从学校到社会工作,时时刻刻你都可以用到,感兴趣的可以去注册试试可以伴随一生的刷题app
觉得有用的可以给个三连,关注一波!!!带你了解更多Android小知识