下面这张图展示了Android系统的主要组成部分:
可以很明显看出,Android系统架构由5部分组成,分别是:Linux Kernel、Android Runtime、Libraries、Application Framework、Applications。第二部分将详细介绍这5个部分。
现在我们拿起手术刀来剖析各个部分。其实这部分SDK文档已经帮我们做得很好了,我们要做的就是拿来主义,然后再加上自己理解。下面自底向上分析各层。
Android基于Linux 2.6提供核心系统服务,例如:安全、内存管理、进程管理、网络堆栈、驱动模型。Linux Kernel也作为硬件和软件之间的抽象层,它隐藏具体硬件细节而为上层提供统一的服务。
如果你学过计算机网络知道OSI/RM,就会知道分层的好处就是使用下层提供的服务而为上层提供统一的服务,屏蔽本层及以下层的差异,当本层及以下层发生了变化不会影响到上层。也就是说各层各司其职,各层提供固定的SAP(Service Access Point),专业点可以说是高内聚、低耦合。
如果你只是做应用开发,就不需要深入了解Linux Kernel层。
Android包含一个核心库的集合,提供大部分在Java编程语言核心类库中可用的功能。每一个Android应用程序是Dalvik虚拟机中的实例,运行在他们自己的进程中。Dalvik虚拟机设计成,在一个设备可以高效地运行多个虚拟机。Dalvik虚拟机可执行文件格式是.dex,dex格式是专为Dalvik设计的一种压缩格式,适合内存和处理器速度有限的系统。
大多数虚拟机包括JVM都是基于栈的,而Dalvik虚拟机则是基于寄存器的。两种架构各有优劣,一般而言,基于栈的机器需要更多指令,而基于寄存器的机器指令更大。dx 是一套工具,可以將 Java .class 转换成 .dex 格式。一个dex文件通常会有多个.class。由于dex有時必须进行最佳化,会使文件大小增加1-4倍,以ODEX结尾。
Dalvik虚拟机依赖于Linux 内核提供基本功能,如线程和底层内存管理。
Android包含一个C/C++库的集合,供Android系统的各个组件使用。这些功能通过Android的应用程序框架(application framework)暴露给开发者。下面列出一些核心库:
- 系统C库——标准C系统库(libc)的BSD衍生,调整为基于嵌入式Linux设备
- 媒体库——基于PacketVideo的OpenCORE。这些库支持播放和录制许多流行的音频和视频格式,以及静态图像文件,包括MPEG4、 H.264、 MP3、 AAC、 AMR、JPG、 PNG
- 界面管理——管理访问显示子系统和无缝组合多个应用程序的二维和三维图形层
- LibWebCore——新式的Web浏览器引擎,驱动Android 浏览器和内嵌的web视图
- SGL——基本的2D图形引擎
- 3D库——基于OpenGL ES 1.0 APIs的实现。库使用硬件3D加速或包含高度优化的3D软件光栅
- FreeType ——位图和矢量字体渲染
- SQLite ——所有应用程序都可以使用的强大而轻量级的关系数据库引擎
通过提供开放的开发平台,Android使开发者能够编制极其丰富和新颖的应用程序。开发者可以自由地利用设备硬件优势、访问位置信息、运行后台服务、设置闹钟、向状态栏添加通知等等,很多很多。
开发者可以完全使用核心应用程序所使用的框架APIs。应用程序的体系结构旨在简化组件的重用,任何应用程序都能发布他的功能且任何其他应用程序可以使用这些功能(需要服从框架执行的安全限制)。这一机制允许用户替换组件。
所有的应用程序其实是一组服务和系统,包括:
- 视图(View)——丰富的、可扩展的视图集合,可用于构建一个应用程序。包括包括列表、网格、文本框、按钮,甚至是内嵌的网页浏览器
- 内容提供者(Content Providers)——使应用程序能访问其他应用程序(如通讯录)的数据,或共享自己的数据
- 资源管理器(Resource Manager)——提供访问非代码资源,如本地化字符串、图形和布局文件
- 通知管理器(Notification Manager)——使所有的应用程序能够在状态栏显示自定义警告
- 活动管理器(Activity Manager)——管理应用程序生命周期,提供通用的导航回退功能
Android装配一个核心应用程序集合,包括电子邮件客户端、SMS程序、日历、地图、浏览器、联系人和其他设置。所有应用程序都是用Java编程语言写的。更加丰富的应用程序有待我们去开发!
从上面我们知道Android的架构是分层的,非常清晰,分工很明确。Android本身是一套软件堆叠(Software Stack),或称为「软件叠层架构」,叠层主要分成三层:操作系统、中间件、应用程序。从上面我们也看到了开源的力量,一个个熟悉的开源软件在这里贡献了自己的一份力量。
public void showBluetoothCheckDialog(final Context context) {
dialog = new AlertDialog.Builder(context).create();// 创建一个AlertDialog对象
viewReconnectBluetooth = ((Activity) context).getLayoutInflater()
.inflate(R.layout.dialog_confirm_bluetooth, null);// 自定义布局
messagetext = (TextView) viewReconnectBluetooth.findViewById(R.id.message);
dialog.setCancelable(false);// dialog弹出后会点击屏幕或物理返回键,dialog不消失
messagetext.setText("当前是蓝牙模式,但是蓝牙设备已断开连接,请选择操作。");
View reconnect = viewReconnectBluetooth.findViewById(R.id.reconnect);
reconnect.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent(context,
DeviceListActivity.class);
context.startActivity(intent);
dialog.dismiss();
}
});
View cancel = viewReconnectBluetooth.findViewById(R.id.cancel);
cancel.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// nothing to do
dialog.dismiss();
}
});
View shutdown = viewReconnectBluetooth.findViewById(R.id.shutdown);
shutdown.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View arg0) {
// Auto-generated method stub
Util.savePreferences(Constants.BLUE_TOOTH, 0, context);
bluetoothAdapter.disable();
app.setVersion(app.getVersion() + 1);
dialog.dismiss();
Toast.makeText(context, "蓝牙模式已关闭", Toast.LENGTH_SHORT).show();
}
});
dialog.setView(viewReconnectBluetooth, 0, 0, 0, 0);// 把自定义的布局设置到dialog中,注意,布局设置一定要在show之前。从第二个参数分别填充内容与边框之间左、上、右、下、的像素
// Service&Application弹出对话框或WindowManager添加view时,要设置window type为TYPE_SYSTEM_ALERT
dialog.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT);
dialog.show();// 一定要先show出来再设置dialog的参数,不然就不会改变dialog的大小了
}
MediaStore.Images.Media.getBitmap(ContentResolver cr,Uri url)方法可以从一个已知的图片Uri中获得图片的bitmap对象,其中ContentResolver通常可以通过在Activity中调用的getContentResolver()方法中获取。Uri地址就是类似 content://media/external/images/media/2 的地址,也就是Content Provider定义的地址形式。但是这种获取bitmap的方式在图片较大的时候并不可取。为什么呢?
从getBitmap的参数可以看出,我们没有传递所要得到的bitmap大小信息,那么getBitmap的返回的bitmap究竟有多大我们就无从知晓,完全取决于getBitmap本身,最乐观的结果是getBitmap能智能的判断我们的需求,返回一个合适的bitmap,但是可能性很小。到底getBitmap中做了什么还是直接去看他的实现源码吧:
android.provider.MediaStore中找到Images.Media的getBitmap方法:
public static final Bitmap getBitmap(ContentResolver cr, Uri url)
throws FileNotFoundException, IOException {
InputStream input = cr.openInputStream(url);
Bitmap bitmap = BitmapFactory.decodeStream(input);
input.close();
return bitmap;
}
其实它很简单很粗暴,返回的是原始大小的bitmap。
MediaStore.Images.Media自身的getBitmap不是很可靠。那我们来自定义一个getBitmap吧:
取名叫getThumbnail,他能返回一个指定大小的bitmap:
public static Bitmap getThumbnail(Uri uri,int size) throws FileNotFoundException, IOException{
InputStream input = this.getContentResolver().openInputStream(uri);
BitmapFactory.Options onlyBoundsOptions = new BitmapFactory.Options();
onlyBoundsOptions.inJustDecodeBounds = true;
onlyBoundsOptions.inDither=true;//optional
onlyBoundsOptions.inPreferredConfig=Bitmap.Config.ARGB_8888;//optional
BitmapFactory.decodeStream(input, null, onlyBoundsOptions);
input.close();
if ((onlyBoundsOptions.outWidth == -1) || (onlyBoundsOptions.outHeight == -1))
return null;
int originalSize = (onlyBoundsOptions.outHeight > onlyBoundsOptions.outWidth) ? onlyBoundsOptions.outHeight : onlyBoundsOptions.outWidth;
double ratio = (originalSize > size) ? (originalSize / size) : 1.0;
BitmapFactory.Options bitmapOptions = new BitmapFactory.Options();
bitmapOptions.inSampleSize = getPowerOfTwoForSampleRatio(ratio);
bitmapOptions.inDither=true;//optional
bitmapOptions.inPreferredConfig=Bitmap.Config.ARGB_8888;//optional
input = this.getContentResolver().openInputStream(uri);
Bitmap bitmap = BitmapFactory.decodeStream(input, null, bitmapOptions);
input.close();
return bitmap;
}
private static int getPowerOfTwoForSampleRatio(double ratio){
int k = Integer.highestOneBit((int)Math.floor(ratio));
if(k==0) return 1;
else return k;
}
private String getPath(Uri uri) {
String[] projection = {MediaStore.Video.Media.DATA};
Cursor cursor = managedQuery(uri, projection, null, null, null);
int column_index = cursor
.getColumnIndexOrThrow(MediaStore.Audio.Media.DATA);
cursor.moveToFirst();
return cursor.getString(column_index);
}
Uri uri = Uri.parse(path);
String path = file.getPath()
File file = new File(path)
/** * 将图片Bitmap保存到本地返回File * @param bmp */
public File saveBmpToFile(Bitmap bmp) {
// 将图片保存到本地时进行压缩开始时间
Log.v("TakePhoteActivity.this","将图片保存到本地时进行压缩开始时间"+getTime());
ByteArrayOutputStream baos = new ByteArrayOutputStream();
int options = 100;
bmp.compress(Bitmap.CompressFormat.JPEG, options, baos);
// 系统时间
long dateTaken = System.currentTimeMillis();
SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd HH.mm.ss.SSS");
String formatStr =formatter.format(dateTaken);
// 图像名称
String filename = formatStr + ".JPEG";
File parent =new File(PATH);
if (!parent.exists()) {
parent.mkdirs();
}
File file = new File(PATH, filename);
if(file.exists()){
file.delete();
}
try {
FileOutputStream fos = new FileOutputStream(file);
fos.write(baos.toByteArray());
fos.flush();
fos.close();
} catch (Exception e) {
e.printStackTrace();
}
return file;
}
/** * 存储图像并将信息添加入媒体数据库 */
private Uri insertImage(ContentResolver cr, String name, long dateTaken,
String directory, String filename, Bitmap source, byte[] jpegData) {
OutputStream outputStream = null;
String filePath = directory + filename;
try {
File dir = new File(directory);
if (!dir.exists()) {
dir.mkdirs();
}
File file = new File(directory, filename);
if (file.createNewFile()) {
outputStream = new FileOutputStream(file);
if (source != null) {
source.compress(Bitmap.CompressFormat.JPEG, 100, outputStream);
} else {
outputStream.write(jpegData);
}
}
} catch (FileNotFoundException e) {
Log.e(TAG, e.getMessage());
return null;
} catch (IOException e) {
Log.e(TAG, e.getMessage());
return null;
} finally {
if (outputStream != null) {
try {
outputStream.close();
} catch (Throwable t) {
}
}
}
try {
ContentValues values = new ContentValues(7);
values.put(MediaStore.Images.Media.TITLE, name);
values.put(MediaStore.Images.Media.DISPLAY_NAME, filename);
values.put(MediaStore.Images.Media.DATE_TAKEN, dateTaken);
values.put(MediaStore.Images.Media.MIME_TYPE, "image/JPEG");
values.put(MediaStore.Images.Media.DATA, filePath);
return cr.insert(IMAGE_URI, values);
} catch (Exception e) {
/** * @Description: 用户禁用了下载服务时,通过如下代码进入“启用/禁用”下载管理程序界面 * @Author: BG235144/AMOSCXY * @Data 2017/3/2 15:34 */
String packageName = "com.android.providers.downloads";
Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
intent.setData(Uri.parse("package:" + packageName));
startActivity(intent);
Toast.makeText(TakePhoteActivity.this, "请开启下载服务!", Toast.LENGTH_SHORT).show();
ContentValues values = new ContentValues(7);
values.put(MediaStore.Images.Media.TITLE, name);
values.put(MediaStore.Images.Media.DISPLAY_NAME, filename);
values.put(MediaStore.Images.Media.DATE_TAKEN, dateTaken);
values.put(MediaStore.Images.Media.MIME_TYPE, "image/JPEG");
values.put(MediaStore.Images.Media.DATA, filePath);
return cr.insert(IMAGE_URI, values);
}
}
// 图片存储路径
public static final String PATH = Environment.getExternalStorageDirectory()
.toString() + "/AndroidMedia/";
// 系统时间
long dateTaken = System.currentTimeMillis();
SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd HH.mm.ss.SSS");
String formatStr = formatter.format(dateTaken);
// 图像名称
String filename = formatStr + ".JPEG";
byte[] jpegData = Bitmap2Bytes(bitmap);
// 保存图片
Uri uri = insertImage(getContentResolver(), filename, dateTaken, PATH, bitmap, jpegData);
/** * Bitmap → byte[] * @param bm * @return */
public byte[] Bitmap2Bytes(Bitmap bm) {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
bm.compress(Bitmap.CompressFormat.PNG, 100, baos);
return baos.toByteArray();
}
Bitmap bt = BitmapFactory.decodeFile(file.getPath());//图片地址
Bitmap mBitmap = MediaStore.Images.Media.getBitmap(this.getContentResolver(), uri);
Uri uri = Uri.parse(MediaStore.Images.Media.insertImage(getContentResolver(), bitmap, null,null));
private Bitmap zoomImage(Bitmap bm,int newWidth,int newHeight){
// 获得图片的宽高
int width = bm.getWidth()
int height = bm.getHeight();
// 计算缩放比例
float scaleWidth = ((float) newWidth) / width;
float scaleHeight = ((float) newHeight) / height;
// 取得想要缩放的matrix参数
Matrix matrix = new Matrix();
matrix.postScale(scaleWidth, scaleHeight);
// 得到新的图片
Bitmap newbm = Bitmap.createBitmap(bm, 0, 0, width, height, matrix,
true);
return newbm;
}
private Bitmap comp(Bitmap image) {
Matrix matrix = new Matrix();
Bitmap bitmap = image;
while (bitmap.getWidth() > 512 || bitmap.getHeight() > 1024) {
matrix.setScale(0.9f, 0.9f);
bitmap = Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, true);
}
return bitmap;
}
/** * 将图片保存到本地时进行压缩 * * @param bmp */
public Bitmap compressBmpToFile(Bitmap bmp) {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
int options = 60;
bmp.compress(Bitmap.CompressFormat.JPEG, options, baos);
// 系统时间
long dateTaken = System.currentTimeMillis();
SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd HH.mm.ss.SSS");
String formatStr = formatter.format(dateTaken);
// 图像名称
String filename = formatStr + ".JPEG";
File parent = new File(PATH);
if (!parent.exists()) {
parent.mkdirs();
}
File file = new File(PATH, filename);
if (file.exists()) {
file.delete();
}
try {
FileOutputStream fos = new FileOutputStream(file);
fos.write(baos.toByteArray());
fos.flush();
fos.close();
} catch (Exception e) {
e.printStackTrace();
}
String filePath = PATH + filename;
Bitmap scanBitmap = BitmapFactory.decodeFile(filePath);
return scanBitmap;
}
/** * 将图片保存到本地时进行压缩 * @param bmp */
public Bitmap compressBmpToFile(Bitmap bmp){
ByteArrayOutputStream baos = new ByteArrayOutputStream();
int options = 80;//个人喜欢从80开始,
bmp.compress(Bitmap.CompressFormat.JPEG, options, baos);
while (baos.toByteArray().length / 1024 > 50) {
baos.reset();
options -= 5;
bmp.compress(Bitmap.CompressFormat.JPEG, options, baos);
}
// 系统时间
long dateTaken = System.currentTimeMillis();
// 图像名称
String filename = DateFormat.format("yyyy-MM-dd kk.mm.ss", dateTaken)
.toString() + ".jpg";
File file = new File(PATH, filename);
try {
FileOutputStream fos = new FileOutputStream(file);
fos.write(baos.toByteArray());
fos.flush();
fos.close();
} catch (Exception e) {
e.printStackTrace();
}
String filePath=PATH + filename;
Bitmap bitmap=BitmapFactory.decodeFile(filePath);
return bitmap;
}
Curzibn/Luban
private byte[] Bitmap2Bytes(Bitmap bm){
ByteArrayOutputStream baos = new ByteArrayOutputStream();
bm.compress(Bitmap.CompressFormat.PNG, 100, baos);
return baos.toByteArray();
}
private Bitmap Bytes2Bimap(byte[] b){
if(b.length!=0){
return BitmapFactory.decodeByteArray(b, 0, b.length);
}else {
return null;
}
}
Resources res=getResources();
Bitmap bmp=BitmapFactory.decodeResource(res, R.drawable.pic);
public static final int CHOOSE_PHOTO = 2;
Button chooseFromAlbum.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (ContextCompat.checkSelfPermission(MainActivity.this, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(MainActivity.this, new String[]{ Manifest.permission. WRITE_EXTERNAL_STORAGE }, 1);
} else {
openAlbum();
}
}
});
private void openAlbum() {
Intent intent = new Intent("android.intent.action.GET_CONTENT");
intent.setType("image/*");
startActivityForResult(intent, CHOOSE_PHOTO); // 打开相册
}
@Override
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
switch (requestCode) {
case 1:
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
openAlbum();
} else {
Toast.makeText(this, "权限被拒绝", Toast.LENGTH_SHORT).show();
}
break;
default:
}
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
switch (requestCode) {
case CHOOSE_PHOTO:
if (resultCode == RESULT_OK) {
// 判断手机系统版本号
if (Build.VERSION.SDK_INT >= 19) {
// 4.4及以上系统使用这个方法处理图片
handleImageOnKitKat(data);
} else {
// 4.4以下系统使用这个方法处理图片
handleImageBeforeKitKat(data);
}
}
break;
default:
break;
}
}
@TargetApi(19)
private void handleImageOnKitKat(Intent data) {
String imagePath = null;
Uri uri = data.getData();
Log.d("TAG", "handleImageOnKitKat: uri is " + uri);
if (DocumentsContract.isDocumentUri(this, uri)) {
// 如果是document类型的Uri,则通过document id处理
String docId = DocumentsContract.getDocumentId(uri);
if("com.android.providers.media.documents".equals(uri.getAuthority())) {
String id = docId.split(":")[1]; // 解析出数字格式的id
String selection = MediaStore.Images.Media._ID + "=" + id;
imagePath = getImagePath(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, selection);
} else if ("com.android.providers.downloads.documents".equals(uri.getAuthority())) {
Uri contentUri = ContentUris.withAppendedId(Uri.parse("content://downloads/public_downloads"), Long.valueOf(docId));
imagePath = getImagePath(contentUri, null);
}
} else if ("content".equalsIgnoreCase(uri.getScheme())) {
// 如果是content类型的Uri,则使用普通方式处理
imagePath = getImagePath(uri, null);
} else if ("file".equalsIgnoreCase(uri.getScheme())) {
// 如果是file类型的Uri,直接获取图片路径即可
imagePath = uri.getPath();
}
displayImage(imagePath); // 根据图片路径显示图片
}
private void handleImageBeforeKitKat(Intent data) {
Uri uri = data.getData();
String imagePath = getImagePath(uri, null);
displayImage(imagePath);
}
private String getImagePath(Uri uri, String selection) {
String path = null;
// 通过Uri和selection来获取真实的图片路径
Cursor cursor = getContentResolver().query(uri, null, selection, null, null);
if (cursor != null) {
if (cursor.moveToFirst()) {
path = cursor.getString(cursor.getColumnIndex(MediaStore.Images.Media.DATA));
}
cursor.close();
}
return path;
}
private void displayImage(String imagePath) {
if (imagePath != null) {
Bitmap bitmap = BitmapFactory.decodeFile(imagePath);
ImageView picture.setImageBitmap(bitmap);
} else {
Toast.makeText(this, "获取图片失败", Toast.LENGTH_SHORT).show();
}
}
/** * @Class: NotificationsUtils * @Description: * @author: BG235144/AMOSCXY * @Date: 2017/3/13 */
public class NotificationsUtils {
private static final String CHECK_OP_NO_THROW = "checkOpNoThrow";
private static final String OP_POST_NOTIFICATION = "OP_POST_NOTIFICATION";
@SuppressLint("NewApi")
public static boolean isNotificationEnabled(Context context) {
AppOpsManager mAppOps = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE);
ApplicationInfo appInfo = context.getApplicationInfo();
String pkg = context.getApplicationContext().getPackageName();
int uid = appInfo.uid;
Class appOpsClass = null;
/* Context.APP_OPS_MANAGER */
try {
appOpsClass = Class.forName(AppOpsManager.class.getName());
Method checkOpNoThrowMethod = appOpsClass.getMethod(CHECK_OP_NO_THROW, Integer.TYPE, Integer.TYPE,
String.class);
Field opPostNotificationValue = appOpsClass.getDeclaredField(OP_POST_NOTIFICATION);
int value = (Integer) opPostNotificationValue.get(Integer.class);
return ((Integer) checkOpNoThrowMethod.invoke(mAppOps, value, uid, pkg) == AppOpsManager.MODE_ALLOWED);
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (NoSuchFieldException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
return false;
}
}
注意:Build.VERSION.SDK_INT>=19时才能使用
Intent intent = new Intent("android.settings.APPLICATION_DETAILS_SETTINGS");
String pkg = "com.android.settings";
String cls = "com.android.settings.applications.InstalledAppDetails";
intent.setComponent(new ComponentName(pkg, cls));
intent.setData(Uri.parse("package:" + mContext.getPackageName()));
startActivity(intent);
权限组名 | 权限名 |
---|---|
CALENDAR | READ_CALENDAR 、WRITE_CALENDAR |
CAMERA | CAMERA |
CONTACTS | READ_CONTACTS 、WRITE_CONTACTS、 GET_ACCOUNTS |
LOCATION | ACCESS_FINE_LOCATION、ACCESS_COARSE_LOCATION |
MICROPHONE | RECORD_AUDIO |
PHONE | READ_PHONE_STATE、CALL_PHONE、READ_CALL_LOG、WRITE_CALL_LOG、ADD_VOICEMAIL、USE_SIP、PROCESS_OUTGOING_CALLS |
SENSOPS | BODY_SENSORS |
SMS | SEND_SMS、RECEIVE_SMS、READ_SMS、RECEIVE_WAP_PUSH、RECEIVE_SMS |
STORAGE | READ_EXTERNAL_STORAGE、WRITE_EXTERNAL_STORAGE |
if (ContextCompat.checkSelfPermission(MainActivity.this, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(MainActivity.this, new String[]{ Manifest.permission. WRITE_EXTERNAL_STORAGE }, 1);
} else {
// 获取权限后要执行的事件
openAlbum();
}
// 检测到还没有获取到权限时执行,申请获取权限
@Override
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
switch (requestCode) {
case 1:
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
// 获取权限后要执行的事件
openAlbum();
} else {
Toast.makeText(this, "权限被拒绝", Toast.LENGTH_SHORT).show();
}
break;
default:
}
}
String callingApp = context.getPackageManager().getNameForUid(Binder.getCallingUid());
PackageManager pm = this.getPackageManager();
String appName = getApplicationInfo().loadLabel(pm).toString();
private String getAppInfo() {
try {
String pkName = this.getPackageName();
String versionName = this.getPackageManager().getPackageInfo(
pkName, 0).versionName;
int versionCode = this.getPackageManager()
.getPackageInfo(pkName, 0).versionCode;
return pkName + " " + versionName + " " + versionCode;
} catch (Exception e) {
}
return null;
}
String systemVersion = android.os.Build.VERSION.RELEASE;
就是Activity的一个容器集合,所有的Activity实例都是存放在这个容器栈里面。其实就是数据结构中栈是一个原理:先进后出,后进先出,所有操作都是发生在栈顶。
launchMode在多个Activity跳转的过程中扮演着重要的角色,它可以决定是否生成新的Activity实例,是否重用已存在的Activity实例,是否和其他Activity实例公用一个task里
android:launchMode="standard"
默认启动模式,每次激活Activity时都会创建Activity,并放入任务栈中。
如果在任务的栈顶正好存在该Activity的实例, 就重用该实例,否者就会创建新的实例并放入栈顶(即使栈中已经存在该Activity实例,只要不在栈顶,都会创建实例)。
如果在栈中已经有该Activity的实例,就重用该实例(会调用实例的onNewIntent())。重用时,会让该实例回到栈顶,因此在它上面的实例将会被移除栈。如果栈中不存在该实例,将会创建新的实例放入栈中。
在一个新栈中创建该Activity实例,并让多个应用共享改栈中的该Activity实例。一旦改模式的Activity的实例存在于某个栈中,任何应用再激活改Activity时都会重用该栈中的实例,其效果相当于多个应用程序共享一个应用,不管谁激活该Activity都会进入同一个应用中。
大家遇到一个应用的Activity供多种方式调用启动的情况,多个调用希望只有一个Activity的实例存在,这就需要Activity的onNewIntent(Intent intent)方法了。只要在Activity中加入自己的onNewIntent(intent)的实现加上Manifest中对Activity设置lanuchMode=“singleTask”就可以。
onNewIntent()非常好用,Activity第一启动的时候执行onCreate()—->onStart()—->onResume()等后续生命周期函数,也就时说第一次启动Activity并不会执行到onNewIntent(). 而后面如果再有想启动Activity的时候,那就是执行onNewIntent()—->onResart()——>onStart()—–>onResume(). 如果android系统由于内存不足把已存在Activity释放掉了,那么再次调用的时候会重新启动Activity即执行onCreate()—->onStart()—->onResume()等。
当调用到onNewIntent(intent)的时候,需要在onNewIntent() 中使用setIntent(intent)赋值给Activity的Intent.否则,后续的getIntent()都是得到老的Intent。
设备无关像素,不同设备有不同的显示效果,这个和设备硬件有关,一般我们为了支持WVGA、HVGA和QVGA 推荐使用这个,不依赖像素。
像素点
px = dp(densityDpi/160)
缩放系数,值为densityDpi/160
一英寸多少个像素点,一般称作像素密度
安卓规定,在160dpi屏幕上,1dp等于1px.这样在160dpi屏幕上指定的1dp宽度按钮,到了320dpi屏幕,宽度自动变成2px,保持了所占屏幕比例。
注意:该值对应于DisplayMetrics类中属性densityDpi的值。
指屏幕横纵方向上的像素个数
与密度和字体缩放度无关的像素,主要用于字体显示
/** * dp、sp 转换为 px 的工具类 * * @author fxsky 2012.11.12 * */
public class DisplayUtil {
/** * 将px值转换为dip或dp值,保证尺寸大小不变 * * @param pxValue * @param scale * (DisplayMetrics类中属性density) * @return */
public static int px2dip(Context context, float pxValue) {
final float scale = context.getResources().getDisplayMetrics().density;
return (int) (pxValue / scale + 0.5f);
}
/** * 将dip或dp值转换为px值,保证尺寸大小不变 * * @param dipValue * @param scale * (DisplayMetrics类中属性density) * @return */
public static int dip2px(Context context, float dipValue) {
final float scale = context.getResources().getDisplayMetrics().density;
return (int) (dipValue * scale + 0.5f);
}
/** * 将px值转换为sp值,保证文字大小不变 * * @param pxValue * @param fontScale * (DisplayMetrics类中属性scaledDensity) * @return */
public static int px2sp(Context context, float pxValue) {
final float fontScale = context.getResources().getDisplayMetrics().scaledDensity;
return (int) (pxValue / fontScale + 0.5f);
}
/** * 将sp值转换为px值,保证文字大小不变 * * @param spValue * @param fontScale * (DisplayMetrics类中属性scaledDensity) * @return */
public static int sp2px(Context context, float spValue) {
final float fontScale = context.getResources().getDisplayMetrics().scaledDensity;
return (int) (spValue * fontScale + 0.5f);
}
}
public Long getTime()
{
return System.currentTimeMillis();
}
public boolean getInterval(Long refreshTime,float i) {
if(System.currentTimeMillis()-refreshTime>=i*1000){
return true;
}
return false;
}
public class ScreenShotListenManager {
private static final String TAG = "ScreenShotListenManager";
/** 读取媒体数据库时需要读取的列 */
private static final String[] MEDIA_PROJECTIONS = {
MediaStore.Images.ImageColumns.DATA,
MediaStore.Images.ImageColumns.DATE_TAKEN,
};
/** 读取媒体数据库时需要读取的列, 其中 WIDTH 和 HEIGHT 字段在 API 16 以后才有 */
private static final String[] MEDIA_PROJECTIONS_API_16 = {
MediaStore.Images.ImageColumns.DATA,
MediaStore.Images.ImageColumns.DATE_TAKEN,
MediaStore.Images.ImageColumns.WIDTH,
MediaStore.Images.ImageColumns.HEIGHT,
};
/** 截屏依据中的路径判断关键字 */
private static final String[] KEYWORDS = {
"screenshot", "screen_shot", "screen-shot", "screen shot",
"screencapture", "screen_capture", "screen-capture", "screen capture",
"screencap", "screen_cap", "screen-cap", "screen cap"
};
private static Point sScreenRealSize;
/** 已回调过的路径 */
private final List<String> sHasCallbackPaths = new ArrayList<String>();
private Context mContext;
private OnScreenShotListener mListener;
private long mStartListenTime;
/** 运行在 UI 线程的 Handler, 用于运行监听器回调 */
private final Handler mUiHandler = new Handler(Looper.getMainLooper());
/** 内部存储器内容观察者 */
private MediaContentObserver mInternalObserver = new MediaContentObserver(MediaStore.Images.Media.INTERNAL_CONTENT_URI, mUiHandler);
/** 外部存储器内容观察者 */
private MediaContentObserver mExternalObserver;
private ScreenShotListenManager(Context context) {
if (context == null) {
throw new IllegalArgumentException("The context must not be null.");
}
mContext = context;
// 获取屏幕真实的分辨率
if (sScreenRealSize == null) {
sScreenRealSize = getRealScreenSize();
if (sScreenRealSize != null) {
Log.d(TAG, "Screen Real Size: " + sScreenRealSize.x + " * " + sScreenRealSize.y);
} else {
Log.w(TAG, "Get screen real size failed.");
}
}
}
public static ScreenShotListenManager newInstance(Context context) {
assertInMainThread();
return new ScreenShotListenManager(context);
}
/** * 启动监听 */
public void startListen() {
assertInMainThread();
sHasCallbackPaths.clear();
// 记录开始监听的时间戳
mStartListenTime = System.currentTimeMillis();
// 创建内容观察者
mInternalObserver = new MediaContentObserver(MediaStore.Images.Media.INTERNAL_CONTENT_URI, mUiHandler);
mExternalObserver = new MediaContentObserver(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, mUiHandler);
// 注册内容观察者
mContext.getContentResolver().registerContentObserver(
MediaStore.Images.Media.INTERNAL_CONTENT_URI,
false,
mInternalObserver
);
mContext.getContentResolver().registerContentObserver(
MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
false,
mExternalObserver
);
}
/** * 停止监听 */
public void stopListen() {
assertInMainThread();
// 注销内容观察者
if (mInternalObserver != null) {
try {
mContext.getContentResolver().unregisterContentObserver(mInternalObserver);
} catch (Exception e) {
e.printStackTrace();
}
mInternalObserver = null;
}
if (mExternalObserver != null) {
try {
mContext.getContentResolver().unregisterContentObserver(mExternalObserver);
} catch (Exception e) {
e.printStackTrace();
}
mExternalObserver = null;
}
// 清空数据
mStartListenTime = 0;
sHasCallbackPaths.clear();
}
/** * 处理媒体数据库的内容改变 */
private void handleMediaContentChange(Uri contentUri) {
Cursor cursor = null;
try {
// 数据改变时查询数据库中最后加入的一条数据
cursor = mContext.getContentResolver().query(
contentUri,
Build.VERSION.SDK_INT < 16 ? MEDIA_PROJECTIONS : MEDIA_PROJECTIONS_API_16,
null,
null,
MediaStore.Images.ImageColumns.DATE_ADDED + " desc limit 1"
);
if (cursor == null) {
Log.e(TAG, "Deviant logic.");
return;
}
if (!cursor.moveToFirst()) {
Log.d(TAG, "Cursor no data.");
return;
}
// 获取各列的索引
int dataIndex = cursor.getColumnIndex(MediaStore.Images.ImageColumns.DATA);
int dateTakenIndex = cursor.getColumnIndex(MediaStore.Images.ImageColumns.DATE_TAKEN);
int widthIndex = -1;
int heightIndex = -1;
if (Build.VERSION.SDK_INT >= 16) {
widthIndex = cursor.getColumnIndex(MediaStore.Images.ImageColumns.WIDTH);
heightIndex = cursor.getColumnIndex(MediaStore.Images.ImageColumns.HEIGHT);
}
// 获取行数据
String data = cursor.getString(dataIndex);
long dateTaken = cursor.getLong(dateTakenIndex);
int width = 0;
int height = 0;
if (widthIndex >= 0 && heightIndex >= 0) {
width = cursor.getInt(widthIndex);
height = cursor.getInt(heightIndex);
} else {
// API 16 之前, 宽高要手动获取
Point size = getImageSize(data);
width = size.x;
height = size.y;
}
// 处理获取到的第一行数据
handleMediaRowData(data, dateTaken, width, height);
} catch (Exception e) {
e.printStackTrace();
} finally {
if (cursor != null && !cursor.isClosed()) {
cursor.close();
}
}
}
private Point getImageSize(String imagePath) {
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeFile(imagePath, options);
return new Point(options.outWidth, options.outHeight);
}
/** * 处理获取到的一行数据 */
private void handleMediaRowData(String data, long dateTaken, int width, int height) {
if (checkScreenShot(data, dateTaken, width, height)) {
Log.d(TAG, "ScreenShot: path = " + data + "; size = " + width + " * " + height
+ "; date = " + dateTaken);
if (mListener != null && !checkCallback(data)) {
mListener.onShot(data);
}
} else {
// 如果在观察区间媒体数据库有数据改变,又不符合截屏规则,则输出到 log 待分析
Log.w(TAG, "Media content changed, but not screenshot: path = " + data
+ "; size = " + width + " * " + height + "; date = " + dateTaken);
}
}
/** * 判断指定的数据行是否符合截屏条件 */
private boolean checkScreenShot(String data, long dateTaken, int width, int height) {
/* * 判断依据一: 时间判断 */
// 如果加入数据库的时间在开始监听之前, 或者与当前时间相差大于10秒, 则认为当前没有截屏
if (dateTaken < mStartListenTime || (System.currentTimeMillis() - dateTaken) > 10 * 1000) {
return false;
}
/* * 判断依据二: 尺寸判断 */
if (sScreenRealSize != null) {
// 如果图片尺寸超出屏幕, 则认为当前没有截屏
if (
!(
(width <= sScreenRealSize.x && height <= sScreenRealSize.y)
||
(height <= sScreenRealSize.x && width <= sScreenRealSize.y)
)) {
return false;
}
}
/* * 判断依据三: 路径判断 */
if (TextUtils.isEmpty(data)) {
return false;
}
data = data.toLowerCase();
// 判断图片路径是否含有指定的关键字之一, 如果有, 则认为当前截屏了
for (String keyWork : KEYWORDS) {
if (data.contains(keyWork)) {
return true;
}
}
return false;
}
/** * 判断是否已回调过, 某些手机ROM截屏一次会发出多次内容改变的通知; <br/> * 删除一个图片也会发通知, 同时防止删除图片时误将上一张符合截屏规则的图片当做是当前截屏. */
private boolean checkCallback(String imagePath) {
if (sHasCallbackPaths.contains(imagePath)) {
return true;
}
// 大概缓存15~20条记录便可
if (sHasCallbackPaths.size() >= 20) {
for (int i = 0; i < 5; i++) {
sHasCallbackPaths.remove(0);
}
}
sHasCallbackPaths.add(imagePath);
return false;
}
/** * 获取屏幕分辨率 */
private Point getRealScreenSize() {
Point screenSize = null;
try {
screenSize = new Point();
WindowManager windowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);
Display defaultDisplay = windowManager.getDefaultDisplay();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
defaultDisplay.getRealSize(screenSize);
} else {
try {
Method mGetRawW = Display.class.getMethod("getRawWidth");
Method mGetRawH = Display.class.getMethod("getRawHeight");
screenSize.set(
(Integer) mGetRawW.invoke(defaultDisplay),
(Integer) mGetRawH.invoke(defaultDisplay)
);
} catch (Exception e) {
screenSize.set(defaultDisplay.getWidth(), defaultDisplay.getHeight());
e.printStackTrace();
}
}
} catch (Exception e) {
e.printStackTrace();
}
return screenSize;
}
/** * 设置截屏监听器 */
public void setListener(OnScreenShotListener listener) {
mListener = listener;
}
public static interface OnScreenShotListener {
public void onShot(String imagePath);
}
private static void assertInMainThread() {
if (Looper.myLooper() != Looper.getMainLooper()) {
StackTraceElement[] elements = Thread.currentThread().getStackTrace();
String methodMsg = null;
if (elements != null && elements.length >= 4) {
methodMsg = elements[3].toString();
}
throw new IllegalStateException("Call the method must be in main thread: " + methodMsg);
}
}
/** * 媒体内容观察者(观察媒体数据库的改变) */
private class MediaContentObserver extends ContentObserver {
private Uri mContentUri;
public MediaContentObserver(Uri contentUri, Handler handler) {
super(handler);
mContentUri = contentUri;
}
@Override
public void onChange(boolean selfChange) {
super.onChange(selfChange);
handleMediaContentChange(mContentUri);
}
}
}
package com.ziniu.mobile.module.androidocrcamera;
import android.app.AlertDialog;
import android.app.Notification;
import android.app.PendingIntent;
import android.app.Service;
import android.content.DialogInterface;
import android.content.Intent;
import android.graphics.BitmapFactory;
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
import android.support.v4.app.NotificationCompat;
import android.view.LayoutInflater;
import android.view.View;
import android.view.WindowManager;
import android.widget.RelativeLayout;
import android.widget.TextView;
import android.widget.Toast;
import com.ziniu.logistics.mobile.protocol.util.StringUtil;
import com.ziniu.mobile.module.R;
import com.ziniu.mobile.module.common.Util;
import com.ziniu.mobile.module.ui.BillChooseActivity;
import com.ziniu.mobile.module.ui.TakePhoteActivity;
public class OcrService extends Service {
private ScreenShotListenManager manager;
public OcrService() {
}
@Override
public IBinder onBind(Intent intent) {
// TODO: Return the communication channel to the service.
throw new UnsupportedOperationException("Not yet implemented");
}
@Override
public void onCreate() {
super.onCreate();
Intent intent = new Intent(OcrService.this, BillChooseActivity.class);
PendingIntent pi = PendingIntent.getActivity(OcrService.this, 0, intent, 0);
Notification notification = new NotificationCompat.Builder(OcrService.this)
.setContentTitle("物流来了")
.setContentText("截屏监听已开启")
.setWhen(System.currentTimeMillis())
.setSmallIcon(R.mipmap.ic_launcher)
.setLargeIcon(BitmapFactory.decodeResource(getResources(), R.mipmap.ic_launcher))
.setContentIntent(pi)
.build();
startForeground(1, notification);
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
if(manager==null){
manager = ScreenShotListenManager.newInstance(OcrService.this);
}
manager.startListen();
manager.setListener(
new ScreenShotListenManager.OnScreenShotListener() {
public void onShot(String imagePath) {
if(Util.isLogin(OcrService.this)||Util.isRlszLogin(OcrService.this)){
if(StringUtil.isEmpty(imagePath)){
Handler handler = new Handler(Looper.getMainLooper());
handler.post(new Runnable() {
public void run() {
Toast.makeText(OcrService.this, "没有找到截屏图片,请重试!", Toast.LENGTH_SHORT).show();
}
});
}else{
Intent intent = new Intent(OcrService.this,
TakePhoteActivity.class);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.putExtra("imagePath",imagePath);
OcrService.this.startActivity(intent);
}
}else{
Handler handler = new Handler(Looper.getMainLooper());
handler.post(new Runnable() {
public void run() {
Toast.makeText(OcrService.this, "请登录!", Toast.LENGTH_SHORT).show();
}
});
}
}
}
);
// START_NOT_STICKY:“非粘性的”。使用这个返回值时,如果在执行完onStartCommand后,服务被异常kill掉,系统不会自动重启服务该
return START_NOT_STICKY;
}
@Override
public void onDestroy() {
if(manager!=null){
manager.stopListen();
manager.setListener(null);
}
super.onDestroy();
}
}
Handler handler = new Handler(Looper.getMainLooper());
handler.post(new Runnable() { public void run() { Toast.makeText(BaseActivity.this, "请登录!", Toast.LENGTH_SHORT).show();
}
});
写好Alter功能块后,在alter.show()语句前加入:
alert.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT);
注:alter为AlertDialog类型对象
然后在AndroidManifest.xml中加入权限:
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"></uses-permission>
下面进行简单的解释:
如果只在Service中写入常在Activity中使用的创建Alter的代码,运行时是会发生错误的,因为Alter的显示需要依附于一个确定的Activity类。而以上做法就是声明我们要弹出的这个提示框是一个系统的提示框,即全局性质的提示框,所以只要手机处于开机状态,无论它现在处于何种界面之下,只要调用alter.show(),就会弹出提示框来。
/** * 但是当我们没在AndroidManifest.xml中设置其debug属性时: * 使用Eclipse运行这种方式打包时其debug属性为true,使用Eclipse导出这种方式打包时其debug属性为法false. * 在使用ant打包时,其值就取决于ant的打包参数是release还是debug. * 因此在AndroidMainifest.xml中最好不设置android:debuggable属性置,而是由打包方式来决定其值. * 跑码运行时isApkDebugable为true,只有release打包时isApkDebugable为false * @param context * @return * @author SHANHY * @date 2015-8-7 */
public static boolean isApkDebugable(Context context) {
try {
ApplicationInfo info= context.getApplicationInfo();
return (info.flags&ApplicationInfo.FLAG_DEBUGGABLE)!=0;
} catch (Exception e) {
}
return false;
}
SystemClock.sleep(1000);
try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); }
在部分Android手机(如MT788、Note2)上,使用Camera拍照以后,得到的照片会被自动旋转(90°、180°、270°),这个情况很不符合预期。仔细分析了一下,因为照片属性中是存储了旋转信息的,所以要解决这个问题,可以在onActivityResult方法中,获取到照片数据后,读取它的旋转信息,如果不是0,说明这个照片已经被旋转过了,那么再使用android.graphics.Matrix将照片旋转回去即可。
/** * 读取图片的旋转的角度 * * @param path * 图片绝对路径 * @return 图片的旋转角度 */
private int getBitmapDegree(String path) {
int degree = 0;
try {
// 从指定路径下读取图片,并获取其EXIF信息
ExifInterface exifInterface = new ExifInterface(path);
// 获取图片的旋转信息
int orientation = exifInterface.getAttributeInt(ExifInterface.TAG_ORIENTATION,
ExifInterface.ORIENTATION_NORMAL);
switch (orientation) {
case ExifInterface.ORIENTATION_ROTATE_90:
degree = 90;
break;
case ExifInterface.ORIENTATION_ROTATE_180:
degree = 180;
break;
case ExifInterface.ORIENTATION_ROTATE_270:
degree = 270;
break;
}
} catch (IOException e) {
e.printStackTrace();
}
return degree;
}
/** * 将图片按照某个角度进行旋转 * * @param bm * 需要旋转的图片 * @param degree * 旋转角度 * @return 旋转后的图片 */
public static Bitmap rotateBitmapByDegree(Bitmap bm, int degree) {
Bitmap returnBm = null;
// 根据旋转角度,生成旋转矩阵
Matrix matrix = new Matrix();
matrix.postRotate(degree);
try {
// 将原始图片按照旋转矩阵进行旋转,并得到新的图片
returnBm = Bitmap.createBitmap(bm, 0, 0, bm.getWidth(), bm.getHeight(), matrix, true);
} catch (OutOfMemoryError e) {
}
if (returnBm == null) {
returnBm = bm;
}
if (bm != returnBm) {
bm.recycle();
}
return returnBm;
}
/* * 目前Android SDK定义的Tag有: * TAG_APERTURE 光圈值 * TAG_DATETIME 拍摄时间(取决于设备设置的时间) * TAG_EXPOSURE_TIME 曝光时间 * TAG_FLASH 闪光灯 * TAG_FOCAL_LENGTH 焦距 * TAG_GPS_LATITUDE 纬度 * TAG_GPS_LATITUDE_REF 纬度参考 * TAG_GPS_LONGITUDE 经度 * TAG_GPS_LONGITUDE_REF 经度参考 * TAG_IMAGE_LENGTH 图片长 * TAG_IMAGE_WIDTH 图片宽 * TAG_ISO ISO * TAG_MAKE 设备制造商 * TAG_MODEL 设备型号 * TAG_ORIENTATION 方向 * TAG_WHITE_BALANCE 白平衡 */
try {
//android读取图片EXIF信息,path为图片的绝对路径,ExifInterface专门用来读写JPEG图片文件Exif信息的类
ExifInterface exifInterface=new ExifInterface(path);
String smodel=exifInterface.getAttribute(ExifInterface.TAG_MODEL);
String width=exifInterface.getAttribute(ExifInterface.TAG_IMAGE_WIDTH);
String height=exifInterface.getAttribute(ExifInterface.TAG_IMAGE_LENGTH);
Toast.makeText(MainActivity.this, smodel+" "+width+"*"+height, Toast.LENGTH_LONG).show();
} catch (Exception e) {
e.printStackTrace();
}
想继承自AppCompatActivity,那么根据提示来使用AppCompat的theme,即将AndroidManifest.xml文件中关于Activity的主题配置改成:
android:theme="@style/Theme.AppCompat.Light.NoActionBar"
完整代码:
<activity android:name=".RlszMainActivity" android:theme="@style/ZINIU_THEME">
</activity>
在用json进行远程同步数据时,json中的数据有特殊字符时,容易造成json解析不了。用对特殊字符进行转义,实现太麻烦(个人觉得)。于是想到了对属性值进行加密处理,而且加密后的数据不会有特殊字符。而base64非常适合(个人觉得base64的出现就是为了解决数据传输特殊字符的问题)。
/** * Base64.DEFAULT:当字符串过长(一般超过76)时会自动在中间加一个换行符 * Base64.NO_WRAP:这个参数意思是略去所有的换行符 */
public class Base64Utils {
// 加密
public static String getBase64(String str) {
String result = "";
if( str != null) {
try {
result = new String(Base64.encode(str.getBytes("utf-8"), Base64.NO_WRAP),"utf-8");
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
}
return result;
}
// 解密
public static String getFromBase64(String str) {
String result = "";
if (str != null) {
try {
result = new String(Base64.decode(str, Base64.NO_WRAP), "utf-8");
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
}
return result;
}
}
JSONObject resultJson = new JSONObject(JsonString);
// sender寄件人
String sendeName = resultJson.getJSONObject("sender").optString("name");
String senderPhone = resultJson.optString("phone");
android {
//签名
signingConfigs {
release {
storeFile file('56laile')
storePassword 'storePassword'
keyAlias 'keyAlias'
keyPassword 'keyPassword'
}
debug {
keyAlias '56laile'
keyPassword 'keyPassword'
storeFile file('56laile')
storePassword 'storePassword'
}
}
compileSdkVersion 24
buildToolsVersion '25.0.2'
defaultConfig {
applicationId "applicationId"
minSdkVersion 14
targetSdkVersion 24
versionCode 30
versionName "1.2.6"
ndk {
abiFilters "armeabi", "armeabi-v7a", "x86", "mips"
}
}
buildTypes {
// release包
release {
//是否zip对齐
zipAlignEnabled true
//是否打开debuggable开关
debuggable false
//去除无用的resource文件
shrinkResources false
//是否打开jniDebuggable开关
jniDebuggable false
//混淆开关
multiDexEnabled true
//混淆配置文件
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
// release type下,屏蔽日志输出
buildConfigField("boolean", "LOG_DEBUG", "false")
//签名配置
signingConfig signingConfigs.release
}
// debug包
debug {
//是否zip对齐
zipAlignEnabled true
//是否打开debuggable开关
debuggable true
//去除无用的resource文件
shrinkResources false
//是否打开jniDebuggable开关
jniDebuggable false
//混淆开关
multiDexEnabled true
//混淆配置文件
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
// release type下,开启日志输出
buildConfigField("boolean", "LOG_DEBUG", "true")
//签名配置
signingConfig signingConfigs.debug
}
}
packagingOptions {
exclude 'META-INF/ASL2.0'
exclude 'META-INF/LICENSE'
}
dexOptions {
// 为DEX 编译器 设置最大的堆大小
javaMaxHeapSize "4g"
// 设置最大的线程数量
maxProcessCount 8
}
//lint检查
lintOptions {
// lint检查错误提示
abortOnError false
// 文字不翻译
disable 'MissingTranslation'
}
}
//设置窗体全屏
getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN);
//设置窗体始终点亮
getWindow().setFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON, WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
//设置窗体背景模糊
getWindow().setFlags(WindowManager.LayoutParams.FLAG_BLUR_BEHIND WindowManager.LayoutParams.FLAG_BLUR_BEHIND);
// 开启windows层硬件加速
getWindow().setFlags(WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED, WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED);
// 隐藏标题栏
requestWindowFeature(Window.FEATURE_NO_TITLE);
android开发中,有时需要使用音量控制键来设置程序的音量大小。通过Activity中的函数 setVolumeControlStream(int streamType)可以设置该Activity中音量控制键控制的音频流,一般在onCreate函数中设置。Android中有如下几种音频流(streamType是需要调整音量的类型):
AudioManager.STREAM_MUSIC // 音乐回放即媒体音量
AudioManager.STREAM_RING // 铃声
AudioManager.STREAM_ALARM // 警报
AudioManager.STREAM_NOTIFICATION // 窗口顶部状态栏通知声
AudioManager.STREAM_SYSTEM // 系统
AudioManager.STREAM_VOICECALL // 通话
AudioManager.STREAM_DTMF // 双音多频,不是很明白什么东西
AudioManager可以修改系统Android系统的音量,下面介绍几个AudioManager的几个音量调整方面的方法.首先是得到AudioManager实例:
AudioManager am=(AudioManager)getSystemService(Context.AUDIO_SERVICE);
调整音量方法有两种,一种是渐进式,即像手动按音量键一样,一步一步增加或减少,另一种是直接设置音量值.
public void adjustStreamVolume (int streamType, int direction, int flags)
am.adjustStreamVolume (AudioManager.STREAM_MUSIC,AudioManager.ADJUST_RAISE, AudioManager.FLAG_SHOW_UI);
解释一下三个参数:
*第一个streamType是需要调整音量的类型,这里设的是媒体音量,可以是:
STREAM_ALARM 警报
STREAM_MUSIC 音乐回放即媒体音量
STREAM_NOTIFICATION 窗口顶部状态栏Notification,
STREAM_RING 铃声
STREAM_SYSTEM 系统
STREAM_VOICE_CALL 通话
STREAM_DTMF 双音多频,不是很明白什么东西
*第二个direction,是调整的方向,增加或减少,可以是:
ADJUST_LOWER 降低音量
ADJUST_RAISE 升高音量
ADJUST_SAME 保持不变,这个主要用于向用户展示当前的音量
*第三个flags是一些附加参数,只介绍两个常用的
FLAG_PLAY_SOUND 调整音量时播放声音
FLAG_SHOW_UI 调整时显示音量条,就是按音量键出现的那个
0表示什么也没有
public void setStreamVolume (intstreamType, intindex, intflags)
am.setStreamVolume(AudioManager.STREAM_MUSIC, am.getStreamMaxVolume(AudioManager.STREAM_MUSIC), AudioManager.FLAG_PLAY_SOUND);
am.getStreamMaxVolume(AudioManager.STREAM_VOICE_CALL);//得到听筒模式的最大值
am.getStreamVolume(AudioManager.STREAM_VOICE_CALL);//得到听筒模式的当前值
第一个和第三个参数与上面的相同第二个参数是一个音量的int值,getStreamMaxVolume(int streamType)得到的是该类型音量的最大值,可以根据这个值计算你需要的音量,我这里直接调到最大.
父类BaseActivity:
public void callback() {
}
方法(父类作为形参):
//连接蓝牙设备
public void connect(BaseActivity baseActivity, final String mac, final boolean failCheck, final List<HPRTBlueTooth> list){
//蓝牙是否打开
if(!isBlueToothEnable()){
EnableBluetooth();
}
//port
if(!getHPRTPrinterHelper().IsOpened())
{
ConnectAsyncTask task = new ConnectAsyncTask(baseActivity, mac, list, failCheck);
task.execute();
}else{
Toast.makeText(context,"蓝牙设备已连接成功",Toast.LENGTH_SHORT).show();
baseActivity.callback();
}
}
//android专有多线程方法AsyncTask的继承类
private class ConnectAsyncTask extends AsyncTask<Void,String,String> {
private BaseActivity context;
private String mac;
private List<HPRTBlueTooth> list;
private boolean failCheck;
ConnectAsyncTask(BaseActivity context, String mac, List<HPRTBlueTooth> list, boolean failCheck) {
this.context = context;
this.mac = mac;
this.list = list;
this.failCheck = failCheck;
}
@Override
protected void onPreExecute() {
super.onPreExecute();
startBluetoothProgressDialog(this.context, "正在连接蓝牙设备");
}
@Override
protected String doInBackground(Void... params) {
try {
int result = HPRTPrinterHelper.PortOpen("Bluetooth," + mac);
if(result == 0){
return "SUCCESS";
}
} catch (Exception e) {
e.printStackTrace();
}
return "FAIL";
}
@Override
protected void onPostExecute(String result) {
stopBluetoothProgressDialog();
if(result.equals("FAIL")){
if(failCheck){
showBluetoothCheckDialog(this.context);
}else {
Toast.makeText(this.context, "连接蓝牙设备失败", Toast.LENGTH_SHORT).show();
}
}else {
if (list == null || list.isEmpty()) {
Toast.makeText(this.context,"连接蓝牙设备成功",Toast.LENGTH_SHORT).show();
this.context.callback();
}else{
for(HPRTBlueTooth ele: list){
print(ele);
}
}
}
}
}
子类中调用该方法(子类作为实参):
hprtBlueToothService.connect(DeviceListActivity.this, toothAddress, false, null);
// 回调函数
@Override
public void callback() {
super.callback();
this.finish();
}
android:imeOptions
可选值为
normal,actionUnspecified,actionNone,actionGo,actionSearch,actionSend,actionNext,actionDone。
部分输入法对此的支持可能不够好。
<EditText
android:id="@+id/search_edit"
android:background="@null"
style="@style/text_s24_818181"
android:digits="1234567890."
android:layout_width="match_parent"
android:layout_height="match_parent"
android:singleLine="true"
android:layout_centerVertical="true"
android:imeOptions="actionSearch"
android:hint="请输入12位号段对接码"/>
mSearchEdit.setOnEditorActionListener(new TextView.OnEditorActionListener() {
@Override
public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
if (actionId == EditorInfo.IME_ACTION_DONE || (event != null && event.getKeyCode() == KeyEvent.KEYCODE_ENTER)) {
String keywords = mSearchEdit.getText().toString().trim();
if (StringUtil.isEmpty(keywords)) {
Toast.makeText(ShoppingCodeActivity.this, "请输入12位号段对接码", Toast.LENGTH_SHORT).show();
} else {
// 2017/2/4 点击软键盘回车键事件
submit();
}
return true;
}
return false;
}
});
android:inputType
可以用|选择多个,取值包括 text, textUri, phone, number, numberDecimal, textEmailAddress等。
在 父 Activity 中 onCreate 中加上
getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_HIDDEN);
// 设置文本最大的显示行数
android:lines=”1”
// 设置单行显示,当文本不能显示全部内容时后面使用省略号替代显示
android:singleLine=”true”
String selectAccount = intent.getStringExtra("selectAccount");
private Map<String, String> accountList = intent.getStringExtra("stringAccountList");
for (Map.Entry<String, String> entry : accountList.entrySet()) {
if ((entry.getKey()).equals(selectAccount)) {
Long selectUserId = Long.parseLong(entry.getValue());
}
}
strategy.setAppChannel("56laile_with_bluetooth");