IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 移动开发 -> matrix-android-lib -> 正文阅读

[移动开发]matrix-android-lib

目录

?1.IAppForeground前台监听接口

?2.IPlugin?接口,功能插件需要实现的接口

?3.Plugin 抽象插件实现

?4.PluginListener插件状态监听

?5.DefaultPluginListener插件状态监听默认实现

?6.Issue问题,可以细化todo

?7.IssuePublisher问题发布者,发布给OnIssueDetectListener

?8.FilePublisher用于一天之内只上报一次问题

?9.MatrixHandlerThread 开辟了一个单独线程处理,getDefaultHandler,getNewHandlerThread

?10.AppActiveMatrixDelegate 监听是否前台,获取场景,这里可以设置fragment名字todo,获取topactivity

?11.Matrix,Builder用于创建,单例模式


?1.IAppForeground前台监听接口

public interface IAppForeground {
    //是否是前台
    void onForeground(boolean isForeground);
}

?2.IPlugin?接口,功能插件需要实现的接口

//IPlugin接口,功能插件需要实现的接口
public interface IPlugin {

    Application getApplication();

    void init(Application application, PluginListener pluginListener);

    void start();

    void stop();

    void destroy();

    String getTag();

    void onForeground(boolean isForeground);
}

?3.Plugin 抽象插件实现

//Plugin 抽象插件实现
public abstract class Plugin implements IPlugin, IssuePublisher.OnIssueDetectListener, IAppForeground {
    private static final String TAG = "Matrix.Plugin";

    //状态
    public static final int PLUGIN_CREATE = 0x00;
    public static final int PLUGIN_INITED = 0x01;
    public static final int PLUGIN_STARTED = 0x02;
    public static final int PLUGIN_STOPPED = 0x04;
    public static final int PLUGIN_DESTROYED = 0x08;

    //监听器
    private PluginListener pluginListener;
    //Application
    private Application application;
    //isSupported默认是true
    private boolean isSupported = true;
    //最初是PLUGIN_CREATE
    private int status = PLUGIN_CREATE;

    @Override
    public void init(Application app, PluginListener listener) {
        if (application != null || pluginListener != null) {
            throw new RuntimeException("plugin duplicate init, application or plugin listener is not null");
        }
        status = PLUGIN_INITED;
        this.application = app;
        this.pluginListener = listener;
        AppActiveMatrixDelegate.INSTANCE.addListener(this);//用于监听是否是前台会调用onForeground
    }

    //上报问题
    @Override
    public void onDetectIssue(Issue issue) {
        if (issue.getTag() == null) {
            // set default tag
            issue.setTag(getTag());
        }
        issue.setPlugin(this);
        JSONObject content = issue.getContent();
        // add tag and type for default
        try {
            if (issue.getTag() != null) {
                content.put(Issue.ISSUE_REPORT_TAG, issue.getTag());
            }
            if (issue.getType() != 0) {
                content.put(Issue.ISSUE_REPORT_TYPE, issue.getType());
            }
            content.put(Issue.ISSUE_REPORT_PROCESS, MatrixUtil.getProcessName(application));
            content.put(Issue.ISSUE_REPORT_TIME, System.currentTimeMillis());

        } catch (JSONException e) {
            MatrixLog.e(TAG, "json error", e);
        }

        //MatrixLog.e(TAG, "detect issue:%s", issue);
        pluginListener.onReportIssue(issue);
    }

    @Override
    public Application getApplication() {
        return application;
    }

    @Override
    public void start() {
        if (isPluginDestroyed()) {
            throw new RuntimeException("plugin start, but plugin has been already destroyed");
        }

        if (isPluginStarted()) {
            throw new RuntimeException("plugin start, but plugin has been already started");
        }

        status = PLUGIN_STARTED;

        if (pluginListener == null) {
            throw new RuntimeException("plugin start, plugin listener is null");
        }
        pluginListener.onStart(this);
    }

    @Override
    public void stop() {
        if (isPluginDestroyed()) {
            throw new RuntimeException("plugin stop, but plugin has been already destroyed");
        }

        if (!isPluginStarted()) {
            throw new RuntimeException("plugin stop, but plugin is never started");
        }

        status = PLUGIN_STOPPED;

        if (pluginListener == null) {
            throw new RuntimeException("plugin stop, plugin listener is null");
        }
        pluginListener.onStop(this);
    }

    @Override
    public void destroy() {
        // destroy前先stop,stop first
        if (isPluginStarted()) {
            stop();
        }
        if (isPluginDestroyed()) {
            throw new RuntimeException("plugin destroy, but plugin has been already destroyed");
        }
        status = PLUGIN_DESTROYED;

        if (pluginListener == null) {
            throw new RuntimeException("plugin destroy, plugin listener is null");
        }
        pluginListener.onDestroy(this);
    }
    
    @Override
    public String getTag() {
        return getClass().getName();
    }

    //是否前台监听
    @Override
    public void onForeground(boolean isForeground) {

    }

    //是否前台获取
    public boolean isForeground() {
        return AppActiveMatrixDelegate.INSTANCE.isAppForeground();
    }


    public int getStatus() {
        return status;
    }

    public boolean isPluginStarted() {
        return (status == PLUGIN_STARTED);
    }

    public boolean isPluginStopped() {
        return (status == PLUGIN_STOPPED);
    }

    public boolean isPluginDestroyed() {
        return (status == PLUGIN_DESTROYED);
    }

    public boolean isSupported() {
        return isSupported;
    }

    public void unSupportPlugin() {
        isSupported = false;
    }

    public JSONObject getJsonInfo() {
        return new JSONObject();
    }

}

?4.PluginListener插件状态监听

//PluginListener插件状态监听
public interface PluginListener {
    void onInit(Plugin plugin);

    void onStart(Plugin plugin);

    void onStop(Plugin plugin);

    void onDestroy(Plugin plugin);

    void onReportIssue(Issue issue);
}

?5.DefaultPluginListener插件状态监听默认实现

public class DefaultPluginListener implements PluginListener {
    private static final String TAG = "Matrix.DefaultPluginListener";

    private final Context context;

    public DefaultPluginListener(Context context) {
        this.context = context;
    }

    @Override
    public void onInit(Plugin plugin) {
        MatrixLog.i(TAG, "%s plugin is inited", plugin.getTag());
    }

    @Override
    public void onStart(Plugin plugin) {
        MatrixLog.i(TAG, "%s plugin is started", plugin.getTag());
    }

    @Override
    public void onStop(Plugin plugin) {
        MatrixLog.i(TAG, "%s plugin is stopped", plugin.getTag());
    }

    @Override
    public void onDestroy(Plugin plugin) {
        MatrixLog.i(TAG, "%s plugin is destroyed", plugin.getTag());
    }

    @Override
    public void onReportIssue(Issue issue) {
        MatrixLog.i(TAG, "report issue content: %s", issue == null ? "" : issue);
    }

}

?6.Issue问题,可以细化todo

//ok 问题,可以细化todo
public class Issue {
    private int        type;
    private String     tag;
    private String     key;
    private JSONObject content;
    private Plugin     plugin;

    public static final String ISSUE_REPORT_TYPE    = "type";
    public static final String ISSUE_REPORT_TAG     = "tag";
    public static final String ISSUE_REPORT_PROCESS = "process";
    public static final String ISSUE_REPORT_TIME = "time";

    public Issue() {
    }

    public Issue(int type) {
        this.type = type;
    }

    public Issue(JSONObject content) {
        this.content = content;
    }

    public JSONObject getContent() {
        return content;
    }

    public void setContent(JSONObject content) {
        this.content = content;
    }


    @Override
    public String toString() {
        String strContent = "";
        if (null != content) strContent = content.toString();
        return String.format("tag[%s]type[%d];key[%s];content[%s]", tag, type, key, strContent);
    }

    public void setKey(String key) {
        this.key = key;
    }

    public void setTag(String tag) {
        this.tag = tag;
    }

    public int getType() {
        return type;
    }

    public String getKey() {
        return key;
    }

    public String getTag() {
        return tag;
    }

    public void setType(int type) {
        this.type = type;
    }

    public Plugin getPlugin() {
        return plugin;
    }

    public void setPlugin(Plugin plugin) {
        this.plugin = plugin;
    }
}

?7.IssuePublisher问题发布者,发布给OnIssueDetectListener

//IssuePublisher问题发布者,发布给OnIssueDetectListener
public class IssuePublisher {

    private final OnIssueDetectListener    mIssueListener;
    //用于标记这个问题是否已经发布了
    private final HashSet<String> mPublishedMap;

    public interface OnIssueDetectListener {
        void onDetectIssue(Issue issue);
    }

    public IssuePublisher(OnIssueDetectListener issueDetectListener) {
        mPublishedMap = new HashSet<>();
        this.mIssueListener = issueDetectListener;
    }

    protected void publishIssue(Issue issue) {
        if (mIssueListener == null) {
            throw new RuntimeException("publish issue, but issue listener is null");
        }
        if (issue != null) {
            mIssueListener.onDetectIssue(issue);
        }
    }

    protected boolean isPublished(String key) {
        if (key == null) {
            return false;
        }

        return mPublishedMap.contains(key);
    }

    protected void markPublished(String key) {
        if (key == null) {
            return;
        }

        mPublishedMap.add(key);
    }

    protected void unMarkPublished(String key) {
        if (key == null) {
            return;
        }
        mPublishedMap.remove(key);
    }
}

?8.FilePublisher用于一天之内只上报一次问题

public class FilePublisher extends IssuePublisher {
    private static final String TAG = "Matrix.FilePublisher";

    private final long                     mExpiredTime;//过期日期一天
    private final SharedPreferences.Editor mEditor;
    private final HashMap<String, Long>    mPublishedMap;//activity名字和发生泄漏的时间

    private final Context mContext;


    public FilePublisher(Context context, long expire, String tag, OnIssueDetectListener issueDetectListener) {
        super(issueDetectListener);
        this.mContext = context;
        mExpiredTime = expire;//1天过期日期
        final String spName = "Matrix_" + tag + MatrixUtil.getProcessName(context);
        SharedPreferences sharedPreferences = context.getSharedPreferences(spName, Context.MODE_PRIVATE);
        mPublishedMap = new HashMap<>();//activity名字和发生泄漏的时间
        long current = System.currentTimeMillis();
        mEditor = sharedPreferences.edit();
        HashSet<String> spKeys = null;
        if (null != sharedPreferences.getAll()) {
            spKeys = new HashSet<>(sharedPreferences.getAll().keySet());
        }
        if (null != spKeys) {
            for (String key : spKeys) {
                try {
                    long start = sharedPreferences.getLong(key, 0);
                    long costTime = current - start;
                    if (start <= 0 || costTime > mExpiredTime) {
                        mEditor.remove(key);//超时则删除
                    } else {
                        mPublishedMap.put(key, start);//没有的话,添加到map里
                    }
                } catch (ClassCastException e) {
                    MatrixLog.printErrStackTrace(TAG, e, "might be polluted - sp: %s, key: %s, value : %s", spName, key, sharedPreferences.getAll().get(key));
                }
            }
        }
        if (null != mEditor) {
            mEditor.apply();
        }
    }

    public void markPublished(String key, boolean persist) {
        if (key == null) {
            return;
        }
        if (mPublishedMap.containsKey(key)) {//如果含有则返回
            return;
        }
        final long now = System.currentTimeMillis();
        mPublishedMap.put(key, now);
        //如果保存文件里则保存到SharedPreferences里
        if (persist) {
            SharedPreferences.Editor e = mEditor.putLong(key, now);
            if (null != e) {
                e.apply();
            }
        }
    }

    @Override
    public void markPublished(String key) {
        markPublished(key, true);
    }

    @Override
    public void unMarkPublished(String key) {
        if (key == null) {
            return;
        }
        if (!mPublishedMap.containsKey(key)) {
            return;
        }
        mPublishedMap.remove(key);
        SharedPreferences.Editor e = mEditor.remove(key);
        if (null != e) {
            e.apply();
        }
    }

    @Override
    public boolean isPublished(String key) {
        if (!mPublishedMap.containsKey(key)) {
            return false;
        }
        long start = mPublishedMap.get(key);
        if (start <= 0 || (System.currentTimeMillis() - start) > mExpiredTime) {
            SharedPreferences.Editor e = mEditor.remove(key);
            if (null != e) {
                e.apply();
            }
            mPublishedMap.remove(key);
            return false;
        }
        return true;
    }

    public Context getContext() {
        return mContext;
    }
}

?9.MatrixHandlerThread 开辟了一个单独线程处理,getDefaultHandler,getNewHandlerThread

//MatrixHandlerThread 开辟了一个单独线程处理,getDefaultHandler,getNewHandlerThread
public class MatrixHandlerThread {
    private static final String TAG = "Matrix.HandlerThread";

    public static final String MATRIX_THREAD_NAME = "default_matrix_thread";

    /**
     * unite defaultHandlerThread for lightweight work,
     * if you have heavy work checking, you can create a new thread
     */
    private static volatile HandlerThread defaultHandlerThread;//开辟一个单独线程这个线程支持Handler,Looper.prepare();Looper.loop();
    private static volatile Handler defaultHandler;//单独线程的Handler
    private static volatile Handler defaultMainHandler = new Handler(Looper.getMainLooper());//main线程的
    private static HashSet<HandlerThread> handlerThreads = new HashSet<>();
    public static boolean isDebug = false;

    public static Handler getDefaultMainHandler() {
        return defaultMainHandler;
    }

    //获取默认的HandlerThread
    public static HandlerThread getDefaultHandlerThread() {
        synchronized (MatrixHandlerThread.class) {
            if (null == defaultHandlerThread) {
                defaultHandlerThread = new HandlerThread(MATRIX_THREAD_NAME);
                defaultHandlerThread.start();
                defaultHandler = new Handler(defaultHandlerThread.getLooper());
                defaultHandlerThread.getLooper().setMessageLogging(isDebug ? new LooperPrinter() : null);
                MatrixLog.w(TAG, "create default handler thread, we should use these thread normal, isDebug:%s", isDebug);
            }
            return defaultHandlerThread;
        }
    }

    public static Handler getDefaultHandler() {
        if (defaultHandler == null) {
            getDefaultHandlerThread();
        }
        return defaultHandler;
    }

    public static HandlerThread getNewHandlerThread(String name, int priority) {
        for (Iterator<HandlerThread> i = handlerThreads.iterator(); i.hasNext(); ) {
            HandlerThread element = i.next();
            if (!element.isAlive()) {
                i.remove();
                MatrixLog.w(TAG, "warning: remove dead handler thread with name %s", name);
            }
        }
        HandlerThread handlerThread = new HandlerThread(name);
        handlerThread.setPriority(priority);
        handlerThread.start();
        handlerThreads.add(handlerThread);
        MatrixLog.w(TAG, "warning: create new handler thread with name %s, alive thread size:%d", name, handlerThreads.size());
        return handlerThread;
    }

?10.AppActiveMatrixDelegate 监听是否前台,获取场景,这里可以设置fragment名字todo,获取topactivity

//AppActiveMatrixDelegate 监听是否前台,获取场景,这里可以设置fragment名字todo,获取topactivity
public enum AppActiveMatrixDelegate {

    INSTANCE;

    private static final String TAG = "Matrix.AppActiveDelegate";
    private final Set<IAppForeground> listeners = new HashSet();
    private boolean isAppForeground = false;
    private String visibleScene = "default";
    private Controller controller = new Controller();
    private boolean isInit = false;
    private String currentFragmentName;//todo
    private Handler handler;

    //step 1 init,注册了controller
    public void init(Application application) {
        if (isInit) {
            MatrixLog.e(TAG, "has inited!");
            return;
        }
        this.isInit = true;
        if (null != MatrixHandlerThread.getDefaultHandlerThread()) {
            this.handler = new Handler(MatrixHandlerThread.getDefaultHandlerThread().getLooper());
        }
        application.registerComponentCallbacks(controller);
        application.registerActivityLifecycleCallbacks(controller);
    }

    public String getCurrentFragmentName() {
        return currentFragmentName;
    }

    /**
     * must set after {@link Activity#onStart()}
     *
     * @param fragmentName
     */
    public void setCurrentFragmentName(String fragmentName) {
        MatrixLog.i(TAG, "[setCurrentFragmentName] fragmentName:%s", fragmentName);
        this.currentFragmentName = fragmentName;
        updateScene(fragmentName);
    }

    public String getVisibleScene() {
        return visibleScene;
    }

    //step 3
    private void onDispatchForeground(String visibleScene) {
        if (isAppForeground || !isInit) {
            return;
        }

        MatrixLog.i(TAG, "onForeground... visibleScene[%s]", visibleScene);
        handler.post(new Runnable() {
            @Override
            public void run() {
                isAppForeground = true;
                synchronized (listeners) {
                    for (IAppForeground listener : listeners) {
                        listener.onForeground(true);
                    }
                }
            }
        });

    }

    //step 4
    private void onDispatchBackground(String visibleScene) {
        if (!isAppForeground || !isInit) {
            return;
        }

        MatrixLog.i(TAG, "onBackground... visibleScene[%s]", visibleScene);

        handler.post(new Runnable() {
            @Override
            public void run() {
                isAppForeground = false;
                synchronized (listeners) {
                    for (IAppForeground listener : listeners) {
                        listener.onForeground(false);
                    }
                }
            }
        });


    }

    public boolean isAppForeground() {
        return isAppForeground;
    }

    public void addListener(IAppForeground listener) {
        synchronized (listeners) {
            listeners.add(listener);
        }
    }

    public void removeListener(IAppForeground listener) {
        synchronized (listeners) {
            listeners.remove(listener);
        }
    }


    //step 2
    private final class Controller implements Application.ActivityLifecycleCallbacks, ComponentCallbacks2 {

        @Override
        public void onActivityStarted(Activity activity) {
            updateScene(activity);
            onDispatchForeground(getVisibleScene());
        }


        @Override
        public void onActivityStopped(Activity activity) {
            if (getTopActivityName() == null) {
                onDispatchBackground(getVisibleScene());
            }
        }


        @Override
        public void onActivityCreated(Activity activity, Bundle savedInstanceState) {

        }

        @Override
        public void onActivityDestroyed(Activity activity) {

        }

        @Override
        public void onActivityResumed(Activity activity) {

        }

        @Override
        public void onActivityPaused(Activity activity) {

        }

        @Override
        public void onActivitySaveInstanceState(Activity activity, Bundle outState) {

        }

        @Override
        public void onConfigurationChanged(Configuration newConfig) {

        }

        @Override
        public void onLowMemory() {

        }

        @Override
        public void onTrimMemory(int level) {
            MatrixLog.i(TAG, "[onTrimMemory] level:%s", level);
            if (level == TRIM_MEMORY_UI_HIDDEN && isAppForeground) { // fallback
                onDispatchBackground(visibleScene);
            }
        }
    }

    private void updateScene(Activity activity) {
        visibleScene = activity.getClass().getName();
    }

    private void updateScene(String currentFragmentName) {
        StringBuilder ss = new StringBuilder();
        ss.append(TextUtils.isEmpty(currentFragmentName) ? "?" : currentFragmentName);
        visibleScene = ss.toString();
    }

    //获取顶部activity名字
    public static String getTopActivityName() {
        long start = System.currentTimeMillis();
        try {
            Class activityThreadClass = Class.forName("android.app.ActivityThread");
            Object activityThread = activityThreadClass.getMethod("currentActivityThread").invoke(null);
            Field activitiesField = activityThreadClass.getDeclaredField("mActivities");
            activitiesField.setAccessible(true);

            Map<Object, Object> activities;
            if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) {
                activities = (HashMap<Object, Object>) activitiesField.get(activityThread);
            } else {
                activities = (ArrayMap<Object, Object>) activitiesField.get(activityThread);
            }
            if (activities.size() < 1) {
                return null;
            }
            for (Object activityRecord : activities.values()) {
                Class activityRecordClass = activityRecord.getClass();
                Field pausedField = activityRecordClass.getDeclaredField("paused");
                pausedField.setAccessible(true);
                if (!pausedField.getBoolean(activityRecord)) {
                    Field activityField = activityRecordClass.getDeclaredField("activity");
                    activityField.setAccessible(true);
                    Activity activity = (Activity) activityField.get(activityRecord);
                    return activity.getClass().getName();
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            long cost = System.currentTimeMillis() - start;
            MatrixLog.d(TAG, "[getTopActivityName] Cost:%s", cost);
        }
        return null;
    }

}

?11.Matrix,Builder用于创建,单例模式

//Matrix,Builder用于创建,单例模式
public class Matrix {
    private static final String TAG = "Matrix.Matrix";


    private static volatile Matrix sInstance;

    private final HashSet<Plugin> plugins;
    private final Application application;
    private final PluginListener pluginListener;

    private Matrix(Application app, PluginListener listener, HashSet<Plugin> plugins) {
        this.application = app;
        this.pluginListener = listener;
        this.plugins = plugins;
        AppActiveMatrixDelegate.INSTANCE.init(application);
        for (Plugin plugin : plugins) {
            plugin.init(application, pluginListener);
            pluginListener.onInit(plugin);
        }

    }

    public static void setLogIml(MatrixLog.MatrixLogImp imp) {
        MatrixLog.setMatrixLogImp(imp);
    }

    public static boolean isInstalled() {
        return sInstance != null;
    }

    //测试里用了
    public static Matrix init(Matrix matrix) {
        if (matrix == null) {
            throw new RuntimeException("Matrix init, Matrix should not be null.");
        }
        synchronized (Matrix.class) {
            if (sInstance == null) {
                sInstance = matrix;
            } else {
                MatrixLog.e(TAG, "Matrix instance is already set. this invoking will be ignored");
            }
        }
        return sInstance;
    }

    //获取实例
    public static Matrix with() {
        if (sInstance == null) {
            throw new RuntimeException("you must init Matrix sdk first");
        }
        return sInstance;
    }

    public void startAllPlugins() {
        for (Plugin plugin : plugins) {
            plugin.start();
        }
    }

    public void stopAllPlugins() {
        for (Plugin plugin : plugins) {
            plugin.stop();
        }
    }

    public void destroyAllPlugins() {
        for (Plugin plugin : plugins) {
            plugin.destroy();
        }
    }

    public Application getApplication() {
        return application;
    }

    public HashSet<Plugin> getPlugins() {
        return plugins;
    }

    public Plugin getPluginByTag(String tag) {
        for (Plugin plugin : plugins) {
            if (plugin.getTag().equals(tag)) {
                return plugin;
            }
        }
        return null;
    }

    public <T extends Plugin> T getPluginByClass(Class<T> pluginClass) {
        String className = pluginClass.getName();
        for (Plugin plugin : plugins) {
            if (plugin.getClass().getName().equals(className)) {
                return (T) plugin;
            }
        }
        return null;
    }

    //创建Matrix的Builder
    public static class Builder {
        private final Application application;
        private PluginListener pluginListener;

        private HashSet<Plugin> plugins = new HashSet<>();

        public Builder(Application app) {
            if (app == null) {
                throw new RuntimeException("matrix init, application is null");
            }
            this.application = app;
        }

        public Builder plugin(Plugin plugin) {
            String tag = plugin.getTag();
            for (Plugin exist : plugins) {
                if (tag.equals(exist.getTag())) {
                    throw new RuntimeException(String.format("plugin with tag %s is already exist", tag));
                }
            }
            plugins.add(plugin);
            return this;
        }

        public Builder pluginListener(PluginListener pluginListener) {
            this.pluginListener = pluginListener;
            return this;
        }

        public Matrix build() {
            if (pluginListener == null) {
                pluginListener = new DefaultPluginListener(application);
            }
            return new Matrix(application, pluginListener, plugins);
        }

    }
}

  移动开发 最新文章
Vue3装载axios和element-ui
android adb cmd
【xcode】Xcode常用快捷键与技巧
Android开发中的线程池使用
Java 和 Android 的 Base64
Android 测试文字编码格式
微信小程序支付
安卓权限记录
知乎之自动养号
【Android Jetpack】DataStore
上一篇文章      下一篇文章      查看所有文章
加:2021-10-27 12:56:47  更:2021-10-27 12:57:33 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/24 1:11:18-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码