换肤框架的搭建

news/2024/7/5 20:31:27

这里写图片描述

首先所有皮肤的view——skinView:如ImageView

public class SkinView {

    private View mSkView;//ImageView
    private List<SkinAttr> mSkinAttrs;//src,backgroud

    public SkinView(View mSkView, List<SkinAttr> mSkinAttrs) {
        this.mSkView = mSkView;
        this.mSkinAttrs = mSkinAttrs;
    }
    public void skin(){
        for (SkinAttr skinAttr : mSkinAttrs) {
            skinAttr.skin(mSkView);
        }
    }
}

所有view的属性——SkinAttr

public class SkinAttr {
    private String mResName;//如资源的名字,meinv.jpg
    private SkinType mSkinType;//textColor,background

    public SkinAttr(String mResName, SkinType mSkinType) {
        this.mResName = mResName;
        this.mSkinType = mSkinType;
    }

    public void skin(View view) {
        mSkinType.skin(view,mResName);
    }
}

每个类型进行判断并设置

public enum SkinType {
    TEXT_COLO("textColor") {
        @Override
        public void skin(View view, String resName) {
            SkinResource resource = getSkinResource();
            ColorStateList color = resource.getColorByName(resName);
            if (color != null) {
                TextView tv = (TextView) view;
                tv.setTextColor(color);
            }
        }
    }, BACKGROUND("background") {
        @Override
        public void skin(View view, String resName) {
            SkinResource resource = getSkinResource();
            Drawable drawable = resource.getDrawableByName(resName);
            ImageView imageView = (ImageView) view;
            if (drawable != null) {
                imageView.setBackgroundDrawable(drawable);
                return;
            }
            //有可能是颜色
            ColorStateList color = resource.getColorByName(resName);
            if (color != null) {
                view.setBackgroundColor(color.getDefaultColor());
            }
        }
    }, SRC("src") {
        @Override
        public void skin(View view, String resName) {
            SkinResource resource = getSkinResource();
            Drawable drawable = resource.getDrawableByName(resName);
            if (drawable != null) {
                ImageView imageView = (ImageView) view;
                imageView.setImageDrawable(drawable);
                return;
            }
        }
    };
    private String mResName;

    SkinType(String resName) {
        this.mResName = resName;
    }

    public abstract void skin(View view, String resName);

    /**
     * 获得资源的名字(meinv.jpg)
     */
    public String getResName() {
        return mResName;
    }

    /**
     * 获得皮肤
     */
    public SkinResource getSkinResource() {
        return SkinManager.getInstance().getSkinResource();
    }
}

皮肤属性解析辅助类SkinAttrSupport

public class SkinAttrSupport {

    private static final String TAG = SkinAttrSupport.class.getSimpleName();

    public static List<SkinAttr> skin(Context context, AttributeSet attrs) {
        List<SkinAttr> skinAttrs = new ArrayList<>();
        int count = attrs.getAttributeCount();
        for (int index = 0; index < count; index++) {//layout_marginTop,----->10.0dip
            //Log.e(TAG, attrs.getAttributeName(index)+",----->"+attrs.getAttributeValue(index));

            //获得属性的名字和值
            String attrName = attrs.getAttributeName(index);
            String attrValue = attrs.getAttributeValue(index);

            SkinType skinType = getSkinType(attrName);

            if (skinType != null) {
                String resName = getResName(context, attrValue);
                //Log.e(TAG,resName);//image_src
                if(TextUtils.isEmpty(resName)){
                    continue;
                }
                SkinAttr skinAttr = new SkinAttr(resName, skinType);
                skinAttrs.add(skinAttr);
            }
        }
        return skinAttrs;
    }

    /**
     * 获得资源的名字
     */
    private static String getResName(Context context, String attrs) {//textSize=15
        if (attrs.startsWith("@")) {
            attrs = attrs.substring(1);//从1开始截取
            int resId = Integer.parseInt(attrs);
            return context.getResources().getResourceEntryName(resId);//设置的图片的资源的名字
        }
        return null;
    }

    /**
     * 获得属性的名字
     */
    private static SkinType getSkinType(String attrName) {
        SkinType[] skinTypes = SkinType.values();
        for (SkinType skinType : skinTypes) {
            if (skinType.getResName().equals(attrName)) {
                return skinType;
            }
        }
        return null;
    }
}

获取资源——skinResource

public class SkinResource {

    private Resources mSkinResource;
    private String mPackName;
    private String TAG=SkinResource.class.getSimpleName();

    public SkinResource(Context context, String skinPath) {
        Resources superResource = context.getResources();

        try {
            AssetManager assets = AssetManager.class.newInstance();//{@hide}

            Method method = AssetManager.class.getDeclaredMethod("addAssetPath", String.class);
            method.invoke(assets, skinPath);

            mSkinResource = new Resources
                    (assets, superResource.getDisplayMetrics(), superResource.getConfiguration());
            //获取包名
            mPackName = context.getPackageManager()
                    .getPackageArchiveInfo(skinPath, PackageManager.GET_ACTIVITIES).packageName;

        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 获取资源
     */
    public Drawable getDrawableByName(String resName) {
        try {
            int resourceId = mSkinResource.getIdentifier(resName, "drawable", mPackName);
            //Log.e(TAG,resName+"——>mPackName"+mPackName);
            Drawable drawable = mSkinResource.getDrawable(resourceId);
            return drawable;
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }
    /**
     * 获取颜色
     */
    public ColorStateList getColorByName(String resName) {
        try {
            int resourceId = mSkinResource.getIdentifier(resName, "drawable", mPackName);
            ColorStateList color = mSkinResource.getColorStateList(resourceId);
            return color;
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }
}

皮肤管理

public class SkinManager {
    private static final SkinManager mInstance;
    private Context mContext;
    private Map<Activity, List<SkinView>> mSkinView = new HashMap<>();
    private SkinResource mSkinResource;

    public void init(Context context) {
        mContext = context.getApplicationContext();
        String skinPath = SkinPreUtils.getInstance(context).getSkinPath();
        File file = new File(skinPath);
        if (!file.exists()) {
            SkinPreUtils.getInstance(context).clearSkinInfo();
            return;
        }
        //包名不存在
        String packageName = context.getPackageManager().getPackageArchiveInfo
                (skinPath, PackageManager.GET_ACTIVITIES).packageName;
        if (TextUtils.isEmpty(packageName)) {
            SkinPreUtils.getInstance(mContext).clearSkinInfo();
            return;
        }
        mSkinResource = new SkinResource(mContext, skinPath);
    }

    static {
        mInstance = new SkinManager();
    }

    public static SkinManager getInstance() {

        return mInstance;
    }

    //换肤
    public int loadSkin(String skinPath) {

        File file = new File(skinPath);
        if (!file.exists()) {
            return SkinConfig.SKIN_FILE_NOEXSIST;//文件不存在
        }
        //包名不存在
        String packageName = mContext.getPackageManager().getPackageArchiveInfo
                (skinPath, PackageManager.GET_ACTIVITIES).packageName;
        if (TextUtils.isEmpty(packageName)) {
            return SkinConfig.SKIN_FILE_ERROR;//不是一个apk
        }
        //如果当前是一样的就不换肤
        String currentPath = SkinPreUtils.getInstance(mContext).getSkinPath();
        if (currentPath.equals(skinPath)) {
            return SkinConfig.SKIN_CHANGE_NOTHING;
        }
        mSkinResource = new SkinResource(mContext, skinPath);
        changeSkin();
        //4.保存皮肤
        SkinPreUtils.getInstance(mContext).saveSkinPath(skinPath);
        return 0;
    }

    /**
     * 切换皮肤
     */
    private void changeSkin() {
        Set<Activity> keys = mSkinView.keySet();
        for (Activity key : keys) {
            List<SkinView> skinViews = mSkinView.get(key);
            for (SkinView skinView : skinViews) {
                skinView.skin();
            }
        }
    }

    //恢复默认
    public int restoreDefault() {
        String currentSkinPath = SkinPreUtils.getInstance(mContext).getSkinPath();
        if (TextUtils.isEmpty(currentSkinPath)) {
            return SkinConfig.SKIN_CHANGE_NOTHING;
        }

        //当前手机运行的路径
        String skinPath = mContext.getPackageResourcePath();
        mSkinResource = new SkinResource(mContext, skinPath);

        //改变皮肤
        changeSkin();

        //清空皮肤信息
        SkinPreUtils.getInstance(mContext).clearSkinInfo();
        return SkinConfig.SKIN_CHANGE_SUCCESS;
    }

    public List<SkinView> getSkinView(Activity activity) {
        return mSkinView.get(activity);
    }

    public SkinResource getSkinResource() {
        return mSkinResource;
    }

    public void register(Activity activity, List<SkinView> skinViews) {
        mSkinView.put(activity, skinViews);
    }

    /**
     * 检查是否需要切换
     *
     * @param skinView
     */
    public void checkChangeSkin(SkinView skinView) {
        String skinPath = SkinPreUtils.getInstance(mContext).getSkinPath();
        if (!TextUtils.isEmpty(skinPath)) {
            skinView.skin();
        }
    }

    public void unRegister(Activity activity) {
        mSkinView.remove(activity);
    }
}

拦截view

public abstract class BaseSkinActivity extends BaseActivity implements LayoutInflaterFactory {
    private SkinAppCompatViewInflater mAppCompatViewInflater;

    private String TAG = "BaseSkinActivity";
    private static final boolean IS_PRE_LOLLIPOP = Build.VERSION.SDK_INT < 21;
    //只能放一些通用的方法,基本每个activity都需要使用的方法
    // 如果是两个或两个以上的地方要使用,最好写一个工具类


    @Override
    public void onCreate(@Nullable Bundle savedInstanceState) {
        //拦截到View的创建  获取View之后去解析
        LayoutInflater layoutInflater = LayoutInflater.from(this);
        LayoutInflaterCompat.setFactory(layoutInflater, this);//会调用onCreateView方法

        super.onCreate(savedInstanceState);
    }

    public View onCreateView(View parent, final String name, @NonNull Context context,
                             @NonNull AttributeSet attrs) {
        //  //1.创建View
        final View view = createView(parent, name, context, attrs);
//        Log.e(TAG, view + "");
        if (view != null) {
            // 2. 解析属性  src  textColor  background  自定义属性

            // 2.1 一个activity的布局肯定对应多个这样的 SkinView
            List<SkinAttr> skinAttrs = SkinAttrSupport.skin(context, attrs);
            SkinView skinView = new SkinView(view, skinAttrs);

            // 3.统一交给SkinManager管理
            managerSkinView(skinView);
            //4.判断是否需要换肤
            SkinManager.getInstance().checkChangeSkin(skinView);

            return view;
        }
        return view;
    }

    protected void managerSkinView(SkinView skinView) {
        List<SkinView> skinViews = SkinManager.getInstance().getSkinView(this);
        if (skinViews == null) {
            skinViews = new ArrayList<>();
            SkinManager.getInstance().register(this, skinViews);
        }
        skinViews.add(skinView);
    }

    public View createView(View parent, final String name, @NonNull Context context,
                           @NonNull AttributeSet attrs) {
        if (mAppCompatViewInflater == null) {
            mAppCompatViewInflater = new SkinAppCompatViewInflater();
        }

        boolean inheritContext = false;
        if (IS_PRE_LOLLIPOP) {
            inheritContext = (attrs instanceof XmlPullParser)
                    // If we have a XmlPullParser, we can detect where we are in the layout
                    ? ((XmlPullParser) attrs).getDepth() > 1
                    // Otherwise we have to use the old heuristic
                    : shouldInheritContext((ViewParent) parent);
        }

        return mAppCompatViewInflater.createView(parent, name, context, attrs, inheritContext,
                IS_PRE_LOLLIPOP, /* Only read android:theme pre-L (L+ handles this anyway) */
                true, /* Read read app:theme as a fallback at all times for legacy reasons */
                VectorEnabledTintResources.shouldBeUsed() /* Only tint wrap the context if enabled */
        );
    }

    private boolean shouldInheritContext(ViewParent parent) {
        if (parent == null) {
            // The initial parent is null so just return false
            return false;
        }
        final View windowDecor = getWindow().getDecorView();
        while (true) {
            if (parent == null) {
                // Bingo. We've hit a view which has a null parent before being terminated from
                // the loop. This is (most probably) because it's the root view in an inflation
                // call, therefore we should inherit. This works as the inflated layout is only
                // added to the hierarchy at the end of the inflate() call.
                return true;
            } else if (parent == windowDecor || !(parent instanceof View)
                    || ViewCompat.isAttachedToWindow((View) parent)) {
                // We have either hit the window's decor view, a parent which isn't a View
                // (i.e. ViewRootImpl), or an attached view, so we know that the original parent
                // is currently added to the view hierarchy. This means that it has not be
                // inflated in the current inflate() call and we should not inherit the context.
                return false;
            }
            parent = parent.getParent();
        }
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        SkinManager.getInstance().unRegister(this);
    }
}

工具类封装
资源文件配置SkinConfig

public class SkinConfig {
    // SP的文件名称
    public static final String SKIN_INFO_NAME = "skinInfo";

    // 保存皮肤文件的路径的名称
    public static final String SKIN_PATH_NAME = "skinPath";
    // 不需要改变任何东西
    public static final int SKIN_CHANGE_NOTHING = -1;

    // 换肤成功
    public static final int SKIN_CHANGE_SUCCESS = 1;

    // 皮肤文件不存在
    public static final int SKIN_FILE_NOEXSIST = -2;

    // 皮肤文件有错误可能不是一个apk文件
    public static final int SKIN_FILE_ERROR = -3;
}

sp保存

public class SkinPreUtils {

    private static SkinPreUtils mInstance;
    private static Context mContext;

    private SkinPreUtils(Context context) {
        this.mContext = context.getApplicationContext();
    }

    public static SkinPreUtils getInstance(Context context) {
        if (mInstance == null) {
            synchronized (SkinPreUtils.class) {
                if (mInstance == null) {
                    mInstance = new SkinPreUtils(context);
                }
            }
        }
        return mInstance;
    }

    /**
     * 保存当前皮肤路径
     *
     * @param skinPath
     */
    public void saveSkinPath(String skinPath) {

        mContext.getSharedPreferences(SkinConfig.SKIN_INFO_NAME, Context.MODE_PRIVATE)
                .edit().putString(SkinConfig.SKIN_PATH_NAME, skinPath).commit();
    }

    /**
     * 获取皮肤的路径
     *
     * @return 当前皮肤路径
     */
    public String getSkinPath() {
        return mContext.getSharedPreferences(SkinConfig.SKIN_INFO_NAME, Context.MODE_PRIVATE)
                .getString(SkinConfig.SKIN_PATH_NAME, "");
    }

    /**
     * 清空皮肤路径
     */
    public void clearSkinInfo() {
        saveSkinPath("");
    }

}

SkinAppCompatViewInflate:是Google官方提供的源码,因为源码不是public所以不能直接使用

public class SkinAppCompatViewInflater {

        private static final Class<?>[] sConstructorSignature = new Class[]{
                Context.class, AttributeSet.class};
        private static final int[] sOnClickAttrs = new int[]{android.R.attr.onClick};

        private static final String[] sClassPrefixList = {
                "android.widget.",
                "android.view.",
                "android.webkit."
        };

        private static final String LOG_TAG = "AppCompatViewInflater";

        private static final Map<String, Constructor<? extends View>> sConstructorMap
                = new ArrayMap<>();

        private final Object[] mConstructorArgs = new Object[2];

        public final View createView(View parent, final String name, @NonNull Context context,
                                     @NonNull AttributeSet attrs, boolean inheritContext,
                                     boolean readAndroidTheme, boolean readAppTheme, boolean wrapContext) {
            final Context originalContext = context;

            // We can emulate Lollipop's android:theme attribute propagating down the view hierarchy
            // by using the parent's context
            if (inheritContext && parent != null) {
                context = parent.getContext();
            }
            if (readAndroidTheme || readAppTheme) {
                // We then apply the theme on the context, if specified
                context = themifyContext(context, attrs, readAndroidTheme, readAppTheme);
            }
            if (wrapContext) {
                context = TintContextWrapper.wrap(context);
            }

            View view = null;

            // We need to 'inject' our tint aware Views in place of the standard framework versions
            switch (name) {
                case "TextView":
                    view = new AppCompatTextView(context, attrs);
                    break;
                case "ImageView":
                    view = new AppCompatImageView(context, attrs);
                    break;
                case "Button":
                    view = new AppCompatButton(context, attrs);
                    break;
                case "EditText":
                    view = new AppCompatEditText(context, attrs);
                    break;
                case "Spinner":
                    view = new AppCompatSpinner(context, attrs);
                    break;
                case "ImageButton":
                    view = new AppCompatImageButton(context, attrs);
                    break;
                case "CheckBox":
                    view = new AppCompatCheckBox(context, attrs);
                    break;
                case "RadioButton":
                    view = new AppCompatRadioButton(context, attrs);
                    break;
                case "CheckedTextView":
                    view = new AppCompatCheckedTextView(context, attrs);
                    break;
                case "AutoCompleteTextView":
                    view = new AppCompatAutoCompleteTextView(context, attrs);
                    break;
                case "MultiAutoCompleteTextView":
                    view = new AppCompatMultiAutoCompleteTextView(context, attrs);
                    break;
                case "RatingBar":
                    view = new AppCompatRatingBar(context, attrs);
                    break;
                case "SeekBar":
                    view = new AppCompatSeekBar(context, attrs);
                    break;
            }

            if (view == null && originalContext != context) {
                // If the original context does not equal our themed context, then we need to manually
                // inflate it using the name so that android:theme takes effect.
                view = createViewFromTag(context, name, attrs);
            }

            if (view != null) {
                // If we have created a view, check it's android:onClick
                checkOnClickListener(view, attrs);
            }

            return view;
        }

        private View createViewFromTag(Context context, String name, AttributeSet attrs) {
            if (name.equals("view")) {
                name = attrs.getAttributeValue(null, "class");
            }

            try {
                mConstructorArgs[0] = context;
                mConstructorArgs[1] = attrs;

                if (-1 == name.indexOf('.')) {
                    for (int i = 0; i < sClassPrefixList.length; i++) {
                        final View view = createView(context, name, sClassPrefixList[i]);
                        if (view != null) {
                            return view;
                        }
                    }
                    return null;
                } else {
                    return createView(context, name, null);
                }
            } catch (Exception e) {
                // We do not want to catch these, lets return null and let the actual LayoutInflater
                // try
                return null;
            } finally {
                // Don't retain references on context.
                mConstructorArgs[0] = null;
                mConstructorArgs[1] = null;
            }
        }

        /**
         * android:onClick doesn't handle views with a ContextWrapper context. This method
         * backports new framework functionality to traverse the Context wrappers to find a
         * suitable target.
         */
        private void checkOnClickListener(View view, AttributeSet attrs) {
            final Context context = view.getContext();

            if (!(context instanceof ContextWrapper) ||
                    (Build.VERSION.SDK_INT >= 15 && !ViewCompat.hasOnClickListeners(view))) {
                // Skip our compat functionality if: the Context isn't a ContextWrapper, or
                // the view doesn't have an OnClickListener (we can only rely on this on API 15+ so
                // always use our compat code on older devices)
                return;
            }

            final TypedArray a = context.obtainStyledAttributes(attrs, sOnClickAttrs);
            final String handlerName = a.getString(0);
            if (handlerName != null) {
                view.setOnClickListener(new DeclaredOnClickListener(view, handlerName));
            }
            a.recycle();
        }

        private View createView(Context context, String name, String prefix)
                throws ClassNotFoundException, InflateException {
            Constructor<? extends View> constructor = sConstructorMap.get(name);

            try {
                if (constructor == null) {
                    // Class not found in the cache, see if it's real, and try to add it
                    Class<? extends View> clazz = context.getClassLoader().loadClass(
                            prefix != null ? (prefix + name) : name).asSubclass(View.class);

                    constructor = clazz.getConstructor(sConstructorSignature);
                    sConstructorMap.put(name, constructor);
                }
                constructor.setAccessible(true);
                return constructor.newInstance(mConstructorArgs);
            } catch (Exception e) {
                // We do not want to catch these, lets return null and let the actual LayoutInflater
                // try
                return null;
            }
        }

        /**
         * Allows us to emulate the {@code android:theme} attribute for devices before L.
         */
        private static Context themifyContext(Context context, AttributeSet attrs,
                                              boolean useAndroidTheme, boolean useAppTheme) {
            final TypedArray a = context.obtainStyledAttributes(attrs, android.support.v7.appcompat.R.styleable.View, 0, 0);
            int themeId = 0;
            if (useAndroidTheme) {
                // First try reading android:theme if enabled
                themeId = a.getResourceId(android.support.v7.appcompat.R.styleable.View_android_theme, 0);
            }
            if (useAppTheme && themeId == 0) {
                // ...if that didn't work, try reading app:theme (for legacy reasons) if enabled
                themeId = a.getResourceId(android.support.v7.appcompat.R.styleable.View_theme, 0);

                if (themeId != 0) {
                    Log.i(LOG_TAG, "app:theme is now deprecated. "
                            + "Please move to using android:theme instead.");
                }
            }
            a.recycle();

            if (themeId != 0 && (!(context instanceof ContextThemeWrapper)
                    || ((ContextThemeWrapper) context).getThemeResId() != themeId)) {
                // If the context isn't a ContextThemeWrapper, or it is but does not have
                // the same theme as we need, wrap it in a new wrapper
                context = new ContextThemeWrapper(context, themeId);
            }
            return context;
        }

        /**
         * An implementation of OnClickListener that attempts to lazily load a
         * named click handling method from a parent or ancestor context.
         */
        private static class DeclaredOnClickListener implements View.OnClickListener {
            private final View mHostView;
            private final String mMethodName;

            private Method mResolvedMethod;
            private Context mResolvedContext;

            public DeclaredOnClickListener(@NonNull View hostView, @NonNull String methodName) {
                mHostView = hostView;
                mMethodName = methodName;
            }

            @Override
            public void onClick(@NonNull View v) {
                if (mResolvedMethod == null) {
                    resolveMethod(mHostView.getContext(), mMethodName);
                }

                try {
                    mResolvedMethod.invoke(mResolvedContext, v);
                } catch (IllegalAccessException e) {
                    throw new IllegalStateException(
                            "Could not execute non-public method for android:onClick", e);
                } catch (InvocationTargetException e) {
                    throw new IllegalStateException(
                            "Could not execute method for android:onClick", e);
                }
            }

            @NonNull
            private void resolveMethod(@Nullable Context context, @NonNull String name) {
                while (context != null) {
                    try {
                        if (!context.isRestricted()) {
                            final Method method = context.getClass().getMethod(mMethodName, View.class);
                            if (method != null) {
                                mResolvedMethod = method;
                                mResolvedContext = context;
                                return;
                            }
                        }
                    } catch (NoSuchMethodException e) {
                        // Failed to find method, keep searching up the hierarchy.
                    }

                    if (context instanceof ContextWrapper) {
                        context = ((ContextWrapper) context).getBaseContext();
                    } else {
                        // Can't search up the hierarchy, null out and fail.
                        context = null;
                    }
                }

                final int id = mHostView.getId();
                final String idText = id == View.NO_ID ? "" : " with id '"
                        + mHostView.getContext().getResources().getResourceEntryName(id) + "'";
                throw new IllegalStateException("Could not find method " + mMethodName
                        + "(View) in a parent or ancestor Context for android:onClick "
                        + "attribute defined on view " + mHostView.getClass() + idText);
            }
        }

}

使用

 public void skin(View view) {
        //换肤
       String skinPath= Environment.getExternalStorageDirectory().getAbsolutePath()+File.separator
                + "Download"+File.separator+"meinv.skin";
        int loadSkin = SkinManager.getInstance().loadSkin(skinPath);
    }

    public void skin1(View view) {
        //默认
        SkinManager.getInstance().restoreDefault();
    }

    public void skin2(View view) {
        //跳转
        Intent intent = new Intent(this, MainActivity.class);
        startActivity(intent);
    }

http://www.niftyadmin.cn/n/3648843.html

相关文章

javascript 符号_通过JavaScript了解Big O符号

javascript 符号If you’ve ever looked into getting a job as a developer you’ve probably come across this Google interview at some point and wondered ‘what the heck are they talking about?’. In this article, we’re going to explore what they mean throwi…

Windows系统一些计数器

Windows系统Windows -Processor指标名称指标描述指标范围指标单位CPU利用率&#xff08;% Processor Time&#xff09;% Processor Time指处理器执行非闲置线程时间的百分比。这个计数器设计成用来作为处理器活动的主要指示器。它通过在每个时间间隔中衡量处理器用于执行闲置处…

Git使用教程详解之四 服务器上的Git

服务器上的 Git 到目前为止&#xff0c;你应该已经学会了使用 Git 来完成日常工作。然而&#xff0c;如果想与他人合作&#xff0c;还需要一个远程的 Git 仓库。尽管技术上可以从个人的仓库里推送和拉取修改内容&#xff0c;但我们不鼓励这样做&#xff0c;因为一不留心就很容易…

使用JavaScript FileReader API读取和处理文件

Reading, writing and analyzing files is an essential component of software development. For security reasons, in JavaScript, we can’t directly access users’ files. If we had something like fs in Node.js, we could just steal documents from users! 读取&…

耍流氓式的保活service

QQ为什么一直常驻后台? &#xff08;白名单&#xff0c;双进程守护) 应用正在运行&#xff0c;这个时候内存不足回收杀进程 1.提高进程的优先级&#xff0c;其实就是减小进程的p->oomkilladj&#xff08;越小越重要&#xff09;&#xff0c;如启动Service调用startForeg…

[收藏]说声放弃太容易-Sunny

说声放弃太容易 原创&#xff1a;梁宁[Sunny]( 2004-04-16 14:14:16) 细草穿沙雪半消&#xff0c;吴官烟冷水迢迢。 梅花竹里无人见&#xff0c;一夜吹香过石桥。一个同事辞职了&#xff0c;她辞职的原因是不喜欢同单位的另外一个同事。她跑来和我说她将离去以及必须离去的原因…

如何安装svelte_Svelte的可读写商店入门

如何安装svelteIf you’re familiar with Redux or Vuex, then the Svelte stores offer a similar feature for state management. If your app is getting complicated, then it becomes difficult for components to relay data between themselves. Moving it to a global …

Android URI详解

就Android平台而言&#xff0c;URI主要分三个部分&#xff1a;scheme, authority and path。其中authority又分为host和port。格式如下&#xff1a; scheme://host:port/path 举个实际的例子&#xff1a; content://com.example.project:200/folder/subfolder/etc \---------/ …