SystemUI 实现音量条同步功能
需求:SystemUI 实现音量条同步功能
具体问题
以前在SystemUI 下拉框添加了音量条控制,目前发现在SystemUI下拉框显示状态的情况下,
按键或者底部虚拟导航点击音量加减时候,SystemUI音量条不更新。
如下图:两个SystemUI 下拉音量条和系统自带音量条不同步
参考资料:
以前做过Android12 SystemUI 新增音量条功能,如下,现在要解决的就是Android物理按键或者虚拟导航音量加减按键后,音量和Android自带音量条不同步问题。
Android13_SystemUI下拉框新增音量控制条
Android12_SystemUI下拉框新增音量控制条
熟悉了解,下拉框新增音量条控制逻辑和业务实现,熟悉相关的音量模块基本的UI组件。
思路:
参考系统设置实现方案:
设置->提示音->媒体音量 在音量按键点击时候,音量条同步更新的业务流程
参考SystemUI 音量控制逻辑:
参考应用端实现
应用端监听到音量变化 VOLUME_CHANGED_ACTION 广播,然后进行亮度调进度调节
具体代码分析
设置模块,全局搜索 VOLUME_CHANGED_ACTION
VolumeSliceHelper ->onReceive-
这里可以看出,在监听到音量变化后,如果哪里订阅了相关的uri,那么就通知 notifyChange 一次,接收地方不就是 onChange 方法嘛 。
分析 设置-提示音
进入对应页面,日志显示如下:
Switching to fragment com.android.settings.notification.SoundSettings
Launching fragment com.android.settings.notification.SoundSettings
SoundSettings
加载布局文件
@Overrideprotected int getPreferenceScreenResId() {return R.xml.sound_settings;}具体媒体音量相关<!-- Media volume --><com.android.settings.notification.VolumeSeekBarPreferenceandroid:key="media_volume"android:icon="@drawable/ic_media_stream"android:title="@string/media_volume_option_title"android:order="-180"settings:controller="com.android.settings.notification.MediaVolumePreferenceController"/>
MediaVolumePreferenceController
Volume 关联的控制器
public class MediaVolumePreferenceController extends VolumeSeekBarPreferenceController {private static final String KEY_MEDIA_VOLUME = "media_volume";public MediaVolumePreferenceController(Context context) {super(context, KEY_MEDIA_VOLUME);}@Overridepublic int getAvailabilityStatus() {return mContext.getResources().getBoolean(R.bool.config_show_media_volume)? AVAILABLE: UNSUPPORTED_ON_DEVICE;}@Overridepublic boolean isSliceable() {return TextUtils.equals(getPreferenceKey(), KEY_MEDIA_VOLUME);}@Overridepublic boolean isPublicSlice() {return true;}@Overridepublic boolean useDynamicSliceSummary() {return true;}@Overridepublic String getPreferenceKey() {return KEY_MEDIA_VOLUME;}@Overridepublic int getAudioStream() {return AudioManager.STREAM_MUSIC;}@Overridepublic int getMuteIcon() {return R.drawable.ic_media_stream_off;}
}
VolumeSeekBarPreferenceController
它的官方解释
:/** A slider preference that directly controls an audio stream volume (no dialog) **/
做了以下几件事情:1)加载自己媒体音量的子布局 preference_volume_slider 2)将自己传递给 framework 层,并接收音量变化回调 SeekBarVolumizer.Callback 动态更新UI
/*** Base class for preference controller that handles VolumeSeekBarPreference*/
public abstract class VolumeSeekBarPreferenceController extendsAdjustVolumeRestrictedPreferenceController implements LifecycleObserver {protected VolumeSeekBarPreference mPreference;protected VolumeSeekBarPreference.Callback mVolumePreferenceCallback;protected AudioHelper mHelper;public VolumeSeekBarPreferenceController(Context context, String key) {super(context, key);setAudioHelper(new AudioHelper(context));}@VisibleForTestingvoid setAudioHelper(AudioHelper helper) {mHelper = helper;}public void setCallback(Callback callback) {mVolumePreferenceCallback = callback;}@Overridepublic void displayPreference(PreferenceScreen screen) {super.displayPreference(screen);if (isAvailable()) {mPreference = screen.findPreference(getPreferenceKey());mPreference.setCallback(mVolumePreferenceCallback);mPreference.setStream(getAudioStream());mPreference.setMuteIcon(getMuteIcon());}}@OnLifecycleEvent(Lifecycle.Event.ON_RESUME)public void onResume() {if (mPreference != null) {mPreference.onActivityResume();}}@OnLifecycleEvent(Lifecycle.Event.ON_PAUSE)public void onPause() {if (mPreference != null) {mPreference.onActivityPause();}}@Overridepublic int getSliderPosition() {if (mPreference != null) {return mPreference.getProgress();}return mHelper.getStreamVolume(getAudioStream());}@Overridepublic boolean setSliderPosition(int position) {if (mPreference != null) {mPreference.setProgress(position);}return mHelper.setStreamVolume(getAudioStream(), position);}@Overridepublic int getMax() {if (mPreference != null) {return mPreference.getMax();}return mHelper.getMaxVolume(getAudioStream());}@Overridepublic int getMin() {if (mPreference != null) {return mPreference.getMin();}return mHelper.getMinVolume(getAudioStream());}/*** @return the audio stream type*/public abstract int getAudioStream();protected abstract int getMuteIcon();}
AdjustVolumeRestrictedPreferenceController
处理音量控制器的基本父类,执行调节音量 官方解释:
/*** Base class for preference controller that handles preference that enforce adjust volume restriction **/
VOLUME_CHANGED_ACTION 就是在这里声明监听的
@Overridepublic IntentFilter getIntentFilter() {final IntentFilter filter = new IntentFilter();filter.addAction(AudioManager.VOLUME_CHANGED_ACTION);filter.addAction(AudioManager.STREAM_MUTE_CHANGED_ACTION);filter.addAction(AudioManager.MASTER_MUTE_CHANGED_ACTION);filter.addAction(AudioManager.STREAM_DEVICES_CHANGED_ACTION);return filter;}
/*** Base class for preference controller that handles preference that enforce adjust volume* restriction*/
public abstract class AdjustVolumeRestrictedPreferenceController extendsSliderPreferenceController {private AccountRestrictionHelper mHelper;public AdjustVolumeRestrictedPreferenceController(Context context, String key) {this(context, new AccountRestrictionHelper(context), key);}@VisibleForTestingAdjustVolumeRestrictedPreferenceController(Context context, AccountRestrictionHelper helper,String key) {super(context, key);mHelper = helper;}@Overridepublic void updateState(Preference preference) {if (!(preference instanceof RestrictedPreference)) {return;}mHelper.enforceRestrictionOnPreference((RestrictedPreference) preference,UserManager.DISALLOW_ADJUST_VOLUME, UserHandle.myUserId());}@Overridepublic IntentFilter getIntentFilter() {final IntentFilter filter = new IntentFilter();filter.addAction(AudioManager.VOLUME_CHANGED_ACTION);filter.addAction(AudioManager.STREAM_MUTE_CHANGED_ACTION);filter.addAction(AudioManager.MASTER_MUTE_CHANGED_ACTION);filter.addAction(AudioManager.STREAM_DEVICES_CHANGED_ACTION);return filter;}
}
VolumeSeekBarPreference
/** A slider preference that directly controls an audio stream volume (no dialog) **/
这个是布局 媒体音量的自定义UI
/** A slider preference that directly controls an audio stream volume (no dialog) **/
public class VolumeSeekBarPreference extends SeekBarPreference {private static final String TAG = "VolumeSeekBarPreference";protected SeekBar mSeekBar;private int mStream;private SeekBarVolumizer mVolumizer;private Callback mCallback;private ImageView mIconView;private TextView mSuppressionTextView;private String mSuppressionText;private boolean mMuted;private boolean mZenMuted;private int mIconResId;private int mMuteIconResId;private boolean mStopped;@VisibleForTestingAudioManager mAudioManager;public VolumeSeekBarPreference(Context context, AttributeSet attrs, int defStyleAttr,int defStyleRes) {super(context, attrs, defStyleAttr, defStyleRes);setLayoutResource(R.layout.preference_volume_slider);mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);}public VolumeSeekBarPreference(Context context, AttributeSet attrs, int defStyleAttr) {super(context, attrs, defStyleAttr);setLayoutResource(R.layout.preference_volume_slider);mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);}public VolumeSeekBarPreference(Context context, AttributeSet attrs) {super(context, attrs);setLayoutResource(R.layout.preference_volume_slider);mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);}public VolumeSeekBarPreference(Context context) {super(context);setLayoutResource(R.layout.preference_volume_slider);mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);}public void setStream(int stream) {mStream = stream;setMax(mAudioManager.getStreamMaxVolume(mStream));// Use getStreamMinVolumeInt for non-public stream type// eg: AudioManager.STREAM_BLUETOOTH_SCOsetMin(mAudioManager.getStreamMinVolumeInt(mStream));setProgress(mAudioManager.getStreamVolume(mStream));}public void setCallback(Callback callback) {mCallback = callback;}public void onActivityResume() {if (mStopped) {init();}}public void onActivityPause() {mStopped = true;if (mVolumizer != null) {mVolumizer.stop();mVolumizer = null;}}@Overridepublic void onBindViewHolder(PreferenceViewHolder view) {super.onBindViewHolder(view);mSeekBar = (SeekBar) view.findViewById(com.android.internal.R.id.seekbar);mIconView = (ImageView) view.findViewById(com.android.internal.R.id.icon);mSuppressionTextView = (TextView) view.findViewById(R.id.suppression_text);init();}protected void init() {if (mSeekBar == null) return;final SeekBarVolumizer.Callback sbvc = new SeekBarVolumizer.Callback() {@Overridepublic void onSampleStarting(SeekBarVolumizer sbv) {if (mCallback != null) {mCallback.onSampleStarting(sbv);}}@Overridepublic void onProgressChanged(SeekBar seekBar, int progress, boolean fromTouch) {if (mCallback != null) {mCallback.onStreamValueChanged(mStream, progress);}}@Overridepublic void onMuted(boolean muted, boolean zenMuted) {if (mMuted == muted && mZenMuted == zenMuted) return;mMuted = muted;mZenMuted = zenMuted;updateIconView();}@Overridepublic void onStartTrackingTouch(SeekBarVolumizer sbv) {if (mCallback != null) {mCallback.onStartTrackingTouch(sbv);}}};final Uri sampleUri = mStream == AudioManager.STREAM_MUSIC ? getMediaVolumeUri() : null;if (mVolumizer == null) {mVolumizer = new SeekBarVolumizer(getContext(), mStream, sampleUri, sbvc);}mVolumizer.start();mVolumizer.setSeekBar(mSeekBar);updateIconView();updateSuppressionText();if (!isEnabled()) {mSeekBar.setEnabled(false);mVolumizer.stop();}}protected void updateIconView() {if (mIconView == null) return;if (mIconResId != 0) {mIconView.setImageResource(mIconResId);} else if (mMuteIconResId != 0 && mMuted && !mZenMuted) {mIconView.setImageResource(mMuteIconResId);} else {mIconView.setImageDrawable(getIcon());}}public void showIcon(int resId) {// Instead of using setIcon, which will trigger listeners, this just decorates the// preference temporarily with a new icon.if (mIconResId == resId) return;mIconResId = resId;updateIconView();}public void setMuteIcon(int resId) {if (mMuteIconResId == resId) return;mMuteIconResId = resId;updateIconView();}private Uri getMediaVolumeUri() {return Uri.parse(ContentResolver.SCHEME_ANDROID_RESOURCE + "://"+ getContext().getPackageName()+ "/" + R.raw.media_volume);}public void setSuppressionText(String text) {if (Objects.equals(text, mSuppressionText)) return;mSuppressionText = text;updateSuppressionText();}protected void updateSuppressionText() {if (mSuppressionTextView != null && mSeekBar != null) {mSuppressionTextView.setText(mSuppressionText);final boolean showSuppression = !TextUtils.isEmpty(mSuppressionText);mSuppressionTextView.setVisibility(showSuppression ? View.VISIBLE : View.GONE);}}public interface Callback {void onSampleStarting(SeekBarVolumizer sbv);void onStreamValueChanged(int stream, int progress);/*** Callback reporting that the seek bar is start tracking.*/void onStartTrackingTouch(SeekBarVolumizer sbv);}
}
核心代码,监听音量变化,将seekbar 传递到framework 层
final Uri sampleUri = mStream == AudioManager.STREAM_MUSIC ? getMediaVolumeUri() : null;if (mVolumizer == null) {mVolumizer = new SeekBarVolumizer(getContext(), mStream, sampleUri, sbvc);}mVolumizer.start();mVolumizer.setSeekBar(mSeekBar);updateIconView();updateSuppressionText();if (!isEnabled()) {mSeekBar.setEnabled(false);mVolumizer.stop();}
SeekBarVolumizer
\frameworks\base\core\java\android\preference\SeekBarVolumizer.java
部分核心代码
private final class Observer extends ContentObserver {public Observer(Handler handler) {super(handler);}@Overridepublic void onChange(boolean selfChange) {super.onChange(selfChange);updateSlider();}}private void updateSlider() {if (mSeekBar != null && mAudioManager != null) {final int volume = mAudioManager.getStreamVolume(mStreamType);final int lastAudibleVolume = mAudioManager.getLastAudibleStreamVolume(mStreamType);final boolean mute = mAudioManager.isStreamMute(mStreamType);mUiHandler.postUpdateSlider(volume, lastAudibleVolume, mute);}}public void postUpdateSlider(int volume, int lastAudibleVolume, boolean mute) {obtainMessage(UPDATE_SLIDER, volume, lastAudibleVolume, new Boolean(mute)).sendToTarget();}@Overridepublic void handleMessage(Message msg) {if (msg.what == UPDATE_SLIDER) {if (mSeekBar != null) {mLastProgress = msg.arg1;mLastAudibleStreamVolume = msg.arg2;final boolean muted = ((Boolean)msg.obj).booleanValue();if (muted != mMuted) {mMuted = muted;if (mCallback != null) {mCallback.onMuted(mMuted, isZenMuted());}}updateSeekBar();}}}protected void updateSeekBar() {final boolean zenMuted = isZenMuted();mSeekBar.setEnabled(!zenMuted);if (zenMuted) {mSeekBar.setProgress(mLastAudibleStreamVolume, true);} else if (mNotificationOrRing && mRingerMode == AudioManager.RINGER_MODE_VIBRATE) {mSeekBar.setProgress(0, true);} else if (mMuted) {mSeekBar.setProgress(0, true);} else {mSeekBar.setProgress(mLastProgress > -1 ? mLastProgress : mOriginalStreamVolume, true);}}
设置音量控制同步进度条 核心代码 总结
类 | 作用 |
---|---|
SoundSettings | 设置->提示音 面板 |
sound_settings | 设置提示音面板布局 |
VolumeSeekBarPreference | 加载自己媒体音量的子布局 preference_volume_slider ;将自己传递给 framework 层,并接收音量变化回调 SeekBarVolumizer.Callback 动态更新UI |
preference_volume_slider | VolumeSeekBarPreference UI 类得布局,真正得媒体音量子布局 |
SeekBarPreference | 媒体音量UI自定义UI类得父类 就是支持基本的功能,seekBar 相关的基本功能。 setProgress 方法,原来是在父类中设置并更新UI的 |
MediaVolumePreferenceController | sound_settings布局中对应音量控制器 |
VolumeSeekBarPreferenceController | 处理音量控制器的基本父类 |
AdjustVolumeRestrictedPreferenceController | 处理音量控制器的基本父类,执行调节音量 |
SeekBarVolumizer | 把seek 传递到framework层,framework层实现seekbar 的控制,并回调到App层 |
分析 SystemUI 层
全局搜索 VOLUME_CHANGED_ACTION 思路分析
VolumeDialogControllerImpl
核心内容 注册了一个音量相关广播 并接收处理
Receiver
onVolumeChangedW
private final class Receiver extends BroadcastReceiver {public void init() {final IntentFilter filter = new IntentFilter();filter.addAction(AudioManager.VOLUME_CHANGED_ACTION);。。。。。。。。。。。。。。。。。。。。。mBroadcastDispatcher.registerReceiverWithHandler(this, filter, mWorker);}public void destroy() {mBroadcastDispatcher.unregisterReceiver(this);}@Overridepublic void onReceive(Context context, Intent intent) {final String action = intent.getAction();boolean changed = false;if (action.equals(AudioManager.VOLUME_CHANGED_ACTION)) {final int stream = intent.getIntExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE, -1);final int level = intent.getIntExtra(AudioManager.EXTRA_VOLUME_STREAM_VALUE, -1);final int oldLevel = intent.getIntExtra(AudioManager.EXTRA_PREV_VOLUME_STREAM_VALUE, -1);if (D.BUG) Log.d(TAG, "onReceive VOLUME_CHANGED_ACTION stream=" + stream+ " level=" + level + " oldLevel=" + oldLevel);changed = updateStreamLevelW(stream, level);}。。。。。。。。。。。。。。。。。。。。。。。。。if (changed) {mCallbacks.onStateChanged(mState);}}}boolean onVolumeChangedW(int stream, int flags) {final boolean showUI = shouldShowUI(flags);final boolean fromKey = (flags & AudioManager.FLAG_FROM_KEY) != 0;final boolean showVibrateHint = (flags & AudioManager.FLAG_SHOW_VIBRATE_HINT) != 0;final boolean showSilentHint = (flags & AudioManager.FLAG_SHOW_SILENT_HINT) != 0;boolean changed = false;if (showUI) {changed |= updateActiveStreamW(stream);}int lastAudibleStreamVolume = getAudioManagerStreamVolume(stream);changed |= updateStreamLevelW(stream, lastAudibleStreamVolume);changed |= checkRoutedToBluetoothW(showUI ? AudioManager.STREAM_MUSIC : stream);if (changed) {mCallbacks.onStateChanged(mState);}if (showUI) {onShowRequestedW(Events.SHOW_REASON_VOLUME_CHANGED);}if (showVibrateHint) {mCallbacks.onShowVibrateHint();}if (showSilentHint) {mCallbacks.onShowSilentHint();}if (changed && fromKey) {Events.writeEvent(Events.EVENT_KEY, stream, lastAudibleStreamVolume);}return changed;}
VolumeDialogImpl
通过回调方法 onStateChanged 找到这个类:
/*** Visual presentation of the volume dialog.** A client of VolumeDialogControllerImpl and its state model.** Methods ending in "H" must be called on the (ui) handler.*/
几个核心方法如下
onStateChanged
@Overridepublic void onStateChanged(State state) {onStateChangedH(state);}
onStateChangedH
protected void onStateChangedH(State state) {if (D.BUG) Log.d(TAG, "onStateChangedH() state: " + state.toString());if (mState != null && state != null&& mState.ringerModeInternal != -1&& mState.ringerModeInternal != state.ringerModeInternal&& state.ringerModeInternal == AudioManager.RINGER_MODE_VIBRATE) {mController.vibrate(VibrationEffect.get(VibrationEffect.EFFECT_HEAVY_CLICK));}mState = state;mDynamic.clear();// add any new dynamic rowsfor (int i = 0; i < state.states.size(); i++) {final int stream = state.states.keyAt(i);final StreamState ss = state.states.valueAt(i);if (!ss.dynamic) continue;mDynamic.put(stream, true);if (findRow(stream) == null) {addRow(stream, R.drawable.ic_volume_remote, R.drawable.ic_volume_remote_mute, true,false, true);}}if (mActiveStream != state.activeStream) {mPrevActiveStream = mActiveStream;mActiveStream = state.activeStream;VolumeRow activeRow = getActiveRow();updateRowsH(activeRow);if (mShowing) rescheduleTimeoutH();}for (VolumeRow row : mRows) {updateVolumeRowH(row);}updateRingerH();mWindow.setTitle(composeWindowTitle());}
这里看到的是处理UI的操作,SystemUI 从VOLUME广播监听 到 接收 到 传递到 VolumeDialogImpl 控制UI就已经形成了闭环了。
分析SystemUI 音量流程- 模拟流程分析
回到 VolumeDialogControllerImpl 类的广播监听回调方法 onReceive
回调的最终方法:
mCallbacks.onStateChanged(mState);
mCallbacks 声明如下:
protected C mCallbacks = new C();
添加监听
public void addCallback(Callbacks callback, Handler handler) {mCallbacks.add(callback, handler);callback.onAccessibilityModeChanged(mShowA11yStream);}
VolumeDialogImpl
设置监听 addCallback
public void init(int windowType, Callback callback) {initDialog(mActivityManager.getLockTaskModeState());mAccessibility.init();mController.addCallback(mControllerCallbackH, mHandler);mController.getState();mConfigurationController.addCallback(this);}
mControllerCallbackH
private final VolumeDialogController.Callbacks mControllerCallbackH= new VolumeDialogController.Callbacks() {@Overridepublic void onShowRequested(int reason, boolean keyguardLocked, int lockTaskModeState) {showH(reason, keyguardLocked, lockTaskModeState);}@Overridepublic void onDismissRequested(int reason) {dismissH(reason);}@Overridepublic void onScreenOff() {dismissH(Events.DISMISS_REASON_SCREEN_OFF);}@Overridepublic void onStateChanged(State state) {onStateChangedH(state);}@Overridepublic void onLayoutDirectionChanged(int layoutDirection) {mDialogView.setLayoutDirection(layoutDirection);}@Overridepublic void onConfigurationChanged() {mDialog.dismiss();mConfigChanged = true;}@Overridepublic void onShowVibrateHint() {if (mSilentMode) {mController.setRingerMode(AudioManager.RINGER_MODE_SILENT, false);}}@Overridepublic void onShowSilentHint() {if (mSilentMode) {mController.setRingerMode(AudioManager.RINGER_MODE_NORMAL, false);}}@Overridepublic void onShowSafetyWarning(int flags) {showSafetyWarningH(flags);}@Overridepublic void onAccessibilityModeChanged(Boolean showA11yStream) {mShowA11yStream = showA11yStream == null ? false : showA11yStream;VolumeRow activeRow = getActiveRow();if (!mShowA11yStream && STREAM_ACCESSIBILITY == activeRow.stream) {dismissH(Events.DISMISS_STREAM_GONE);} else {updateRowsH(activeRow);}}@Overridepublic void onCaptionComponentStateChanged(Boolean isComponentEnabled, Boolean fromTooltip) {updateODICaptionsH(isComponentEnabled, fromTooltip);}};
分析到 VolumeDialogController.Callbacks mControllerCallbackH 就不用继续分析了。 这个回调 是写在类里面的。
分析 SystemUI 层 分析总结
- VOLUME_CHANGED_ACTION 监听回调
- VolumeDialogControllerImpl 注册回调addCallback
- VolumeDialogImpl 初始化 init 中 注册回调 addCallback
实现方案如下:
实现方案如下:
通过上面的 SystemUI 的音量回调流程分析和设置中音量回调流程,都是从音量监听的地方开始。但针对本需求并不合适在对应地方设置回调来实现 音量的监听。
实现思路
我们也直接监听VOLUME_CHANGED_ACTION 来实现需求
修改文件:
/vendor/mediatek/proprietary/packages/apps/SystemUI/src/com/android/systemui/settings/volume/VolumeController.java
参考SystemUI层的 VOLUME_CHANGED_ACTION 监听
我们其实在VolumeDialogControllerImpl 类中已经分析了 VOLUME_CHANGED_ACTION 广播相关源码分析,接下来就是照葫芦画瓢实现即可。
基本代码如下:
//wangfangchen add
import com.android.systemui.volume.Events;
import com.android.systemui.volume.VolumeDialogControllerImpl;
import android.content.BroadcastReceiver;
import android.content.Intent;
import android.content.IntentFilter;
import android.media.AudioManager;
//wangfangchen end public class VolumeController implements ToggleSlider.Listener {private static final String TAG = "VolumeController";private static final int SLIDER_ANIMATION_DURATION = 3000;private static final int MSG_UPDATE_SLIDER = 1;private static final int MSG_ATTACH_LISTENER = 2;private static final int MSG_DETACH_LISTENER = 3; private final Context mContext;private final ToggleSlider mControl;private final CurrentUserTracker mUserTracker;private final Handler mBackgroundHandler;private volatile boolean mAutomatic; // Brightness adjusted automatically using ambient light.private volatile boolean mIsVrModeEnabled;private boolean mListening;private boolean mExternalChange;private boolean mControlValueInitialized;private float mBrightnessMin =0;// PowerManager.BRIGHTNESS_MIN;private float mBrightnessMax =100;// PowerManager.BRIGHTNESS_MAX;private ValueAnimator mSliderAnimator;//wangfangchen add private final Receiver mReceiver = new Receiver();protected final BroadcastDispatcher mBroadcastDispatcher;private final W mWorker;long timeFlag = System.currentTimeMillis();//wangfangchen end public interface BrightnessStateChangeCallback {/** Indicates that some of the brightness settings have changed */void onBrightnessLevelChanged();}private final Runnable mStartListeningRunnable = new Runnable() {@Overridepublic void run() {if (mListening) {return;}mListening = true;mUserTracker.startTracking();mUpdateSliderRunnable.run();mHandler.sendEmptyMessage(MSG_ATTACH_LISTENER);}};private final Runnable mStopListeningRunnable = new Runnable() {@Overridepublic void run() {if (!mListening) {return;}mListening = false;mUserTracker.stopTracking();mHandler.sendEmptyMessage(MSG_DETACH_LISTENER);}};/*** Fetch the Volume from the system * background thread.*/private final Runnable mUpdateSliderRunnable = new Runnable() {@Overridepublic void run() {Log.d(TAG, "mUpdateSliderRunnable ");int nowVoiceValue = SoundUtils.INSTANCE.get100CurrentVolume();mHandler.obtainMessage(MSG_UPDATE_SLIDER, nowVoiceValue,0).sendToTarget(); }};private final Handler mHandler = new Handler() {@Overridepublic void handleMessage(Message msg) {mExternalChange = true;try {switch (msg.what) {case MSG_UPDATE_SLIDER:Log.d(TAG, "handleMessage MSG_UPDATE_SLIDER ");updateSlider(msg.arg1, msg.arg2 != 0);break;case MSG_ATTACH_LISTENER:Log.d(TAG, "handleMessage MSG_ATTACH_LISTENER ");mControl.setOnChangedListener(VolumeController.this);break;case MSG_DETACH_LISTENER:Log.d(TAG, "handleMessage MSG_DETACH_LISTENER ");mControl.setOnChangedListener(null);break;default:super.handleMessage(msg);}} finally {mExternalChange = false;}}};public VolumeController(Context context, ToggleSlider control,BroadcastDispatcher broadcastDispatcher) {Log.d(TAG,"VolumeController:GAMMA_SPACE_MAX:"+GAMMA_SPACE_MAX);mContext = context;mControl = control;mControl.setMax(100); //GAMMA_SPACE_MAXmBackgroundHandler = new Handler((Looper) Dependency.get(Dependency.BG_LOOPER));mUserTracker = new CurrentUserTracker(broadcastDispatcher) {@Overridepublic void onUserSwitched(int newUserId) {mBackgroundHandler.post(mUpdateSliderRunnable);}};Log.d(TAG,"VolumeController ,post mUpdateSliderRunnable ");mBackgroundHandler.post(mUpdateSliderRunnable);//wangfangchen add mWorker = new W((Looper) Dependency.get(Dependency.BG_LOOPER));mBroadcastDispatcher = broadcastDispatcher;mReceiver.init();//wangfangchen end }public void registerCallbacks() {mBackgroundHandler.post(mStartListeningRunnable);}/** Unregister all call backs, both to and from the controller */public void unregisterCallbacks() {mBackgroundHandler.post(mStopListeningRunnable);mControlValueInitialized = false;}@Overridepublic void onChanged(boolean tracking, int value, boolean stopTracking) {Log.d(TAG, "onChanged tracking:"+tracking+" value:"+value+" stopTracking:"+stopTracking);if (mExternalChange) return;if (mSliderAnimator != null) {mSliderAnimator.cancel();}if (!tracking) {AsyncTask.execute(new Runnable() {public void run() {//wangfangchen add timeFlag = System.currentTimeMillis();Log.d(TAG,"onChanged setVoice value:"+value+" timeFlag:"+timeFlag);//wangfangchen end SoundUtils.INSTANCE.setVoice(value);}});}}public void checkRestrictionAndSetEnabled() {Log.d(TAG, " checkRestrictionAndSetEnabled ");mBackgroundHandler.post(new Runnable() {@Overridepublic void run() {mControl.setEnforcedAdmin(RestrictedLockUtilsInternal.checkIfRestrictionEnforced(mContext,UserManager.DISALLOW_CONFIG_BRIGHTNESS,mUserTracker.getCurrentUserId()));}});}private void setBrightness(float brightness) {Log.d(TAG, "setBrightness brightness:"+brightness);}private void updateSlider(int brightnessValue, boolean inVrMode) {Log.d(TAG, "updateSlider brightnessValue:"+brightnessValue);animateSliderTo(brightnessValue);}private void animateSliderTo(int target) {Log.d(TAG,"animateSliderTo target:"+target);if (!mControlValueInitialized) {// Don't animate the first value since its default state isn't meaningful to users.mControl.setValue(target);mControlValueInitialized = true;}if (mSliderAnimator != null && mSliderAnimator.isStarted()) {mSliderAnimator.cancel();}mSliderAnimator = ValueAnimator.ofInt(mControl.getValue(), target);mSliderAnimator.addUpdateListener((ValueAnimator animation) -> {mExternalChange = true;mControl.setValue((int) animation.getAnimatedValue());mExternalChange = false;});final long animationDuration = SLIDER_ANIMATION_DURATION * Math.abs(//mControl.getValue() - target) / GAMMA_SPACE_MAX;mControl.getValue() - target) / 100;Log.d(TAG,"animateSliderTo animationDuration:"+animationDuration);mSliderAnimator.setDuration(animationDuration);mSliderAnimator.start();}/** Factory for creating a {@link VolumeController}. */public static class Factory {private final Context mContext;private final BroadcastDispatcher mBroadcastDispatcher;@Injectpublic Factory(Context context, BroadcastDispatcher broadcastDispatcher) {mContext = context;mBroadcastDispatcher = broadcastDispatcher;}/** Create a {@link VolumeController} */public VolumeController create(ToggleSlider toggleSlider) {return new VolumeController(mContext, toggleSlider, mBroadcastDispatcher);}}//wangfangchen addprivate final class Receiver extends BroadcastReceiver {public void init() {final IntentFilter filter = new IntentFilter();filter.addAction(AudioManager.VOLUME_CHANGED_ACTION);mBroadcastDispatcher.registerReceiverWithHandler(this, filter, mWorker);}public void destroy() {mBroadcastDispatcher.unregisterReceiver(this);}@Overridepublic void onReceive(Context context, Intent intent) {final String action = intent.getAction();boolean changed = false;if (action.equals(AudioManager.VOLUME_CHANGED_ACTION)) {final int stream = intent.getIntExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE, -1);final int level = intent.getIntExtra(AudioManager.EXTRA_VOLUME_STREAM_VALUE, -1);final int oldLevel = intent.getIntExtra(AudioManager.EXTRA_PREV_VOLUME_STREAM_VALUE, -1);Log.d(TAG, "onReceive VOLUME_CHANGED_ACTION stream=" + stream+ " level=" + level + " oldLevel=" + oldLevel);Log.d(TAG," mBackgroundHandler:"+mBackgroundHandler+" mUpdateSliderRunnable:"+mUpdateSliderRunnable);long timeNow = System.currentTimeMillis();long spereteTime=timeNow-timeFlag;Log.d(TAG," spereteTime:"+spereteTime);if(spereteTime<100){Log.d(TAG," time is to short return ");return ;}if(mBackgroundHandler!=null&&mUpdateSliderRunnable!=null){mBackgroundHandler.post(mUpdateSliderRunnable);}}}}boolean onVolumeChangedW(int stream, int flags) {Log.d(TAG,"onVolumeChangedW stream:"+stream+" flags:"+flags);return true;}private final class W extends Handler {private static final int VOLUME_CHANGED = 1;W(Looper looper) {super(looper);}@Overridepublic void handleMessage(Message msg) {switch (msg.what) {case VOLUME_CHANGED: onVolumeChangedW(msg.arg1, msg.arg2); break;}}}//wangfangchen end}
主要实现 步骤分析说明
- 类的导入
//wangfangchen add
import com.android.systemui.volume.Events;
import com.android.systemui.volume.VolumeDialogControllerImpl;
import android.content.BroadcastReceiver;
import android.content.Intent;
import android.content.IntentFilter;
import android.media.AudioManager;
//wangfangchen end
- 类创建声明
//wangfangchen add private final Receiver mReceiver = new Receiver();protected final BroadcastDispatcher mBroadcastDispatcher;private final W mWorker;long timeFlag = System.currentTimeMillis();//wangfangchen end
- 构造方法中赋值变量,注册广播
//wangfangchen add mWorker = new W((Looper) Dependency.get(Dependency.BG_LOOPER));mBroadcastDispatcher = broadcastDispatcher;mReceiver.init();//wangfangchen end
- 进度条变更onChange 方法中声明时间TAG
public void onChanged(boolean tracking, int value, boolean stopTracking) {Log.d(TAG, "onChanged tracking:"+tracking+" value:"+value+" stopTracking:"+stopTracking);if (mExternalChange) return;if (mSliderAnimator != null) {mSliderAnimator.cancel();}if (!tracking) {AsyncTask.execute(new Runnable() {public void run() {//wangfangchen add timeFlag = System.currentTimeMillis();Log.d(TAG,"onChanged setVoice value:"+value+" timeFlag:"+timeFlag);//wangfangchen end SoundUtils.INSTANCE.setVoice(value);}});}}
- 音量变化监听,从新设置音量值
@Overridepublic void onReceive(Context context, Intent intent) {final String action = intent.getAction();boolean changed = false;if (action.equals(AudioManager.VOLUME_CHANGED_ACTION)) {final int stream = intent.getIntExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE, -1);final int level = intent.getIntExtra(AudioManager.EXTRA_VOLUME_STREAM_VALUE, -1);final int oldLevel = intent.getIntExtra(AudioManager.EXTRA_PREV_VOLUME_STREAM_VALUE, -1);Log.d(TAG, "onReceive VOLUME_CHANGED_ACTION stream=" + stream+ " level=" + level + " oldLevel=" + oldLevel);Log.d(TAG," mBackgroundHandler:"+mBackgroundHandler+" mUpdateSliderRunnable:"+mUpdateSliderRunnable);long timeNow = System.currentTimeMillis();long spereteTime=timeNow-timeFlag;Log.d(TAG," spereteTime:"+spereteTime);if(spereteTime<100){Log.d(TAG," time is to short return ");return ;}if(mBackgroundHandler!=null&&mUpdateSliderRunnable!=null){mBackgroundHandler.post(mUpdateSliderRunnable);}}}
相关文章:
SystemUI 实现音量条同步功能
需求:SystemUI 实现音量条同步功能 具体问题 以前在SystemUI 下拉框添加了音量条控制,目前发现在SystemUI下拉框显示状态的情况下, 按键或者底部虚拟导航点击音量加减时候,SystemUI音量条不更新。 如下图:两个Syste…...
Android SystemUI——车载CarSystemUI加载(八)
Android 系统早期的状态栏和导航栏对于手机设备来说那是相当重要的,但是随着手机版本的不断更新,状态栏和导航栏对于手机的重要性在逐渐降低,特别是在快捷手势出现之后,导航栏几乎变得可有可无。但是对于当前如火如荼的车载系统来说,状态栏和导航栏却几乎是必备的,谷歌自…...
全类别机器人传感器模块推荐
视觉感知 双目视觉模块:常见分辨率1280720,帧率30fps-60fps,水平视场角60-90,垂直视场角40-60,通过USB接口传数据。用于机器人导航、避障等,基于三角测量原理获取三维信息,定位更精准。单目摄像…...
怎么用python写个唤醒睡眠电脑的脚本?
环境: win10 python3.12 问题描述: 怎么用python写个唤醒睡眠电脑的脚本? 解决方案: 1.唤醒处于睡眠状态的电脑通常不是通过编程直接实现的,而是依赖于硬件和操作系统提供的特性。对于Windows系统,可…...
2025春秋杯冬季赛 day1 crypto
文章目录 通往哈希的旅程小哈斯RSA1ez_rsa 通往哈希的旅程 根据提示推断是哈希函数,ai一下,推测大概率是一个sha1,让ai写一个爆破脚本即可 import hashlib# 给定目标 SHA-1 哈希值 target_hash "ca12fd8250972ec363a16593356abb1f3cf…...
C# 条件编译的应用
一、引言 在 C# 的开发领域中,条件编译宛如一位幕后英雄,虽不常被开发者挂在嘴边,却在诸多关键场景中发挥着无可替代的作用。它就像是一把神奇的钥匙,能够依据特定的条件,精准地决定源代码中的某些部分是否被纳入最终…...
C语言编程笔记:文件处理的艺术
大家好,这里是小编的博客频道 小编的博客:就爱学编程 很高兴在CSDN这个大家庭与大家相识,希望能在这里与大家共同进步,共同收获更好的自己!!! 本文目录 引言正文一、为什么要用文件二、文件的分…...
了解 .mgJSON 文件
.mgJSON (Motion Graphics JSON)是一个基于标准 JSON 格式的文件扩展名,专门用于存储和交换与动态图形、动画和多媒体应用相关的数据。该格式支持静态和动态数据流,能够精确描述动画、物体变换、图形效果等。 .mgJSON 文件通过层级…...
通信协议之多摩川编码器协议
前言 学习永无止境!本篇是通信协议之多摩川编码器协议,主要介绍RS485硬件层以及软件层帧格式。 注:本文章为学习笔记,部分图片与文字来源于网络/应用手册,如侵权请联系!谢谢! 一、多摩川协议概述…...
dbt Semantic Layer 详细教程-6 :指标(metrics)配置规范及示例
前面几篇博文介绍了语义模型及实体、维度和度量规范及示例,一旦创建了语义模型,就该开始添加度量了。可以在与语义模型相同的YAML文件中定义度量,也可以将度量拆分为单独的YAML文件,放入任何其他子目录中(前提是这些子…...
Redis可视化工具--RedisDesktopManager的安装
需要安装使用,0.9.4以上是要收费的 下载地址:https://github.com/uglide/RedisDesktopManager/releases/download/0.9.3/redis-desktop-manager-0.9.3.817.exe 详情:https://blog.csdn.net/u012688704/article/details/82251338 点击进行安…...
《汽车维护与修理》是什么级别的期刊?是正规期刊吗?能评职称吗?
问题解答: 问:《汽车维护与修理》是不是核心期刊? 答:不是,是知网收录的正规学术期刊。 问:《汽车维护与修理》级别? 答:国家级。主管单位:中国汽车维修行业协会 …...
窥探QCC518x/308x系列与手机之间的蓝牙HCI记录与分析 - 手机篇
今天要介绍给大家的是, 当我们在开发高通耳机时如果遇到与手机之间相容性问题, 通常会用Frontline或Ellisys的Bluetooth Analyzer来截取资料分析, 如果手边没有这样的仪器, 要如何窥探Bluetooth的HCI log.这次介绍的是手机篇. 这次跟QCC518x/QCC308x测试的手机是Samsung S23 U…...
MySQL的不同SQL模式导致行为不同?
现象: 我在两个mysql库都有相同定义的表,其中一个字段是varchar(1200)。当我都对这个表进行insert操作,而且超过此字段的规定长度(此处是1200),这两库的行为是不一样的:库B是直接报错too long&…...
【SPIE出版|EI、Scopus双检索】2025年绿色能源与环境系统国际学术会议(GEES 2025)
2025年绿色能源与环境系统国际学术会议(GEES 2025) 会议时间:2025年6月20-22日 会议地点:中国-湖北 最终截稿日期:2025年6月8日 注册截止时间:2025年6月8日 提交检索类型:EI Compendex 和 …...
搭建一个基于Spring Boot的书籍学习平台
搭建一个基于Spring Boot的书籍学习平台可以涵盖多个功能模块,例如用户管理、书籍管理、学习进度跟踪、笔记管理、评论和评分等。以下是一个简化的步骤指南,帮助你快速搭建一个基础的书籍学习平台。 — 1. 项目初始化 使用 Spring Initializr 生成一个…...
SDL2:PC端编译使用 -- SDL2多媒体库使用音频实例
更多内容:XiaoJ的知识星球 SDL2:PC端编译使用 1. SDL2:PC端编译使用1.1 安装必要的依赖1.2 下载编译SDL21.3 SDL2使用示例:Audio1.4 运行示例程序 1. SDL2:PC端编译使用 1.1 安装必要的依赖 首先,确保安装…...
考研计算机组成原理——零基础学习的笔记
第一章 研究计算机硬件的学科。 1.计算机系统概述 计算机系统硬件软件(系统软件:比如操作系统、数据库管理系统、标准程序库等,应用软件:QQ等) 1.2计算机的层次结构 1.2.1计算机硬件的基本组成 冯诺伊曼计算机&a…...
STM32 FreeROTS Tickless低功耗模式
低功耗模式简介 FreeRTOS 的 Tickless 模式是一种特殊的运行模式,用于最小化系统的时钟中断频率,以降低功耗。在 Tickless 模式下,系统只在有需要时才会启动时钟中断,而在无任务要运行时则完全进入休眠状态,从而降低功…...
GAN 用于图像增强
工程需求,临时学一下gan的原理和基于图像增强的实现 原理 论文链接 Generative Adversarial Nets 我们提出了一个通过对抗过程来估计生成模型的新框架,其中我们同时训练两个模型:捕获数据分布的生成模型G和估计样本来自训练数据而不是G的…...
【RAG落地利器】向量数据库Qdrant使用教程
TrustRAG项目地址🌟:https://github.com/gomate-community/TrustRAG 可配置的模块化RAG框架 环境依赖 本教程基于docker安装Qdrant数据库,在此之前请先安装docker. Docker - The easiest way to use Qdrant is to run a pre-built Docker i…...
Day30下 - RAG系统
一、入库文档,问答查询 1. 数据读取 from langchain_core.documents import Document import randomfile_name"knowledge/熬夜救星护肤霜.txt" with open(filefile_name, mode"r", encoding"utf-8") as f:data f.read()# 随机生成…...
linux 安装PrometheusAlert配置钉钉告警
在 Linux 上安装 PrometheusAlert 并配置钉钉告警的步骤如下: 1. 准备工作 钉钉机器人: 在钉钉群中创建一个机器人,获取 Webhook URL。示例 Webhook URL:https://oapi.dingtalk.com/robot/send?access_token=your_dingtalk_token。PrometheusAlert 安装包: 从 Prometheus…...
EAMM: 通过基于音频的情感感知运动模型实现的一次性情感对话人脸合成
EAMM: 通过基于音频的情感感知运动模型实现的一次性情感对话人脸合成 1所有的材料都可以在EAMM: One-Shot Emotional Talking Face via Audio-Based Emotion-Aware Motion Model网站上找到。 摘要 尽管音频驱动的对话人脸生成技术已取得显著进展,但现有方法要么忽…...
Linux提权-02 sudo提权
文章目录 1. sudo 提权原理1.1 原理1.2 sudo文件配置 2. 提权利用方式2.1 sudo权限分配不当2.2 sudo脚本篡改2.3 sudo脚本参数利用2.4 sudo绕过路径执行2.5 sudo LD_PRELOAD环境变量2.6 sudo caching2.7 sudo令牌进程注入 3. 参考 1. sudo 提权原理 1.1 原理 sudo是一个用于在…...
【Pandas】pandas Series apply
Pandas2.2 Series Function application, GroupBy & window 方法描述Series.apply()用于将一个函数应用到 Series 的每个元素或整个 Series pandas.Series.apply pandas.Series.apply 是 Pandas 库中 Series 对象的一个方法,用于将一个函数应用到 Series 的…...
Git学习笔记
Git学习笔记 目录 版本控制 本地版本控制 集中版本控制 分布式版本控制 基本使用方式 Git Config Git Remote Git Add Objects Refs Annotation Tag 追溯历史版本 修改历史版本 Git GC Git Clone & Pull & Fetch Git Push 常见问题 不同的工作流 集…...
HTML5 Canvas实现的跨年烟花源代码
以下是一份基于HTML5 Canvas实现的跨年烟花源代码: <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml">…...
javaEE初阶————多线程初阶(2)
今天给大家带来第二期啦,保证给大家讲懂嗷; 1,线程状态 NEW安排了工作还未开始行动RUNNABLE可工作的,或者即将工作,正在工作BLOCKED排队等待WAITING排队等待其他事TIMED_WAITING排队等待其他事TERMINATED工作完成了 …...
linux下的NFS和FTP部署
目录 NFS应用场景架构通信原理部署权限认证Kerberos5其他认证方式 命令serverclient查看测试系统重启后自动挂载 NFS 共享 高可用实现 FTP对比一些ftp服务器1. **vsftpd (Very Secure FTP Daemon)**2. **ProFTPD (Professional FTP Daemon)**3. **Pure-FTPd**4. **WU-FTPD (Was…...
《Java核心技术II》可中断套接字
4.2.4 可中断套接字 SocketChannel可以中断套接字 SocketChannel channel.open(new InetSocketAddress(host,port)); 通道(channel)并没有与之相关联的流,实际上,所拥有的read和write方法都是通过Buffer对象实现的。 如果不想处理缓冲区,…...
电梯系统的UML文档05
Dispatcher 不控制实际的电梯组件,但它在软件系统中是重要的。每一个电梯有一个ispatcher,主要功能是计算电梯的移动方向、移动目的地以及保持门的打开时间。它和系统中除灯控制器以外的几乎所有控制对象交互。 安全装置也是一个环境对象,它…...
浅谈云计算19 | OpenStack管理模块 (上)
OpenStack管理模块(上) 一、操作界面管理架构二、认证管理2.1 定义与作用2.2 认证原理与流程2.2.1 认证机制原理2.2.2 用户认证流程 三、镜像管理3.1 定义与功能3.2 镜像服务架构3.3 工作原理与流程3.3.1 镜像存储原理3.3.2 镜像检索流程 四、计算管理4.…...
1.5 GPT 模型家族全解析:从 GPT-1 到 GPT-4 的演进与创新
GPT 模型家族全解析:从 GPT-1 到 GPT-4 的演进与创新 随着人工智能技术的飞速发展,GPT(Generative Pre-trained Transformer)模型家族已经成为了现代自然语言处理(NLP)领域的标杆。从初代的 GPT-1 到最新的 GPT-4,每一代模型的发布都标志着人工智能技术的一个飞跃,并推…...
C#如何调用执行命令行窗口(CMD)
一、引言 在 C# 的编程世界里,我们常常会遇到需要与操作系统底层进行交互的场景。这时,调用命令行窗口(CMD)就成为了一个强大的工具。无论是自动化日常任务,还是执行外部程序和批处理文件,通过 C# 调用 CM…...
归子莫的科技周刊#2:白天搬砖,夜里读诗
归子莫的科技周刊#2:白天搬砖,夜里读诗 本周刊开源,欢迎投稿。 刊期:2025.1.5 - 2025.1.11。原文地址。 封面图 下班在深圳看到的夕阳,能遇到是一种偶然的机会,能拍下更是一种幸运。 白天搬砖,…...
Spring Boot + Apache POI 实现 Excel 导出:BOM物料清单生成器(支持中文文件名、样式美化、数据合并)
目录 引言 Apache POI操作Excel的实用技巧 1.合并单元格操作 2.设置单元格样式 1. 创建样式对象 2. 设置边框 3. 设置底色 4. 设置对齐方式 5. 设置字体样式 6.设置自动换行 7. 应用样式到单元格 3. 定位和操作指定单元格 4.实现标签-值的形式 5.列宽设置 1. 设…...
OpenVela——专为AIoT领域打造的开源操作系统
目录 一、系统背景与开源 1.1. 起源 1.2. 开源 二、系统特点 2.1. 轻量化 2.2. 标准兼容性 2.3. 安全性 2.4. 高度可扩展性 三、技术支持与功能 3.1. 架构支持 3.2. 异构计算支持 3.3. 全面的连接套件 3.4. 开发者工具 四、应用场景与优势 4.1. 应用场景 4.2. …...
02UML图(D1_结构图)
目录 学习前言 ---------------------------------- 讲解一:类图 一、类图的组成结构 1. 类(Class) 1.1. 类的成员变量的表示方式 1.2. 类的成员方法的表示方式 2. 接口(Interface) 3. 包(Package) 二、UML类…...
二十三种设计模式-装饰器模式
一、定义与核心思想 装饰器模式是一种结构型设计模式,其核心思想是动态地给一个对象添加一些额外的职责。通过这种方式,可以在不改变原有对象结构的基础上,灵活地增加新的功能,使得对象的行为可以得到扩展,同时又保持…...
SSM课设-酒店管理系统功能设计
【课设者】SSM课设-酒店管理系统 分为用户端管理员端 技术栈: 后端: Spring Spring MVC MyBatis Mysql JSP 前端: HtmlCssJavaScriptAjax 功能: 用户端主要功能包括: 登录注册 客房预订 客房评论 首页 管理员端主要功能包括: 会员信息管理 客房信息…...
R语言的文件操作
R语言的文件操作 引言 在数据科学和分析的过程中,文件操作是不可或缺的一部分。R语言作为一种强大的统计计算和图形作图的编程语言,提供了丰富的文件操作函数,使得用户能够方便地读取和保存数据。本文将详细介绍R语言中的文件操作ÿ…...
[javaWeb]初识Web
将该图片在浏览器中打印出来 代码: <html> <head> <title>HTML初识</title> </head> <body> <h1>猫猫</h1> <img src "img/1.jpg"> </body> &l…...
基于微信小程序的摄影竞赛系统设计与实现(LW+源码+讲解)
专注于大学生项目实战开发,讲解,毕业答疑辅导,欢迎高校老师/同行前辈交流合作✌。 技术范围:SpringBoot、Vue、SSM、HLMT、小程序、Jsp、PHP、Nodejs、Python、爬虫、数据可视化、安卓app、大数据、物联网、机器学习等设计与开发。 主要内容:…...
.netframwork模拟启动webapi服务并编写对应api接口
在.NET Framework环境中模拟启动Web服务,可以使用几种不同的方法。一个常见的选择是利用HttpListener类来创建一个简单的HTTP服务器,或者使用Owin/Katana库来自托管ASP.NET Web API或MVC应用。下面简要介绍Owin/Katana示例代码。这种方法更加灵活&#x…...
Go语言之路————条件控制:if、for、switch
Go语言之路————if、for、switch 前言ifforswitchgoto和label 前言 我是一名多年Java开发人员,因为工作需要现在要学习go语言,Go语言之路是一个系列,记录着我从0开始接触Go,到后面能正常完成工作上的业务开发的过程࿰…...
54,【4】BUUCTF WEB GYCTF2020Ezsqli
进入靶场 吓我一跳,但凡放个彭于晏我都不说啥了 提交个1看看 1 and 11 1# 还尝试了很多,不过都被过滤了,头疼 看看别人的WP 竟然要写代码去跑!!!,不会啊,先用别人的代码吧…...
在线图片压缩工具
在线图片压缩工具,无需登录,无需成本,用完就走。 包括中文和英文版本。 官网地址: https://compress.openai2025.com/ 效果:...
快手极速版如何查找ip归属地?怎么关掉
在数字化时代,个人隐私的保护成为了广大用户关注的焦点。快手极速版作为一款备受欢迎的短视频应用,其IP归属地的显示与关闭功能自然也成了用户热议的话题。本文将详细介绍如何在快手极速版中查找IP归属地以及如何关闭IP属地显示,帮助用户更好…...
精准掌握:Nginx匹配规则及其优先级解析
Nginx作为一款高性能的HTTP和反向代理服务器,其配置文件的匹配规则及优先级设置对于实现精确的请求路由和资源分配至关重要。本文将深入探讨Nginx的匹配规则及其优先级,帮助读者更好地理解和应用这些配置。 一、Nginx匹配规则概述 Nginx的匹配规则主要…...