android小工具-系统音量管理器
简介:调节系统音量的小工具,能够快捷的调节系统铃声,媒体音乐、闹钟和通话声音。你可能会想,手机自带的音量键还不够快捷吗?还得写个程序?首先,用音量键调音只能调节一种声音,像闹钟这种声音不能直接调。其次,我这个小工具支持自动调音。只要事先设定,就能在不同时段把手机音量改成你的计划音量。在安静场合避免因为手机铃声导致的尴尬,休闲的时候不会错过重要的电话。
最终效果图:
第一阶段:获取并修改系统音量
布局:文字(TextView)+进度条(SeekBar)
需要实现的功能是拖动进度条,改变对应的系统音量。使用进度条之前要先设置一个最大值,100就是从0—100,15就是从0—15.我们这里的最值就是系统各音量的最大值。各音量的最大值是不同的。
1、 android系统音量的大小分级。
Android系统的声音分为铃声,媒体音乐,闹钟,通话声音等。每种声音的音量大小分多个等级,默认铃声有15级,通话有7级等。具体可以参考最后的链接。
2、 获取各音频的最大值
获取各音频最值要借助系统AudioManager。从名字上我们就能看出这是系统提供管理声音的Manager.然后调用getStreamMaxVolume(type);传入音频类型。音频的类型可以参考最后的链接。
代码
am = (AudioManager) getSystemService(this.AUDIO_SERVICE);
//各音频流的最大值,参数代表类型
ringMax=am.getStreamMaxVolume(AudioManager.STREAM_RING);
musicMax=am.getStreamMaxVolume(AudioManager.STREAM_MUSIC);
alarmMax=am.getStreamMaxVolume(AudioManager.STREAM_ALARM);
callMax=am.getStreamMaxVolume(AudioManager.STREAM_VOICE_CALL);
3、 修改各音频的值
修改同样使用AudioManager
代码
//i是一个整形,要设置的音量大小。
am.setStreamVolume(AudioManager.STREAM_RING,i,0);
am.setStreamVolume(AudioManager.STREAM_MUSIC,i,0);
am.setStreamVolume(AudioManager.STREAM_VOICE_CALL,i,0);
am.setStreamVolume(AudioManager.STREAM_ALARM,i,0);
上面的i从哪里来的?在你拖动进度条的时候,系统会传进来。前提是得告诉系统你需要这个信息。所以我们要为进度条加上观察者。
代码:
ringBar=(SeekBar)findViewById(R.id.main_event_ring);
ringBar.setOnSeekBarChangeListener(barChangeListener);
观察者代码:
//创建进度条触摸事件观察者
SeekBar.OnSeekBarChangeListener barChangeListener = new SeekBar.OnSeekBarChangeListener() {
@Override
public void onProgressChanged(SeekBar seekBar, int i, boolean b) {
//seekBar.setPressed(b);
//seekBar.setProgress(i); bug
if(seekBar==ringBar){
am.setStreamVolume(AudioManager.STREAM_RING,i,0);
}else if (seekBar==musicBar){
am.setStreamVolume(AudioManager.STREAM_MUSIC,i,0);
}else if (seekBar==callBar){
am.setStreamVolume(AudioManager.STREAM_VOICE_CALL,i,0);
}else if (seekBar==alarmBar){
am.setStreamVolume(AudioManager.STREAM_ALARM,i,0);
}
}
@Override
public void onStartTrackingTouch(SeekBar seekBar) { } @Override
public void onStopTrackingTouch(SeekBar seekBar) { }
};
4、 监听系统声音的改变。
上面我们已经可以通过拖动进度条的方式来设置系统音量。但是当系统音量改变时,进度条不会主动改变,没点反应不行啊。所以在系统音量改变的时候,我们要把进度的值设成当前音量值。
代码:
//获取当前系统音量
void changeBar(){
alarmBar.setProgress(am.getStreamVolume(AudioManager.STREAM_ALARM));
musicBar.setProgress(am.getStreamVolume(AudioManager.STREAM_MUSIC));
ringBar.setProgress(am.getStreamVolume(AudioManager.STREAM_RING));
callBar.setProgress(am.getStreamVolume(AudioManager.STREAM_VOICE_CALL));
}
怎么知道系统音量改变了?让系统告诉我们音量改变了。嗯!这是个好方法,所以我们写一个系统音量改变广播的接收器。
代码:
/**
* 处理音量变化时的界面显示
*/
private class MyVolumeReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
//如果音量发生变化则更改seekbar的位置
if(intent.getAction().equals("android.media.VOLUME_CHANGED_ACTION")) {
changeBar();
}
}
}
考虑到只有界面出现的时候才需要进度条跟着系统音量一起变化,所以在onStart里面注册,在onStop里面销毁。
注册代码:
//注册音量发生变化时接收的广播
private void myRegisterReceiver(){
mVolumeReceiver = new MyVolumeReceiver() ;
IntentFilter filter = new IntentFilter() ;
filter.addAction("android.media.VOLUME_CHANGED_ACTION") ;
registerReceiver(mVolumeReceiver, filter) ;
}
销毁代码:
//销毁监听音量的广播
private void myUnRegisterRecevier(){
unregisterReceiver(mVolumeReceiver);
}
第二阶段:添加计划和计划展示功能。
最初的需求是按照计划设定来设置系统的音量。所以在这阶段要实现计划的管理,增删改查。
布局:
1、 一个计划(Event)
上面的布局展示了一个计划有什么。标题,开始时间,结束时间,各音频的设定值。为了处理这些内容,新建一个Event表示一个计划。
字段:静态counter代表Event的总数。每次添加一个Event,counter就加1。Id是每个Event的标识。时间有两个值,String用来显示,int是用来排序的。
Id的设置为counter,之后counter加1
排序:按照开始时间降序排列。计划展示按照时间顺序更方便。
存储:为了便于存储,重写toString()方法。同时提供一个静态方法返回字符串代表的Event.
2、 计划控制器
主要是增删查,改的功能通过删除原来的计划,然后新增一个改动后的计划实现。其他功能是把计划存储到文件里面,从文件里面读出来。
字段:一个静态的Event表,确保所有的操作都在同一份数据上。Context用来获取文件的存取路径。
代码:
package com.example.administrator.soundmanager.controler; import android.content.Context;
import android.os.Environment;
import android.util.Log; import com.example.administrator.soundmanager.model.Event; import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List; public class EventControler {
private Context mContext;
private static List<Event> events=new ArrayList<>();
public EventControler(Context mContext) {
this.mContext=mContext;
if(events.size()<1){
getEventFromFile();
}
} //在表中添加一个事件
public boolean addEvent(Event e){
events.add(e);
saveEvents();
return true;
}
//从表中删除id为eventId的事件。
public boolean deleteEvent(int eventId){
Iterator<Event> iterator =events.iterator();
while (iterator.hasNext()){
if(iterator.next().getEventId()==eventId){
iterator.remove();
}
}
if (events.size()>0){
saveEvents();
}else{
deleteFile("events.evt");
} return true;
}
//获取id为eventId的事件。
public Event getEvent(int eventId){
Iterator<Event> iterator =events.iterator();
while (iterator.hasNext()){
Event e=iterator.next();
if(e.getEventId()==eventId){
return e;
}
}
return null;
}
//获取事件记录表
public List<Event> getEvents(){
return events;
} //将事件记录表中的数据保存到文件中
private boolean saveEvents(){
if(events.size()>0){
StringBuilder stringBuilder=new StringBuilder();
for(Event e:events)
stringBuilder.append(e+"\n");
saveFile(stringBuilder.toString(),"events.evt");
return true;
}else{
return false;
}
}
//从数据文件中读取事件记录。
private boolean getEventFromFile(){
events.clear();
String content=getFile("events.evt");
if(content!=null){
for(String s: content.split("\n"))
events.add(Event.getEvent(s));
return true;
}
return false;
} //文件操作。
private void saveFile(String str, String fileName) {
String cachePath = getCachePath();
try {
//创建临时文件
File tmpFile=new File(cachePath,"temp.evt");
// 如果文件存在
if (tmpFile.exists()) {
// 创建新的空文件
tmpFile.delete();
}
tmpFile.createNewFile();
// 获取文件的输出流对象
FileOutputStream outStream = new FileOutputStream(tmpFile);
// 获取字符串对象的byte数组并写入文件流
outStream.write(str.getBytes());
// 最后关闭文件输出流
outStream.close();
// 创建指定路径的文件
File file = new File(cachePath, fileName);
if(file.exists()){
file.delete();
}
//文件重命名
tmpFile.renameTo(file);
} catch (Exception e) {
e.printStackTrace();
Log.e("EventControler","IOException saveFile failed");
}
}
private String getFile(String fileName) {
try {
// 创建文件
File file = new File(getCachePath(),fileName);
if(file.exists()){
// 创建FileInputStream对象
FileInputStream fis = new FileInputStream(file);
// 创建字节数组 每次缓冲1M
byte[] b = new byte[1024];
int len = 0;// 一次读取1024字节大小,没有数据后返回-1.
// 创建ByteArrayOutputStream对象
ByteArrayOutputStream baos = new ByteArrayOutputStream();
// 一次读取1024个字节,然后往字符输出流中写读取的字节数
while ((len = fis.read(b)) != -1) {
baos.write(b, 0, len);
}
// 将读取的字节总数生成字节数组
byte[] data = baos.toByteArray();
// 关闭字节输出流
baos.close();
// 关闭文件输入流
fis.close();
// 返回字符串对象
return new String(data);
}else {
return null;
}
} catch (Exception e) {
e.printStackTrace();
Log.e("EventControler","IOException getFile failed");
return null;
}
}
private void deleteFile(String fileName){
File file=new File(getCachePath(),fileName);
if(file.exists()){
file.delete();
}
}
private String getCachePath(){
String cachePath ;
//外部存储可用
if (Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState())
|| !Environment.isExternalStorageRemovable()) {
cachePath = mContext.getExternalCacheDir().getPath() ;
}else{
cachePath=mContext.getCacheDir().getPath();
}
return cachePath;
}
}
3、 编辑界面
布局在开始的时候展示了。在这个界面需要实现什么呢?新建一个计划和修改一个已有的计划这两个功能。其实修改是把原来的删除之后新建。
因为eventId可以标识一个计划,所以通过eventId的值来判别是修改还是新建。
下面的工作就简单了,只要界面的内容有改动,就把event对应的值进行修改就好。所以,
编辑框添加内容改变观察者
进度条的内容改变观察者跟第一阶段的相似
开始时间和结束时间的获取,使用对话框和TimePicker控件实现。具体参考的链接在最后。
一切都修改完成后,点击完成按钮.如果是修改就先删除原来的值。
4、 展示界面
显示界面使用了一个RecycleView控件。这个控件的使用主要是,子项的布局文件,数据源,适配器,布局管理器。
布局
子项布局
数据源:从计划控制器得到
布局管理:竖直方向的线性布局
适配器:直接上代码
class ListAdapter extends RecyclerView.Adapter<ListAdapter.ViewHolder>{
private List<Event> events;
public ListAdapter(List<Event> eventList) {
events=eventList;
} @Override
public void onBindViewHolder(ViewHolder holder, final int position) {
Event e=events.get(position);
holder.title.setText(e.getEventName()); holder.startTime.setText(e.getStartTime());
holder.endTime.setText(e.getEndTime()); holder.ring.setProgress(e.getRing());
holder.ring.setMax(ringMax);
holder.ring.setEnabled(false);//禁止拖动
...... } @Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
Context mContext=parent.getContext();
final ViewHolder holder=new ViewHolder(LayoutInflater.from(mContext).inflate(R.layout.item_event,parent,false));
//点击进入编辑界面
View.OnClickListener clickListener=new View.OnClickListener() {
@Override
public void onClick(View view) {
Intent intent =new Intent(ShowEventsActivity.this,EditEventActivity.class);
intent.putExtra("eventId",events.get(holder.getPosition()).getEventId());
notifyDataSetChanged();
startActivityForResult(intent,1000);
}
};
//长按删除
View.OnLongClickListener longClickListener=new View.OnLongClickListener() {
@Override
public boolean onLongClick(View view) {
AlertDialog.Builder builder = new AlertDialog.Builder(ShowEventsActivity.this);
builder.setTitle("删除该计划");
builder.setPositiveButton("确定", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialogInterface, int i) {
eventControler.deleteEvent(events.get(holder.getPosition()).getEventId());
notifyDataSetChanged();
dialogInterface.cancel();
}
});
builder.setNegativeButton("取消", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialogInterface, int i) {
dialogInterface.cancel();
}
});
builder.create().show();
return true;
}
};
holder.view.setOnClickListener(clickListener);
holder.view.setOnLongClickListener(longClickListener);
return holder;
} @Override
public int getItemCount() {
return events.size();
}
class ViewHolder extends RecyclerView.ViewHolder{
TextView title;
TextView startTime,endTime;
SeekBar ring,music,call,alarm;
View view;
public ViewHolder(View view) {
super(view);
this.view=view;
title=(TextView)view.findViewById(R.id.item_event_title);
.....
}
}
}
5、 细节
从展示界面点击list的子项或者点击新建进入编辑界面,编辑完成之后需要更新显示界面。为此在进入编辑界面时采用带返回值的方式启动。这样显示界面就能知道什么时候编辑完成了。
展示界面list的子项在使用进度条展示设定值时,进度条不能被拖动。因此要把进度条设置成禁用状态。
第三阶段:按照设定自动调节系统音量。
第二阶段过后我们就有了计划表,所以自动执行的功能就是这阶段的事了。这里用一个服务完成该功能。服务在运行的时候用当前时间与计划表中的时间作对比,以此判断出当前有效的计划,然后按照计划设定系统音量。
1、 负责调节的服务
后台服务一直运行,每隔一分钟检查一下是否需要改变音量。
代码:
package com.example.administrator.soundmanager; import android.app.AlarmManager;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.media.AudioManager;
import android.os.Binder;
import android.os.Build;
import android.os.Handler;
import android.os.IBinder;
import android.os.Message;
import android.os.SystemClock;
import android.preference.PreferenceManager; import com.example.administrator.soundmanager.controler.EventControler;
import com.example.administrator.soundmanager.model.Event;
import com.example.administrator.soundmanager.util.LOG; import java.util.Calendar;
import java.util.Collections;
import java.util.Iterator;
import java.util.List; public class SoundSetService extends Service {
private final String TAG="SoundSetService";
private List<Event> eventList;
private boolean isRun=false;
private int stopCounter=0;
private AudioManager am;
private NotificationManager notificationManager;
//配置文件
SharedPreferences preferences;
private MyBinder mBinder;
//判断是否为服务自己启动
private boolean fromMySelf=false;
private Handler soundHandler=new Handler(){
@Override
public void handleMessage(Message msg) {
setSysSound();
}
};
public SoundSetService() {
}
@Override
public void onCreate() {
super.onCreate();
LOG.d(TAG,"..............onCreate");
eventList=new EventControler(this).getEvents();
//获得配置文件
preferences=PreferenceManager.getDefaultSharedPreferences(this);
//读取数据,如果无法找到,则使用默认值
isRun=preferences.getBoolean("isRun",false);
//免打扰权限检测
notificationManager=(NotificationManager)getSystemService(Context.NOTIFICATION_SERVICE);
mBinder=new MyBinder();
//使用前台服务保活
startForeground(1001,new Notification());
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
LOG.d(TAG,"..............onStartCommand");
//设置系统音量
Message msg=soundHandler.obtainMessage();
soundHandler.sendMessage(msg);
fromMySelf=intent.getBooleanExtra("fromMySelf",false);
//注册定时事件,每过1分钟通过广播自动唤醒服务,使得服务得以长期运行。如果过服务被销毁,则失效
final AlarmManager alarmManager=(AlarmManager)getSystemService(Context.ALARM_SERVICE);
Intent i=new Intent(this, SoundSetService.class);
i.putExtra("fromMySelf",true);
final PendingIntent weakUpIntent=PendingIntent.getService(this,0,i,0);
alarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP,
SystemClock.elapsedRealtime()+60000, weakUpIntent);
/**
* 应用需要加入内存清理白名单
*/
return START_STICKY;
} @Override
public IBinder onBind(Intent intent) {
LOG.d(TAG,"..............onBind");
// TODO: Return the communication channel to the service.
return mBinder;
} //设置系统音量
private void setSysSound(){
if(isRun){
Event currentEvent=null;
//获取当前时间
Calendar calendar= Calendar.getInstance();
calendar.setTimeInMillis(System.currentTimeMillis());
int mHour= calendar.get(Calendar.HOUR_OF_DAY);
int mMinute=calendar.get(Calendar.MINUTE);
int currentTime=mHour*60+mMinute; //按开始时间降序排列
Collections.sort(eventList); //计算当前有效事件。
Iterator<Event> eventIterator=eventList.iterator();
while (eventIterator.hasNext()){
Event e=eventIterator.next();
LOG.d(TAG,e.toString()+"sTime:"+e.getsTime()+"eTime:"+e.geteTime());
if(e.getsTime()<=currentTime && e.geteTime()>=currentTime){
currentEvent=e;
break;
}
}
if(currentEvent!=null){
setSysSound(currentEvent);
LOG.d(TAG,"currentEvent : "+currentEvent.toString());
}
//服务关闭计数清空
if(stopCounter==0){
}else{
stopCounter=0;
}
}else if(fromMySelf){//3次试探之后,服务关闭。
if(stopCounter>3){
stopCounter=0;
LOG.d(TAG,"stopSelf()");
stopSelf();
}else{
stopCounter++;
}
}
}
private void setSysSound(Event e){
if(am==null){
am= (AudioManager) getSystemService(this.AUDIO_SERVICE);
}
if( Build.VERSION.SDK_INT >= Build.VERSION_CODES.N){
if(notificationManager!=null&¬ificationManager.isNotificationPolicyAccessGranted()) {
am.setStreamVolume(AudioManager.STREAM_RING,e.getRing(),0);
am.setStreamVolume(AudioManager.STREAM_SYSTEM,e.getRing(),0);
am.setStreamVolume(AudioManager.STREAM_NOTIFICATION,e.getRing(),0);
}
}else{
am.setStreamVolume(AudioManager.STREAM_RING,e.getRing(),0);
am.setStreamVolume(AudioManager.STREAM_SYSTEM,e.getRing(),0);
am.setStreamVolume(AudioManager.STREAM_NOTIFICATION,e.getRing(),0);
}
am.setStreamVolume(AudioManager.STREAM_RING,e.getRing(),0);
//如果当前有音乐播放,则不改变音量。
if(!am.isMusicActive()){
am.setStreamVolume(AudioManager.STREAM_MUSIC,e.getMusic(),0);
}
am.setStreamVolume(AudioManager.STREAM_VOICE_CALL,e.getCall(),0);
am.setStreamVolume(AudioManager.STREAM_ALARM,e.getAlarm(),0);
} @Override
public void onDestroy() {
super.onDestroy();
LOG.d(TAG,"..............onDestroy");
mBinder=null;
} public class MyBinder extends Binder{
public boolean isRuning(){
return isRun;
}
public void start(){
isRun=true;
setSysSound();
//更新配置文件
preferences.edit().putBoolean("isRun",isRun).commit();
}
public void end(){
isRun=false;
//更新配置文件
preferences.edit().putBoolean("isRun",isRun).commit();
}
} }
2、 MainActivity设置是否使用自动调节。
在MainActivity中通过绑定的方式,可以控制Service的状态。
在最终效果图可以看到,有个CheckBox,这个用来设置是否使用自动音量调节服务。状态值保留在配置文件里面
CheckBox的观察者。
//设置音量管理服务的运行。
void doServiceSet(){
if(!serviceBox.isChecked()){
if(soundSer==null){
}else{
if(!soundSer.isRuning()){
}else{
soundSer.end();
}
}
}else{
if(soundSer!=null){
if(soundSer.isRuning()){
}else{
soundSer.start();
}
}else{
bindService(new Intent(MainActivity.this,SoundSetService.class),serviceConnection,0);
startService(new Intent(MainActivity.this,SoundSetService.class));
serviceBox.setChecked(false);
}
}
}
3、 开机自启
这个为了防止开机之后忘记打开软件,造成不好的体验。增加了开机自启。通过静态注册广播接收系统的开机实现。
接收器执行代码
第四阶段、测试
1、sdk>=23的版本上程序闪退
调试报错java.lang.SecurityException: Not allowed to change Do Not Disturb state。查询得知需要动态获取免打扰修改的权限,才能设置系统的音量,媒体、闹钟不需要该权限。
解决:
//获取Do not disturb权限,才可进行音量操作
private void getDoNotDisturb(){
NotificationManager nm =
(NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N& nm!=null &!nm.isNotificationPolicyAccessGranted()) {
Intent intent = new Intent(
android.provider.Settings.ACTION_NOTIFICATION_POLICY_ACCESS_SETTINGS); startActivity(intent);
}
}
代码在start后面调用,由用户选择是否授权。
源码:https://github.com/Sutg/SoundManager
参考:
文件读写:https://blog.csdn.net/yoryky/article/details/78675373
内置存储和外部存储:https://zm12.sm-tc.cn/?src=l4uLj4zF0NCIiIjRnJGdk5CYjNGckJLQlZaRmJKQz8zOxtCejYuWnJOajNDKysfJysrG0ZeLkpM%3D&uid=7151bc062e1aec6ad263c0f024a4b76e&hid=91d8f653d3b2c3b90d2b247107e56400&pos=1&cid=9&time=1553565744734&from=click&restype=1&pagetype=0020004002000402&bu=ss_doc&query=Android%E7%9A%84%E5%86%85%E7%BD%AE%E5%AD%98%E5%82%A8&mode=&v=1&force=true&wap=false&uc_param_str=dnntnwvepffrgibijbprsvdsdichei
解决RecyclerView item 宽度没有填充屏幕:https://blog.csdn.net/json_corleone/article/details/84230546
广播实现音量同步:https://m.jb51.net/article/101825.htm
音量的获取与设置:https://blog.csdn.net/coderder/article/details/78436892
滚动时间选择器:https://www.cnblogs.com/android-zcq/p/5435681.html
Android系统的音量默认和最大值:https://blog.csdn.net/l0605020112/article/details/35570543
服务保活:https://www.jianshu.com/p/20801232bc7e
android小工具-系统音量管理器的更多相关文章
- [转]给Linux系统管理员准备的Nmap命令的29个实用范例+ tsysv 系统服务器管理器
原文链接:http://os.51cto.com/art/201401/428152.htm Nmap即网络映射器对Linux系统/网络管理员来说是一个开源且非常通用的工具.Nmap用于在远程机器上探 ...
- Android UI组件:布局管理器
为了更好的管理Android应用的用户界面中的组件,Android提供了布局管理器.通过使用布局管理器,Android应用的图形用户界面具有良好的平台无关性.通常,推荐使用布局管理器来管理组件的分布. ...
- android中常用的布局管理器(二)
接上篇博客 (3)LinearLayout 线性布局管理器 线性布局管理器是将放入其中的组件按照垂直或水平方向来布局,每一行或每一列只能放一个组件,并且不会换行,当组件排列到窗体的边缘后,后面 ...
- 认识 Cargo-Rust构建工具和包管理器
认识 Cargo-Rust构建工具和包管理器 上两篇文章 都有说到 hello world 程序,但是我们如果使用自己创建文件的方式创建项目,一旦文件多了,那得多麻烦,整个项目将变得难以管理.下面我来 ...
- Linux系统-----包管理器的演变
每个电脑设备都使用某种形式的软件来执行其预定任务.在软件开发的早期,对产品进行了严格的bug和其他缺陷测试.在过去的十多年里,软件通过互联网发布,目的是通过应用新版本的软件来修复任何错误.在某些情况下 ...
- wemall app商城源码中基于JAVA的Android异步加载图片管理器代码
wemall doraemon是Android客户端程序,服务端采用wemall微信商城,不对原商城做任何修改,只需要在原商城目录下上传接口文件即可完成服务端的配置,客户端可随意定制修改.本文分享其中 ...
- 使用Android服务,实现报警管理器和广播接收器
介绍 几乎在所有平台上都有很多进程运行背景,它们被称为服务.可能在Android平台中有一些服务可以执行长时间运行的操作,这些操作在处理时不需要用户交互. 在本文中,借助预定义的Android警报服务 ...
- Android开发之通过包管理器获取安装应用信息
最近在自己写一个APP,有一个模块需要获取手机应用的一些信息.坑还是有,但都基本踩过了,自己把他实现了出来,实现方法还是很需要掌握的.底部弹出的对话框中四个选项的实现不多做说明,主要讲讲如何获取这些安 ...
- 实例源码--Android小工具源码
下载源码 技术要点: 1. Android控件布局的使用 2. Http通信 3. XML数据解析 4. 网络状态的监听 5. 源码带有非常详细的中文注释 ...... 详细介绍: 1. An ...
随机推荐
- 浅谈osi模型 三次握手 四次挥手 ddos攻击原理
C/S B/S 架构 C:client 端 B:browser 浏览器 S:server 端 C/S架构,基于客户端与服务端之间的通信 例如:QQ,抖音,快手,微信,支付宝等等 优点:个性化设置,响应 ...
- 树莓派dht11,土壤湿度传感器,继电器的使用。树莓派云灌溉(二)
关于传感器的一些说明 我的想法是这样的 我尽量用易于理解的语言去说我的想法 首先,土壤湿度传感器和dh11会获取数据,然后树莓派会处理这些数据,读出土壤温湿度和空气温湿度,并将这些数据上传到云服务器, ...
- Javascript中的基本数据类型,如何判断数据类型,作用域链的理解
第一部分:Javascript中的数据类型 javascript中 基本数据类型有 五种, 数字 number 字符串 string 布尔 boolean 未定义 undefined 空值 nul ...
- 动图+源码,演示Java中常用数据结构执行过程及原理
最近在整理数据结构方面的知识, 系统化看了下Java中常用数据结构, 突发奇想用动画来绘制数据流转过程. 主要基于jdk8, 可能会有些特性与jdk7之前不相同, 例如LinkedList Linke ...
- net core Webapi基础工程搭建(六)——数据库操作_Part 1
目录 前言 SqlSugar Service层 BaseService(基类) 小结 前言 后端开发最常打交道的就是数据库了(静态网站靠边),上一篇net core Webapi基础工程搭建(五)-- ...
- Python 字符串的所有方法详解
name = "my name is {name} and my age is {age}" # 首字母大写 name.capitalize() # 统计某个字符的个数 name. ...
- Facebook的早期历史
Facemash:谁更有吸引力?Facebook的起源 2003年,当时扎克伯格还是一名哈佛大学的二年级学生,他编写了一个名为Facemash的网站.他利用黑客技术入侵了学校管理部门的网站,并从中 ...
- springboot整合webservice采用CXF技术
转载自:https://blog.csdn.net/qq_31451081/article/details/80783220 强推:https://blog.csdn.net/chjskarl/art ...
- shiro实现session共享(本文转自店蛋蛋)
session共享:在多应用系统中,如果使用了负载均衡,用户的请求会被分发到不同的应用中,A应用中的session数据在B应用中是获取不到的,就会带来共享的问题. 假设:用户第一次访问,连接的A服务器 ...
- 前端H5与安卓和ios之间通信
在一些app场景中,经常看到app里面嵌套H5页面, 安卓和ios提供一个空壳子,方法两者互相调用.上一周就是写H5页面让安卓和ios调用使用,中间传参,接受参数.通过 window.wx 对象调用一 ...