首页 文章 精选 留言 我的

精选列表

搜索[MoE动态路由],共10000篇文章
优秀的个人博客,低调大师

Android 中使用 dlib+opencv 实现动态人脸检测

1 概述 完成 Android 相机预览功能以后,在此基础上我使用 dlib 与 opencv 库做了一个关于人脸检测的 demo。该 demo 在相机预览过程中对人脸进行实时检测,并将检测到的人脸用矩形框描绘出来。具体实现原理如下: 采用双层 View,底层的 TextureView 用于预览,程序从 TextureView 中获取预览帧数据,然后调用 dlib 库对帧数据进行处理,最后将检测结果绘制在顶层的 SurfaceView 中。 2 项目配置 由于项目中用到了 dlib 与 opencv 库,因此需要对其进行配置。主要涉及到以下几个方面: 2.1 C++支持 在项目创建过程中依次选择 Include C++ Support、C++11、Exceptions Support ( -fexceptions )以及 Runtime Type Information Support ( -frtti ) 。最后生成的 build.gradle 文件如下: defaultConfig { applicationId "com.example.lightweh.facedetection" minSdkVersion 23 targetSdkVersion 28 versionCode 1 versionName "1.0" testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" externalNativeBuild { cmake { arguments "-DCMAKE_BUILD_TYPE=Release" cppFlags "-std=c++11 -frtti -fexceptions" } } } 其中,arguments 参数是后添加上去的,主要用于指定 CMake 的编译模式为 Release,因为在 Debug 模式下 dlib 库中相关算法的运行速度非常慢。前期如果需要调试 C++ 代码,可先将 arguments 参数注释。 2.2 dlib 与 opencv 下载 到dlib官网下载最新版本的源码,解压后将文件夹中的dlib目录复制到 Android Studio 工程的 cpp 目录下。 到 sourceforge 下载最新的 opencv-android 库,解压后将文件夹中的 native 目录同样复制到 Android Studio 工程的 cpp 目录下,并改名为 opencv。 2.3 CMakeLists 配置 在 CMakeLists 文件中,我们首先包含 dlib 的 cmake 文件,接下来添加 opencv 的 include 文件夹并引入 opencv 的 so 库,同时将 jni_common 目录中的文件及人脸检测相关文件添加至 native-lib 库中,最后进行链接。 # 设置native目录 set(NATIVE_DIR ${CMAKE_SOURCE_DIR}/src/main/cpp) # 设置dlib include(${NATIVE_DIR}/dlib/cmake) # 设置opencv include文件夹 include_directories(${NATIVE_DIR}/opencv/jni/include) # 设置opencv的so库 add_library( libopencv_java3 SHARED IMPORTED) set_target_properties( libopencv_java3 PROPERTIES IMPORTED_LOCATION ${NATIVE_DIR}/opencv/libs/${ANDROID_ABI}/libopencv_java3.so) # 将jni_common目录中所有文件名,存至SRC_LIST中 AUX_SOURCE_DIRECTORY(${NATIVE_DIR}/jni_common SRC_LIST) add_library( # Sets the name of the library. native-lib # Sets the library as a shared library. SHARED # Provides a relative path to your source file(s). ${SRC_LIST} src/main/cpp/face_detector.h src/main/cpp/face_detector.cpp src/main/cpp/native-lib.cpp) find_library( # Sets the name of the path variable. log-lib # Specifies the name of the NDK library that # you want CMake to locate. log) target_link_libraries( # Specifies the target library. native-lib dlib libopencv_java3 jnigraphics # Links the target library to the log library # included in the NDK. ${log-lib}) # 指定release编译选项 set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} -s -O3 -Wall") set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -s -O3 -Wall") 由于 C++ 代码中用到了头文件 "android/bitmap.h",所以链接时需要添加 jnigraphics 库。 3 JNI相关 Java 类定义 3.1 VisionDetRet 类 VisionDetRet 类的相关对象主要负责 C++ 与 Java 之间的数据传递。 public final class VisionDetRet { private int mLeft; private int mTop; private int mRight; private int mBottom; VisionDetRet() {} public VisionDetRet(int l, int t, int r, int b) { mLeft = l; mTop = t; mRight = r; mBottom = b; } public int getLeft() { return mLeft; } public int getTop() { return mTop; } public int getRight() { return mRight; } public int getBottom() { return mBottom; } } 3.2 FaceDet 类 FaceDet 类为 JNI 函数调用类,主要定义了一些需要 C++ 实现的 native 方法。 public class FaceDet { private static final String TAG = "FaceDet"; // accessed by native methods @SuppressWarnings("unused") private long mNativeFaceDetContext; static { try { // 预加载native方法库 System.loadLibrary("native-lib"); jniNativeClassInit(); Log.d(TAG, "jniNativeClassInit success"); } catch (UnsatisfiedLinkError e) { Log.e(TAG, "library not found"); } } public FaceDet() { jniInit(); } @Nullable @WorkerThread public List<VisionDetRet> detect(@NonNull Bitmap bitmap) { VisionDetRet[] detRets = jniBitmapDet(bitmap); return Arrays.asList(detRets); } @Override protected void finalize() throws Throwable { super.finalize(); release(); } public void release() { jniDeInit(); } @Keep private native static void jniNativeClassInit(); @Keep private synchronized native int jniInit(); @Keep private synchronized native int jniDeInit(); @Keep private synchronized native VisionDetRet[] jniBitmapDet(Bitmap bitmap); } 4 Native 方法实现 4.1 定义 VisionDetRet 类对应的 C++ 类 #include <jni.h> #define CLASSNAME_VISION_DET_RET "com/lightweh/dlib/VisionDetRet" #define CONSTSIG_VISION_DET_RET "()V" #define CLASSNAME_FACE_DET "com/lightweh/dlib/FaceDet" class JNI_VisionDetRet { public: JNI_VisionDetRet(JNIEnv *env) { // 查找VisionDetRet类信息 jclass detRetClass = env->FindClass(CLASSNAME_VISION_DET_RET); // 获取VisionDetRet类成员变量 jID_left = env->GetFieldID(detRetClass, "mLeft", "I"); jID_top = env->GetFieldID(detRetClass, "mTop", "I"); jID_right = env->GetFieldID(detRetClass, "mRight", "I"); jID_bottom = env->GetFieldID(detRetClass, "mBottom", "I"); } void setRect(JNIEnv *env, jobject &jDetRet, const int &left, const int &top, const int &right, const int &bottom) { // 设置VisionDetRet类对象jDetRet的成员变量值 env->SetIntField(jDetRet, jID_left, left); env->SetIntField(jDetRet, jID_top, top); env->SetIntField(jDetRet, jID_right, right); env->SetIntField(jDetRet, jID_bottom, bottom); } // 创建VisionDetRet类实例 static jobject createJObject(JNIEnv *env) { jclass detRetClass = env->FindClass(CLASSNAME_VISION_DET_RET); jmethodID mid = env->GetMethodID(detRetClass, "<init>", CONSTSIG_VISION_DET_RET); return env->NewObject(detRetClass, mid); } // 创建VisionDetRet类对象数组 static jobjectArray createJObjectArray(JNIEnv *env, const int &size) { jclass detRetClass = env->FindClass(CLASSNAME_VISION_DET_RET); return (jobjectArray) env->NewObjectArray(size, detRetClass, NULL); } private: jfieldID jID_left; jfieldID jID_top; jfieldID jID_right; jfieldID jID_bottom; }; 4.2 定义人脸检测类 人脸检测算法需要用大小位置不同的窗口在图像中进行滑动,然后判断窗口中是否存在人脸。本文采用的是 dlib 中的是HOG(histogram of oriented gradient)方法对人脸进行检测,其检测效果要好于 opencv。dlib 中同样提供了 CNN 方法来进行人脸检测,效果好于 HOG,不过需要使用 GPU 加速,不然程序运行会非常慢。 class FaceDetector { private: dlib::frontal_face_detector face_detector; std::vector<dlib::rectangle> det_rects; public: FaceDetector(); // 实现人脸检测算法 int Detect(const cv::Mat &image); // 返回检测结果 std::vector<dlib::rectangle> getDetResultRects(); }; FaceDetector::FaceDetector() { // 定义人脸检测器 face_detector = dlib::get_frontal_face_detector(); } int FaceDetector::Detect(const cv::Mat &image) { if (image.empty()) return 0; if (image.channels() == 1) { cv::cvtColor(image, image, CV_GRAY2BGR); } dlib::cv_image<dlib::bgr_pixel> dlib_image(image); det_rects.clear(); // 返回检测到的人脸矩形特征框 det_rects = face_detector(dlib_image); return det_rects.size(); } std::vector<dlib::rectangle> FaceDetector::getDetResultRects() { return det_rects; } 4.3 native 方法实现 JNI_VisionDetRet *g_pJNI_VisionDetRet; JavaVM *g_javaVM = NULL; // 该函数在加载本地库时被调用 JNIEXPORT jint JNI_OnLoad(JavaVM *vm, void *reserved) { g_javaVM = vm; JNIEnv *env; vm->GetEnv((void **) &env, JNI_VERSION_1_6); // 初始化 g_pJNI_VisionDetRet g_pJNI_VisionDetRet = new JNI_VisionDetRet(env); return JNI_VERSION_1_6; } // 该函数用于执行清理操作 void JNI_OnUnload(JavaVM *vm, void *reserved) { g_javaVM = NULL; delete g_pJNI_VisionDetRet; } namespace { #define JAVA_NULL 0 using DetPtr = FaceDetector *; // 用于存放人脸检测类对象的指针,关联Jave层对象与C++底层对象(相互对应) class JNI_FaceDet { public: JNI_FaceDet(JNIEnv *env) { jclass clazz = env->FindClass(CLASSNAME_FACE_DET); mNativeContext = env->GetFieldID(clazz, "mNativeFaceDetContext", "J"); env->DeleteLocalRef(clazz); } DetPtr getDetectorPtrFromJava(JNIEnv *env, jobject thiz) { DetPtr const p = (DetPtr) env->GetLongField(thiz, mNativeContext); return p; } void setDetectorPtrToJava(JNIEnv *env, jobject thiz, jlong ptr) { env->SetLongField(thiz, mNativeContext, ptr); } jfieldID mNativeContext; }; // Protect getting/setting and creating/deleting pointer between java/native std::mutex gLock; std::shared_ptr<JNI_FaceDet> getJNI_FaceDet(JNIEnv *env) { static std::once_flag sOnceInitflag; static std::shared_ptr<JNI_FaceDet> sJNI_FaceDet; std::call_once(sOnceInitflag, [env]() { sJNI_FaceDet = std::make_shared<JNI_FaceDet>(env); }); return sJNI_FaceDet; } // 从java对象获取它持有的c++对象指针 DetPtr const getDetPtr(JNIEnv *env, jobject thiz) { std::lock_guard<std::mutex> lock(gLock); return getJNI_FaceDet(env)->getDetectorPtrFromJava(env, thiz); } // The function to set a pointer to java and delete it if newPtr is empty // C++对象new以后,将指针转成long型返回给java对象持有 void setDetPtr(JNIEnv *env, jobject thiz, DetPtr newPtr) { std::lock_guard<std::mutex> lock(gLock); DetPtr oldPtr = getJNI_FaceDet(env)->getDetectorPtrFromJava(env, thiz); if (oldPtr != JAVA_NULL) { delete oldPtr; } getJNI_FaceDet(env)->setDetectorPtrToJava(env, thiz, (jlong) newPtr); } } // end unnamespace #ifdef __cplusplus extern "C" { #endif #define DLIB_FACE_JNI_METHOD(METHOD_NAME) Java_com_lightweh_dlib_FaceDet_##METHOD_NAME void JNIEXPORT DLIB_FACE_JNI_METHOD(jniNativeClassInit)(JNIEnv *env, jclass _this) {} // 生成需要返回的结果数组 jobjectArray getRecResult(JNIEnv *env, DetPtr faceDetector, const int &size) { // 根据检测到的人脸数创建相应大小的jobjectArray jobjectArray jDetRetArray = JNI_VisionDetRet::createJObjectArray(env, size); for (int i = 0; i < size; i++) { // 对检测到的每一个人脸创建对应的实例对象,然后插入数组 jobject jDetRet = JNI_VisionDetRet::createJObject(env); env->SetObjectArrayElement(jDetRetArray, i, jDetRet); dlib::rectangle rect = faceDetector->getDetResultRects()[i]; // 将人脸矩形框的值赋给对应的jobject实例对象 g_pJNI_VisionDetRet->setRect(env, jDetRet, rect.left(), rect.top(), rect.right(), rect.bottom()); } return jDetRetArray; } JNIEXPORT jobjectArray JNICALL DLIB_FACE_JNI_METHOD(jniBitmapDet)(JNIEnv *env, jobject thiz, jobject bitmap) { cv::Mat rgbaMat; cv::Mat bgrMat; jniutils::ConvertBitmapToRGBAMat(env, bitmap, rgbaMat, true); cv::cvtColor(rgbaMat, bgrMat, cv::COLOR_RGBA2BGR); // 获取人脸检测类指针 DetPtr mDetPtr = getDetPtr(env, thiz); // 调用人脸检测算法,返回检测到的人脸数 jint size = mDetPtr->Detect(bgrMat); // 返回检测结果 return getRecResult(env, mDetPtr, size); } jint JNIEXPORT JNICALL DLIB_FACE_JNI_METHOD(jniInit)(JNIEnv *env, jobject thiz) { DetPtr mDetPtr = new FaceDetector(); // 设置人脸检测类指针 setDetPtr(env, thiz, mDetPtr); return JNI_OK; } jint JNIEXPORT JNICALL DLIB_FACE_JNI_METHOD(jniDeInit)(JNIEnv *env, jobject thiz) { // 指针置0 setDetPtr(env, thiz, JAVA_NULL); return JNI_OK; } #ifdef __cplusplus } #endif 5 Java端调用人脸检测算法 在开启人脸检测之前,需要在相机 AutoFitTextureView 上覆盖一层自定义 BoundingBoxView 用于绘制检测到的人脸矩形框,该 View 的具体实现如下: public class BoundingBoxView extends SurfaceView implements SurfaceHolder.Callback { protected SurfaceHolder mSurfaceHolder; private Paint mPaint; private boolean mIsCreated; public BoundingBoxView(Context context, AttributeSet attrs) { super(context, attrs); mSurfaceHolder = getHolder(); mSurfaceHolder.addCallback(this); mSurfaceHolder.setFormat(PixelFormat.TRANSPARENT); setZOrderOnTop(true); mPaint = new Paint(); mPaint.setAntiAlias(true); mPaint.setColor(Color.RED); mPaint.setStrokeWidth(5f); mPaint.setStyle(Paint.Style.STROKE); } @Override public void surfaceChanged(SurfaceHolder surfaceHolder, int format, int width, int height) { } @Override public void surfaceCreated(SurfaceHolder surfaceHolder) { mIsCreated = true; } @Override public void surfaceDestroyed(SurfaceHolder surfaceHolder) { mIsCreated = false; } public void setResults(List<VisionDetRet> detRets) { if (!mIsCreated) { return; } Canvas canvas = mSurfaceHolder.lockCanvas(); //清除掉上一次的画框。 canvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR); canvas.drawColor(Color.TRANSPARENT); for (VisionDetRet detRet : detRets) { Rect rect = new Rect(detRet.getLeft(), detRet.getTop(), detRet.getRight(), detRet.getBottom()); canvas.drawRect(rect, mPaint); } mSurfaceHolder.unlockCanvasAndPost(canvas); } } 同时,需要在布局文件中添加对应的 BoundingBoxView 层,保证与 AutoFitTextureView 完全重合: <?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".CameraFragment"> <com.lightweh.facedetection.AutoFitTextureView android:id="@+id/textureView" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerVertical="true" android:layout_centerHorizontal="true" /> <com.lightweh.facedetection.BoundingBoxView android:id="@+id/boundingBoxView" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignLeft="@+id/textureView" android:layout_alignTop="@+id/textureView" android:layout_alignRight="@+id/textureView" android:layout_alignBottom="@+id/textureView" /> </RelativeLayout> BoundingBoxView 添加完成以后,即可在 CameraFragment 中添加对应的人脸检测代码: private class detectAsync extends AsyncTask<Bitmap, Void, List<VisionDetRet>> { @Override protected void onPreExecute() { mIsDetecting = true; super.onPreExecute(); } protected List<VisionDetRet> doInBackground(Bitmap... bp) { List<VisionDetRet> results; // 返回检测结果 results = mFaceDet.detect(bp[0]); return results; } protected void onPostExecute(List<VisionDetRet> results) { // 绘制检测到的人脸矩形框 mBoundingBoxView.setResults(results); mIsDetecting = false; } } 然后,分别在 onResume 与 onPause 函数中完成人脸检测类对象的初始化和释放: @Override public void onResume() { super.onResume(); startBackgroundThread(); mFaceDet = new FaceDet(); if (mTextureView.isAvailable()) { openCamera(mTextureView.getWidth(), mTextureView.getHeight()); } else { mTextureView.setSurfaceTextureListener(mSurfaceTextureListener); } } @Override public void onPause() { closeCamera(); stopBackgroundThread(); if (mFaceDet != null) { mFaceDet.release(); } super.onPause(); } 最后,在 TextureView 的回调函数 onSurfaceTextureUpdated 完成调用: @Override public void onSurfaceTextureUpdated(SurfaceTexture texture) { if (!mIsDetecting) { Bitmap bp = mTextureView.getBitmap(); // 保证图片方向与预览方向一致 bp = Bitmap.createBitmap(bp, 0, 0, bp.getWidth(), bp.getHeight(), mTextureView.getTransform(null), true ); new detectAsync().execute(bp); } } 6 测试结果 经测试,960x720的 bitmap 图片在华为手机(Android 6.0,8核1.2GHz,2G内存)上执行一次检测约耗时800~850ms。Demo 运行效果如下: 更多Android进阶技术,面试资料系统整理分享,职业生涯规划,产品,思维,行业观察,谈天说地。可以加Android架构师群;701740775。

优秀的个人博客,低调大师

jQuery圆形滚动进度条动态加载loading效果

2017年12月13日 可以随时停止进度,需要计算时间清除定时器 .loading .wrap, .loading .circle, .loading .percent { position: absolute; width: 200px; height: 200px; border-radius: 50%; } .loading .wrap { top: 150px; left: 50%; margin-left: -100px; background-color: #ccc; } .loading .circle { box-sizing: border-box; border: 10px solid #ccc; clip: rect(0, 200px, 200px, 100px); } .loading .clip-auto { clip: rect(auto, auto, auto, auto); } .loading .percent { box-sizing: border-box; top: -10px; left: -10px; } .loading .left { transition: transform ease; border: 10px solid #51abe5; clip: rect(0, 100px, 200px, 0); } .loading .right { border: 10px solid #51abe5; clip: rect(0, 200px, 200px, 100px); } .loading .nothing { width: 0; } .loading .num { z-index: 1; position: absolute; left: 10px; top: 10px; width: 180px; height: 180px; color: #fff; line-height: 180px; text-align: center; font-size: 40px; border-radius: 50%; /*background-color: #fff;*/ background: #2b9cdb; box-sizing: border-box; } <div class="wrap"> <div class="circle"> <div class="percent left"></div> <div class="percent right nothing"></div> </div> <div class="num"><span class="export">0</span>%</div> </div> var percent = 0; //初始化百分比 var rcent = 100; //需要显示的百分比 var loading = setInterval(function() { if(percent > 100) { percent = 0; $('.circle').removeClass('clip-auto'); $('.right').addClass('nothing'); } else if(percent > 50) { $('.circle').addClass('clip-auto'); $('.right').removeClass('nothing'); } $('.left').css("-webkit-transform", "rotate(" + (18 / 5) * percent + "deg)"); $('.export').text(percent); if(percent == rcent) { //加载完成 clearInterval(loading); } else { percent++; } }, 50);

优秀的个人博客,低调大师

界面无小事(七):使用代码动态增删布局

界面无小事(一):RecyclerView+CardView了解一下界面无小事(二):让RecyclerView展示更多不同视图界面无小事(三):用RecyclerView + Toolbar做个文件选择器界面无小事(四):来写个滚动选择器吧!界面无小事(五):自定义TextView界面无小事(六):来做个好看得侧拉菜单!github传送门 目录 效果图 前言 布局文件 实现 最后 效果图 不多废话, 先上图, 有兴趣再看下去: 效果图 前言 这篇是之前的一篇旧文改的, 也是想将这篇放入自己的界面无小事专题, 所以当成新篇章来写, 绝对不是为了什么日更之类的事情哦(手动滑稽)~. 布局文件 先来看看布局文件, 不是很复杂, 但是涉及到之后java部分的代码, 所以必须都贴出来. 不过你可以看下预览图就好: 布局预览图 <?xml version="1.0" encoding="utf-8"?> <ScrollView xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context="com.so.moreview.ui.activity.MainActivity"> <LinearLayout android:id="@+id/ll_content" android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical" android:padding="@dimen/eight_dp"> <LinearLayout android:id="@+id/ll_item" android:layout_width="match_parent" android:layout_height="wrap_content" android:background="@android:color/darker_gray" android:orientation="vertical" android:padding="@dimen/eight_dp" tools:ignore="UselessParent"> <EditText android:id="@+id/et_item" android:layout_width="match_parent" android:layout_height="@dimen/forty_dp" android:background="@android:color/white" android:gravity="center_vertical" android:inputType="textMultiLine" android:textSize="@dimen/sixteen_sp" tools:ignore="LabelFor" /> <RelativeLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="@dimen/eight_dp"> <ImageButton android:id="@+id/ib_add" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentEnd="true" android:background="@drawable/add" android:contentDescription="" tools:ignore="ContentDescription" /> </RelativeLayout> </LinearLayout> </LinearLayout> </ScrollView> 实现 LinkedList<ImageButton> mAddList; mAddList.add(curView, btAdd); LinkedList<ImageButton> mDelList; mDelList.add(curView, btDel); 这里我使用LinkedList<ImageButton>实例存储ImageButton, 就是为了让增删的时候方便一些. 最关键的是增删按钮的代码: 添加条目 /** * @param v 添加一个新条目 */ private void addItem(View v) { if (v == null) { return; } // 1. 根据传入的v, 判断是mListAddBtn中的哪一个 int curView = -1; for (int i = 0; i < mAddList.size(); i++) { if (mAddList.get(i) == v) { curView = i; break; } } // 2. 根据获取的值添加控件 if (curView >= 0) { curView++; // ll_item LinearLayout ll = new LinearLayout(MainActivity.this); LinearLayout.LayoutParams llParams = new LinearLayout.LayoutParams( ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT); llParams.setMargins(0, UIUtil.dp2px(8), 0, 0); ll.setLayoutParams(llParams); ll.setBackgroundColor(ContextCompat.getColor(this, android.R.color.darker_gray)); ll.setPadding(UIUtil.dp2px(8), UIUtil.dp2px(8), UIUtil.dp2px(8), UIUtil.dp2px(8)); ll.setOrientation(LinearLayout.VERTICAL); // et_item EditText et = new EditText(MainActivity.this); LinearLayout.LayoutParams etParams = new LinearLayout.LayoutParams( ViewGroup.LayoutParams.MATCH_PARENT, mEtHeight); et.setLayoutParams(etParams); et.setBackgroundColor(ContextCompat.getColor(this, android.R.color.white)); et.setGravity(Gravity.CENTER_VERTICAL); et.setInputType(InputType.TYPE_TEXT_FLAG_MULTI_LINE); et.setTextSize(16); et.setId(mEtId); ll.addView(et); RelativeLayout rl = new RelativeLayout(MainActivity.this); RelativeLayout.LayoutParams rlParams = new RelativeLayout.LayoutParams( ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT); rlParams.setMargins(0, UIUtil.dp2px(8), 0, 0); rl.setLayoutParams(rlParams); // ib_add ImageButton btAdd = new ImageButton(MainActivity.this); RelativeLayout.LayoutParams btAddParams = new RelativeLayout.LayoutParams( ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT); btAddParams.addRule(RelativeLayout.ALIGN_PARENT_END); btAdd.setLayoutParams(btAddParams); btAdd.setBackgroundResource(R.drawable.add); btAdd.setId(mBtnAddId); btAdd.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { addItem(v); } }); rl.addView(btAdd); mAddList.add(curView, btAdd); // ib_del ImageButton btDel = new ImageButton(MainActivity.this); RelativeLayout.LayoutParams btDelParams = new RelativeLayout.LayoutParams( ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT); btDelParams.setMargins(0, 0, UIUtil.dp2px(8), 0); btDelParams.addRule(RelativeLayout.LEFT_OF, mBtnAddId); btDel.setLayoutParams(btDelParams); btDel.setBackgroundResource(R.drawable.del); btDel.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { delItem(v); } }); rl.addView(btDel); mDelList.add(curView, btDel); ll.addView(rl); mLlContent.addView(ll, curView); mBtnAddId++; mEtId++; } } 看起来有些长, 但是对照布局文件来看, 就非常简单了, 就是用java代码把布局文件里写的再写一遍. 删除条目 /** * @param v 删除一个新的EditText条目 */ private void delItem(View v) { if (v == null) { return; } int curView = -1; for (int i = 0; i < mDelList.size(); i++) { if (mDelList.get(i) == v) { curView = i; break; } } if (curView >= 0) { mAddList.remove(curView); mDelList.remove(curView); mLlContent.removeViewAt(curView); } } 删除就很简单了, 先弄清点击的是哪个按钮, 然后把相关的一股脑删了即可. 最后 其实这样改动视图还是比较过时的, 之后会准备一篇RecyclerView增删条目的文章. 到时候一对比就可以看到效果了. 但是在某些场合用用还是可以的, 比如弹窗中微调布局之类的. 喜欢记得点赞哦, 暗中关注我也是可以的~

优秀的个人博客,低调大师

语音科技圈新动态,亚马逊开放Alexa底层技术

考虑到未来最大的回报可能是来自电子商务领域的服务,亚马逊已经开始提供Alexa的独享优惠,以鼓励人们通过语音购物。 近日,为帮助开发者将Alexa的聊天功能融入他们自己的应用,让用户通过语音控制几乎所有的软件,亚马逊面向开发者开放了他们的Alexa的底层技术。 据悉,这项名为Amazon Lex的服务在去年年末就已经推出了预览版。亚马逊CTO维纳·沃格尔斯(Werner Vogels)表示,目前,亚马逊在语音识别和处理方面的能力已经足以支持用户更便捷的使用对话助手或聊天机器人了,不再像以前的工具那么繁琐难用。 将开放Alexa的底层技术作为一项服务,不仅可以通过数百万的用户,利用人们发送给应用的文本和录音来训练Lex以及Alexa,从而持续改进亚马逊的语音识别技术,还可以帮助亚马逊加快数据收集速度。 据统计,目前Echo等使用Alexa的设备销量大约为1000万台,可以预见,此次开放Alexa的底层技术,亚马逊很有可能成为语音控制计算领域的顶尖企业。 与其他云计算服务一样,亚马逊将根据Lex处理的文本和语音数量向开发者收取一定的费用。此外,考虑到未来最大的回报可能是来自电子商务领域的服务,亚马逊已经开始提供Alexa的独享优惠,以鼓励人们通过语音购物。 原文发布时间: 2017-04-20 14:28 本文作者: 伶轩 本文来自云栖社区合作伙伴镁客网,了解相关信息可以关注镁客网。

优秀的个人博客,低调大师

我的架构之路 — 配置中心(三)— 动态更新配置

包含两方面的含义:一、变化的配置能够下发到客户端.二、客户端能够方便地处理这些变化的配置。下面会讲普通监听器回调方式和spring 注解到field的方式。 配置下发 1)客户端间隔20秒(可配)去数据库拉取配置,sql传递的参数有配置名称、环境、上一次最大更新时间2)取到配置后,合并更新本地备份。3)通过监听器的方式通知配置变化。 #监听器接口 public interface ConfigUpdateListener { void update(Properties properties); } #启动时添加监听器 public void addConfigUpdateListener(ConfigUpdateListener configUpdateListener) { configUpdateListeners.add(configUpdateListener); } #配置变化,调用监听器进行通知 for (ConfigUpdateListener configUpdateListener : configUpdateListeners) { configUpdateListener.update(changedProperties); } spring 注解方式绑定成员变量 通过@ConfigValue注解来实现变量绑定,当配置变化的时候会同时更新这个变量。这种方式可以用于一起开关切换、降级处理的场景。 @ConfigValue("baseUrl") private String baseUrl; 实现原理:1)从配置中心获取配置。2)通过BeanPostProcessor 遍历每个spring bean,找出所有包含@ConfigValue注解的field,然后设置值为配置中心的属性值,同时把记录field到Map对象,以后更新配置时就不用想办法遍历了。这里有一个严格处理原则,如果配置中心找不到相应key或值赋值时候报异常,终止程序,保证安全性。其实代码对非spring的、static 成员变量也支持,只是需要程序启动时候指定包含这些static变量的类名列表。 public class PropertyPlaceholderConfigurer extends org.springframework.beans.factory.config.PropertyPlaceholderConfigurer implements BeanPostProcessor { private Map<String,List<BeanField>> configMap= new ConcurrentHashMap<String,List<BeanField>>(); @Override public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { ConfigInjectSupport.inject(mergedProperties,bean.getClass(),bean,configMap); return bean; } @Override public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { return bean; } public static void inject(Properties properties, Class clazz, Object bean, Map<String,List<BeanField>> configMap){ Field[] fields = clazz.getDeclaredFields(); if (null != fields) { for (Field f : fields) { // 如果 bean ==null(非spring),则必须是静态方法 if(bean==null && !Modifier.isStatic(f.getModifiers())) continue; ConfigValue configValue = f.getAnnotation(ConfigValue.class); if (configValue != null) { String key = configValue.value(); f.setAccessible(true); Object value = properties.get(key); if (value != null) { try { f.set(bean, converter.convertIfNecessary(value, f.getType(), f)); } catch (IllegalAccessException e) { throw new RuntimeException(e); } }else{ RuntimeException e= new RuntimeException("can not find config. key="+key+", class="+clazz+", field="+f.getName()); e.printStackTrace(); throw e; } List<BeanField> list = configMap.get(key); if (list == null) { list = new ArrayList<BeanField>(); configMap.put(key, list); } BeanField beanField=new BeanField(); beanField.setBean(bean); beanField.setField(f); list.add(beanField); } } } clazz = clazz.getSuperclass(); } 3)加入监听器,接收变化的配置,更新field值 public static void addConfigUpdateListener(final Map<String,List<BeanField>> configMap) { DefaultConfigClient.addConfigUpdateListener(new ConfigUpdateListener() { @Override public void update(Properties properties) { for (Map.Entry<Object, Object> e : properties.entrySet()) { List<BeanField> beanFields = configMap.get(e.getKey().toString()); if (beanFields != null) { Object value = properties.get(e.getKey()); for (BeanField beanField : beanFields) { try { ConfigValue configValue = beanField.getField().getAnnotation(ConfigValue.class); if(configValue.updatable()) { ConfigInjectSupport.setFieldValue(beanField, value); } } catch (IllegalAccessException ex) { ex.printStackTrace(); } } } } } }); } public static void setFieldValue(BeanField beanField,Object value) throws IllegalAccessException { Field f=beanField.getField(); f.set(beanField.getBean(), ConvertUtils.convert(value,f.getType())); }

优秀的个人博客,低调大师

动态加载Js代码到Head标签中的脚本

我遇到了这样的问题,请教google,结果大多数只是介绍那个注册js的几个函数,而这几个函数插入的js都在body里面,幸而在老外那里看到了这个代码,其实比较简单,但够有用 1 HtmlGenericControlInclude2 = new HtmlGenericControl( " script " ); 2 Include2.Attributes.Add( " type " , " text/javascript " ); 3 Include2.InnerHtml = " alert('JavaScriptinPageHeader'); " ; 4 this .Page.Header.Controls.Add(Include2); 或 Literalli = new Literal(); li.Text = " <script</script> " ; this .Page.Header.Controls.Add(li); 或者 Code 欢迎加群互相学习,共同进步。QQ群:iOS: 58099570 | Android: 572064792 | Nodejs:329118122 做人要厚道,转载请注明出处! 本文转自张昺华-sky博客园博客,原文链接:http://www.cnblogs.com/sunshine-anycall/archive/2009/10/24/1589315.html ,如需转载请自行联系原作者

优秀的个人博客,低调大师

安全动态深解析:DDoS攻击的发展与演变

二十年来,分布式拒绝服务攻击(DDoS)一直是网络犯罪分子进行攻击的一个重要的工具,如今其感染蔓延得越来越广泛,功能也越来越强大。 什么是DDoS攻击? 分布式拒绝服务(DDoS)攻击是指攻击者或攻击者们试图使服务无法交付。这可以通过阻止任何访问的事物来实现:服务器,设备,服务,网络,应用程序,甚至应用程序内的特定事务。在DoS攻击中,它向系统发送恶意数据或请求,而DDoS攻击可以来自多个系统。 通常,这些攻击是通过请求数据来攻击淹没系统的。这可能是向Web服务器发送大量的请求来访问一个页面,而这个页面不能响应用这些需求而崩溃;或者可能是数据库遭遇大量查询,其结果是使可用的互联网带宽,CPU和内存容量变得不堪重负。 其影响范围可能是应用程序和网站体验服务的中断,甚至导致整个业务宕机。 DDoS攻击的症状 DDoS攻击看起来像许多可能产生可用性问题的非恶意事件,例如服务器或系统被破坏,合法用户的合法请求太多,甚至导致数据传输中断。它通常需要流量分析来确定正在发生的情况。 当今的DDoS攻击 然而,分布式拒绝服务攻击的危害让人们改变了对它的看法。2000年年初,加拿大高中生Michael Calce,网名为“黑手党男孩”,通过一个分布式拒绝服务(DDoS)攻击,设法关闭了当时全球最主要门户网站之一雅虎(Yahoo)网站的服务。在接下来的一周中,Calce又成功地中断了亚马逊,CNN和eBay等其他网站的服务。 当然不是这并不是DDoS攻击的第一次实施,但是这种高度公开和一系列成功的攻击将拒绝服务攻击从新奇和微小的骚扰转变成了首度信息安全官和首席信息官维护业务安全运营的噩梦。 从那时起,DDoS攻击已经成为一种频繁发生的威胁,因为它们作为网络活动的一种手段,通常用于目标明确的报复,进行敲诈勒索,甚至发动网络战。 经过多年的发展和演变,DDoS攻击的功能和危害也变得越来越大。20世纪90年代中期,每秒可能只有150次请求攻击,但这足以让许多系统瘫痪。如今他们传播的速度甚至超过1000Gbps。这在很大程度上是由现代僵尸网络的庞大规模所推动的。 去年秋天,互联网基础架构服务提供商Dyn DNS(如今的Oracle DYN)遭到数以千万计的IP地址的DNS查询服务的袭击,这是去年所发生的最新和最强大的DDoS攻击之一。据报道,通过物联网僵尸网络病毒“Mirai”实施的这起袭击事件感染了超过100,000个物联网设备,其中包括IP摄像机和打印机。在其爆发的高峰期,“Mirai”僵尸网络病毒感染了40万台以上的机器人。亚马逊,Netflix,Reddit,Spotify,Tumblr和Twitter等服务都遭到攻击。 “Mirai”僵尸网络的特点在于,与大多数DDoS攻击不同,它主要攻击安全防护比较脆弱的物联网设备,而不是电脑和服务器。根据调研机构BI Intelligence的调查,到2020年,将有340亿台互联网连接设备,而大多数(240亿台)将是物联网设备。 不幸的是,“Mirai”不会是最后一个物联网的僵尸网络。在Akamai,Cloudflare,Flashpoint,Google,RiskIQ和Cymru团队的安全团队进行的调查中,发现了一个同样规模大小的僵尸网络,被称之为WireX,这由100多个国家的10万个安全设备组成的僵尸网络。而这次调查是针对内容提供商和内容传送网络的一系列大型DDoS攻击所促成的。 DDoS攻击工具 通常,DDoS攻击者依赖于僵尸网络,这是集中控制的受到恶意软件感染的系统网络的集合。这些受感染的端点通常是计算机和服务器,但更多的是物联网设备和移动设备。攻击者通过网络钓鱼攻击,恶意攻击和其他大规模感染技术等手段识别和攻击脆弱的系统。攻击者通过各种方法越来越多地从构建者那里租用这些僵尸网络。 DDoS攻击的类型 DDoS攻击有三个主要类别:第一类是使用大量虚假流量来降低资源(如网站或服务器),包括ICMP,UDP和欺骗数据包泛洪攻击。第二类是DDoS攻击使用数据包来定位网络基础架构和基础架构管理工具。这些协议攻击包括SYN Floods和Smurf DDoS等。第三类是一些DDoS攻击针对组织的应用层,并通过恶意编写的请求来淹没应用程序。这三个类别的DDoS攻击目标是一致的:使网络资源反应迟钝或完全无反应。 DDoS攻击如何演变 如上所述,这些攻击通过租用的僵尸网络来实现变得越来越普遍。而这一趋势将会持续下去。 另一个趋势是在攻击中使用多个攻击向量,也称为高级持久拒绝服务(APDoS)。例如,APDoS攻击可能涉及应用层,例如对数据库和应用程序的攻击以及直接在服务器上的攻击。 “这超越了单纯的洪水攻击。”Binary Defense公司合伙人兼执行总监Chuck Mackey说。 另外,Mackey解释说,攻击者通常不仅直接针对受害者,还直接攻击受害者所依赖的组织,如互联网服务提供商和云计算提供商。他说:“这些是具有高影响力的广泛攻击,并且协调一致。” 这也正在改变DDoS攻击对组织的影响,并扩大了他们的风险。美国富理达律师事务所(Foley &Lardner LLP)的网络安全律师Mike Overly说:“企业不仅要关心自己免受DDoS攻击,还要关注其所依赖的广泛的业务合作伙伴,供应商和服务商是否会遭遇攻击。安全最古老的格言之一就是业务的安全与否取决于最薄弱环节。在当今的环境(最近的违规行为证明)中,最弱的环节可以就是第三方。” 当然,网络犯罪分子还在不断改进和完善DDoS攻击,其技术和策略也不会停滞不前。正如JASK公司安全研究总监Rod Soto所说,新增的物联网设备,机器学习和人工智能的兴起都将在这些攻击中发挥重要的作用。 “攻击者最终将这些技术整合到DDoS攻击中,使安全人员难以应对,特别是那些无法通过简单的ACL或签名来阻止的攻击。因此,DDoS攻击的安全防御技术也必须向这个方向发展。”Soto说。 本文转自d1net(转载)

资源下载

更多资源
Mario

Mario

马里奥是站在游戏界顶峰的超人气多面角色。马里奥靠吃蘑菇成长,特征是大鼻子、头戴帽子、身穿背带裤,还留着胡子。与他的双胞胎兄弟路易基一起,长年担任任天堂的招牌角色。

Nacos

Nacos

Nacos /nɑ:kəʊs/ 是 Dynamic Naming and Configuration Service 的首字母简称,一个易于构建 AI Agent 应用的动态服务发现、配置管理和AI智能体管理平台。Nacos 致力于帮助您发现、配置和管理微服务及AI智能体应用。Nacos 提供了一组简单易用的特性集,帮助您快速实现动态服务发现、服务配置、服务元数据、流量管理。Nacos 帮助您更敏捷和容易地构建、交付和管理微服务平台。

Rocky Linux

Rocky Linux

Rocky Linux(中文名:洛基)是由Gregory Kurtzer于2020年12月发起的企业级Linux发行版,作为CentOS稳定版停止维护后与RHEL(Red Hat Enterprise Linux)完全兼容的开源替代方案,由社区拥有并管理,支持x86_64、aarch64等架构。其通过重新编译RHEL源代码提供长期稳定性,采用模块化包装和SELinux安全架构,默认包含GNOME桌面环境及XFS文件系统,支持十年生命周期更新。

Sublime Text

Sublime Text

Sublime Text具有漂亮的用户界面和强大的功能,例如代码缩略图,Python的插件,代码段等。还可自定义键绑定,菜单和工具栏。Sublime Text 的主要功能包括:拼写检查,书签,完整的 Python API , Goto 功能,即时项目切换,多选择,多窗口等等。Sublime Text 是一个跨平台的编辑器,同时支持Windows、Linux、Mac OS X等操作系统。

用户登录
用户注册