前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >「drone」开发记录

「drone」开发记录

作者头像
AnRFDev
发布2021-02-01 15:19:52
3770
发布2021-02-01 15:19:52
举报
文章被收录于专栏:AnRFDev

斜体文字被切掉了一块

TextView.setText的时候在右边加上一个空格。

或者string.xml中添加 来占位。

代码语言:javascript
复制
<string name="dp_page_ready">READY&#x200A;</string>

或者在TextView.setText的时候加上一个空格。

动态给LinearLayout添加子View

一列27个自定义view,如果要写到xml里就太麻烦了。 在Java代码中新建子View,设置LayoutParams,然后添加到LinearLayout里。

代码语言:javascript
复制
private void initGrids() {
    final int bigGridHeightPx = (int) dpToPx(10);
    final int bigGrid2MarginVerticalPx = (int) dpToPx(3);
    final int smallGridHeightPx = (int) dpToPx(6); // 这里有27个格点
    final int smallGridMarginBotPx = (int) dpToPx(1);

    LinearLayout linearLayoutLeft1 = findViewById(R.id.dp_page_g_field_left1);
    LinearLayout linearLayoutLeft2 = findViewById(R.id.dp_page_g_field_left2);
    mLeftGridList = new ArrayList<>();
    GridsHorView g1 = new GridsHorView(this);
    GridsHorView g2 = new GridsHorView(this);
    GridsHorView g3 = new GridsHorView(this);

    LinearLayout.LayoutParams p1 = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, bigGridHeightPx);
    LinearLayout.LayoutParams p2 = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, bigGridHeightPx);
    p2.setMargins(0, bigGrid2MarginVerticalPx, 0, bigGrid2MarginVerticalPx);
    linearLayoutLeft1.addView(g1, p1);
    linearLayoutLeft1.addView(g2, p2);
    linearLayoutLeft1.addView(g3, p1);
    mLeftGridList.addAll(Arrays.asList(g1, g2, g3));

    for (int i = 0; i < 27; i++) {
        GridsHorView smallGrid = new GridsHorView(this);
        smallGrid.setAlphaValue((int) (255 * (1 - 0.02 * i)));
        LinearLayout.LayoutParams sp = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, smallGridHeightPx);
        sp.setMargins(0, 0, 0, smallGridMarginBotPx);
        mLeftGridList.add(smallGrid);
        linearLayoutLeft2.addView(smallGrid, sp);
    }

    for (GridsHorView g : mLeftGridList) {
        g.setOri(GridsHorView.Ori.RIGHT_TO_LEFT);
        g.disableMode();
        g.setCubeCount(1);
    }

    // 初始化右边(P2)的格子...
}

private float dpToPx(float dp) {
    return dp * getResources().getDisplayMetrics().density;
}

获取当前WiFi的名字

需要定位权限。

代码语言:javascript
复制
public static String getWiFiName(Context context) {
    String wifiId = "WIFI_NAME_NOT_FOUND";
    try {
        WifiManager wifiMgr = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
        WifiInfo info = wifiMgr.getConnectionInfo();
        wifiId = info != null ? info.getSSID() : null;
    } catch (Exception e) {
        e.printStackTrace();
    }
    if (!TextUtils.isEmpty(wifiId) && wifiId.startsWith("\"")) {
        wifiId = wifiId.substring(1); // 删去前面那个引号
    }
    return wifiId;
}

这个方法适用于判断WiFi名称的前缀。

检查权限的方法

代码语言:javascript
复制
protected void checkPermission(String[] permissions, final int reqCode) {
    List<String> perList = new ArrayList<>();
    for (String p : permissions) {
        if (PackageManager.PERMISSION_GRANTED != ContextCompat.checkSelfPermission(this, p)) {
            LL.e("没有权限: " + p);
            perList.add(p);
        }
    }
    if (!perList.isEmpty()) {
        String[] per = new String[perList.size()];
        for (int i = 0; i < per.length; i++) {
            per[i] = perList.get(i);
        }
        ActivityCompat.requestPermissions(this, per, reqCode);
    }
}

设置Click监听器

应用在Activity中,给一堆view设置同一个监听器

代码语言:javascript
复制
// 设置点击监听器
protected void setOnClickListeners(View.OnClickListener l, View... views) {
    for (View v : views) {
        v.setOnClickListener(l);
    }
}

protected void setOnClickListeners(View.OnClickListener l, int... resIds) {
    for (int r : resIds) {
        findViewById(r).setOnClickListener(l);
    }
}

设置字体

先把字体加载好。

代码语言:javascript
复制
protected void setTvPangMenAndItalic(int... tvResIds) {
    for (int i : tvResIds) {
        ((TextView) findViewById(i)).setTypeface(AppControl.getPangMenTf(), Typeface.ITALIC);
    }
}

protected void setTvPangMenAndItalic(TextView... tvs) {
    for (TextView t : tvs) {
        t.setTypeface(AppControl.getPangMenTf(), Typeface.ITALIC);
    }
}

收起软键盘

代码语言:javascript
复制
void hideSoftKeyboard() {
    try {
        InputMethodManager inputMgr = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
        inputMgr.hideSoftInputFromWindow(getCurrentFocus().getWindowToken(), InputMethodManager.HIDE_NOT_ALWAYS);
    } catch (Exception e) {
        LL.e("rustApp", e);
    }
}

判断网络是否有连接

代码语言:javascript
复制
protected boolean isNetworkAvailable(Context context) {
    ConnectivityManager connectivity = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);

    if (connectivity == null) {
        return false;
    } else {
        NetworkInfo[] info = connectivity.getAllNetworkInfo();
        if (info != null) {
            for (NetworkInfo anInfo : info) {
                if (anInfo.getState() == NetworkInfo.State.CONNECTED) {
                    return true;
                }
            }
        }
    }
    return false;
}

判断某个App是否安装

获取PackageManager通过包名来判断某个App是否安装。 但是有的手机在获取PackageManager的时候就能抛出异常。

代码语言:javascript
复制
protected boolean appInstalledOrNot(String uri) {
    try {
        PackageManager pm = getPackageManager();
        pm.getPackageInfo(uri, PackageManager.GET_ACTIVITIES);
        return true;
    } catch (Exception e) {
        LL.e("appInstalledOrNot: " + uri, e);
    }
    return false;
}

总而言之这并不是个很好的办法。

性能优化

一些性能优化的处理措施和思考。

给UI线程更多的CPU资源

数据库的操作放在子线程中进行。数据库线程也可能会和UI线程争抢CPU的时间片。 假设数据库要删除大量数据(比如1万条)。 那么我们可以尝试在数据库处理了某个数量(例如1千)的操作后,sleep一下,给UI线程让出CPU时间。 但现在一般都是多核手机,具体效果有待考量。

log记录工具

把log写到文件里。用RandomAccessFile与MappedByteBuffer写日志到文件中。任务处理放在子线程中,由HandlerThread来管理。

代码语言:javascript
复制
public class LL {

    public static abstract class Level {
        public static final String D = "D"; // 普通debug
        public static final String W = "W"; // 警告
        public static final String E = "E"; // 错误
    }

    private static String defTag = "App";

    private static boolean showLogcat = true;
    private static boolean writeFile = true;

    // 注意申请SD卡读写权限
    private static String logFileDir = Environment.getExternalStorageDirectory().getAbsolutePath() +
            File.separator + "rust" + File.separator + "logs";

    private static String fileName;

    private static List<LogListener> listenerList = new ArrayList<>();

    private static HandlerThread handlerThread;
    private static Handler writerHandler;

    private static final int LOG_FILE_GROW_SIZE = 1024 * 10; // log文件每次增长的大小
    private static long gCurrentLogPos = 0;                  // log文件当前写到的位置 - 注意要单线程处理

    /**
     * 使用前必须调用此方法进行准备
     *
     * @param context 建议传入applicationContext
     * @param fileDir 存放log文件的目录
     */
    public static void prepare(Context context, @NonNull String fileDir, String logFilePrefix) {
        gCurrentLogPos = 0;
        if (TextUtils.isEmpty(fileDir)) {
            if (Environment.getExternalStorageState().equals(
                    Environment.MEDIA_MOUNTED)) {
                logFileDir = Environment.getExternalStorageDirectory().getAbsolutePath() +
                        File.separator + "rust" + File.separator + "logs";
            } else {
                logFileDir = context.getFilesDir().getAbsolutePath() +
                        File.separator + "rust" + File.separator + "logs";
            }
        } else {
            logFileDir = fileDir;
        }
        if (null == handlerThread) {
            handlerThread = new HandlerThread("LL");
            handlerThread.start();
        }
        writerHandler = new Handler(handlerThread.getLooper());
        fileName = logFilePrefix + "_" + System.currentTimeMillis() + ".txt";
        Log.d(defTag, "[prepare] file: " + fileName);
    }

    public static String getLogFileDir() {
        return logFileDir;
    }

    public static String getFileName() {
        return fileName;
    }

    // 退出
    public static void quit() {
        if (writerHandler != null) {
            writerHandler.removeCallbacksAndMessages(null);
        }
        if (handlerThread != null) {
            handlerThread.quit();
        }
    }

    public static void setDefTag(String t) {
        LL.defTag = t;
    }

    public static void setWriteFile(boolean w) {
        LL.writeFile = w;
    }

    public static void d(String content) {
        d(defTag, content, writeFile);
    }

    public static void d(String tag, String content) {
        d(tag, content, writeFile);
    }

    public static void dn(String content) {
        d(defTag, content, false);
    }

    // 不写到文件中
    public static void dn(String tag, String content) {
        d(tag, content, false);
    }

    public static void d(String tag, String content, boolean write) {
        if (showLogcat) {
            Log.d(tag, content);
        }
        tellLog(Level.D, tag, content);
        if (write) {
            if (writerHandler != null) {
                writerHandler.post(new WriteRunnable(tag, content));
            }
        }
    }

    // log级别 WARN - w
    public static void w(String content) {
        w(defTag, content, writeFile);
    }

    public static void w(String tag, String content) {
        w(tag, content, writeFile);
    }

    // 不写到文件中
    public static void wn(String content) {
        w(defTag, content, false);
    }

    // 不写到文件中
    public static void wn(String tag, String content) {
        w(tag, content, false);
    }

    public static void w(String tag, String content, boolean write) {
        if (showLogcat) {
            Log.w(tag, content);
        }
        tellLog(Level.W, tag, content);
        if (write) {
            if (writerHandler != null) {
                writerHandler.post(new WriteRunnable(tag, content));
            }
        }
    }

    public static void e(String content) {
        e(defTag, content);
    }

    public static void e(String tag, String content) {
        e(tag, content, writeFile);
    }

    public static void e(String tag, Exception e) {
        e(tag, e.getMessage(), writeFile);
    }

    // 只打log  不写文件
    public static void en(String tag, String content) {
        e(tag, content, false);
    }

    public static void e(String tag, String content, boolean write) {
        if (showLogcat) {
            Log.e(tag, content);
        }
        tellLog(Level.E, tag, content);
        if (write) {
            if (writerHandler != null) {
                writerHandler.post(new WriteRunnable(tag, content));
            }
        }
    }

    private static void tellLog(String level, String tag, String content) {
        if (null != listenerList) {
            for (LogListener l : listenerList) {
                l.onLog(level, tag, content);
            }
        }
    }

    public static void addListener(LogListener l) {
        if (null == listenerList) {
            listenerList = new ArrayList<>();
        }
        listenerList.add(l);
    }

    public static void removeListener(LogListener l) {
        if (null != listenerList) {
            listenerList.remove(l);
        }
    }

    static class WriteRunnable implements Runnable {
        String mmTag;
        String mmContent;

        WriteRunnable(String tag, String content) {
            this.mmTag = tag;
            this.mmContent = content;
        }

        @Override
        public void run() {
            SimpleDateFormat logTimeFormat = new SimpleDateFormat("HH:mm:ss.SSS", Locale.CHINA);
            String logContent = logTimeFormat.format(new Date()) + " [" + mmTag + "] " + mmContent + "\r\n";
            try {
                File dir = new File(logFileDir);
                if (!dir.exists()) {
                    boolean mk = dir.mkdirs();
                    Log.d(defTag, "make dir " + mk);
                }
                File eFile = new File(logFileDir + File.separator + fileName);
                byte[] strBytes = logContent.getBytes();
                try {
                    RandomAccessFile randomAccessFile = new RandomAccessFile(eFile, "rw");
                    MappedByteBuffer mappedByteBuffer;
                    final int inputLen = strBytes.length;
                    if (!eFile.exists()) {
                        boolean nf = eFile.createNewFile();
                        Log.d(defTag, "new log file " + nf);
                        mappedByteBuffer = randomAccessFile.getChannel().map(FileChannel.MapMode.READ_WRITE, gCurrentLogPos, LOG_FILE_GROW_SIZE);
                    } else {
                        mappedByteBuffer = randomAccessFile.getChannel().map(FileChannel.MapMode.READ_WRITE, gCurrentLogPos, inputLen);
                    }
                    if (mappedByteBuffer.remaining() < inputLen) {
                        mappedByteBuffer = randomAccessFile.getChannel().map(FileChannel.MapMode.READ_WRITE, gCurrentLogPos, LOG_FILE_GROW_SIZE + inputLen);
//                        Log.d(defTag, "run: grow size ");
                    }
//                    Log.d(defTag, "run: gCurrentLogPos: " + gCurrentLogPos + ", pos: " + mappedByteBuffer.position() + ", remaining: " + mappedByteBuffer.remaining());
                    mappedByteBuffer.put(strBytes);
                    gCurrentLogPos += inputLen;
                } catch (Exception e) {
                    Log.e(defTag, "WriteRunnable run: ", e);
                    if (!eFile.exists()) {
                        boolean nf = eFile.createNewFile();
                        Log.d(defTag, "new log file " + nf);
                    }
                    FileOutputStream os = new FileOutputStream(eFile, true);
                    os.write(logContent.getBytes());
                    os.flush();
                    os.close();
                }
            } catch (Exception e) {
                e.printStackTrace();
                Log.e(defTag, "写log文件出错: ", e);
            }
        }
    }

}

监听器

代码语言:javascript
复制
public abstract class LogListener {
    public abstract void onLog(String level, String tag, String content);
}

自定义View

一些自定义的view,比如一些折线图,条形图。

折线图 ColorAutoLineChart

单独一条折线,可自动缩放Y轴高度。使用FloatBuffer来存储数据。

代码语言:javascript
复制
public class ColorAutoLineChart extends View {

    private static final String TAG = "AppColorAutoLineChart";

    private float yMax = 1856f;
    private float yMin = -1024f;

    // 图表线条在view顶部留出的间距
    float viewYStart = 2;
    float axisTextSize = 7;

    private int onShowPointsCount = 256;  // 当前显示的数据个数
    private int cacheMaxPoint = 9000;     // 数据存储最大个数

    float axisLineWid = 1f; // 坐标轴线条宽度
    int dataLineWid = 2;

    // 数据线颜色
    private int dataColor = Color.WHITE;

    private float xStep = 1.0f;
    private float viewWidth;
    private float viewHeight;
    private float botLeftXOnView = 0; // 图表左下点在view中的x坐标
    private float botLeftYOnView = 0;
    private float originYToBottom = 20; // 图表原点距离view底部的距离

    private FloatBuffer dataBuffer;

    private Paint bgPaint;
    private Paint linePaint;
    private Paint wavePaint;
    Path wavePath = new Path(); // 用来画渐变色
    int waveTopColor = Color.parseColor("#f65212");

    public ColorAutoLineChart(Context context) {
        this(context, null);
    }

    public ColorAutoLineChart(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public ColorAutoLineChart(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init(context);
    }

    public void addData(int[] data) {
        for (int i : data) {
            addData(i);
        }
    }

    public void addData(float data) {
        dataBuffer.put(data);
        if (dataBuffer.position() > (dataBuffer.capacity() * 2 / 3)) {
            float[] bufferArr = dataBuffer.array();
            System.arraycopy(bufferArr, dataBuffer.position() - cacheMaxPoint, bufferArr, 0, cacheMaxPoint);
            dataBuffer.position(cacheMaxPoint);
//            Log.d(TAG, "把当前数据移动到buffer起始位置 " + dataBuffer);
        }
        invalidate();
    }

    private void init(Context context) {
        dataBuffer = FloatBuffer.allocate(3 * cacheMaxPoint); // 分配3倍的空间
        bgPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        linePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        wavePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        bgPaint.setStrokeWidth(axisLineWid);
        bgPaint.setStyle(Paint.Style.STROKE);
        linePaint.setStrokeWidth(dataLineWid);
        linePaint.setStyle(Paint.Style.STROKE);
        linePaint.setColor(dataColor);

        botLeftXOnView = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 0, context.getResources().getDisplayMetrics());
        originYToBottom = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 20, context.getResources().getDisplayMetrics());
        viewYStart = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 20, context.getResources().getDisplayMetrics());
        axisLineWid = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 1, context.getResources().getDisplayMetrics());
        axisTextSize = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, 8, context.getResources().getDisplayMetrics());
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        viewWidth = getWidth();
        viewHeight = getHeight();
        botLeftYOnView = viewHeight - originYToBottom;
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        canvas.drawColor(Color.TRANSPARENT);
        xStep = (viewWidth - botLeftXOnView) / (onShowPointsCount - 1);

        int dataStartIndexInBuffer = 0; // 数据在buffer中的起始下标
        if (dataBuffer.position() > onShowPointsCount) {
            dataStartIndexInBuffer = dataBuffer.position() - onShowPointsCount;
        }
        float[] bufferArr = dataBuffer.array();
        float maxData = bufferArr[0];
        float minData = bufferArr[0];
        for (int i = dataStartIndexInBuffer; i < dataBuffer.position(); i++) {
            float cur = bufferArr[i];
            if (cur < minData) {
                minData = cur;
            } else if (cur > maxData) {
                maxData = cur;
            }
        }

        drawWave(canvas, dataStartIndexInBuffer);
    }

    private void drawWave(Canvas canvas, int dataStartIndexInBuffer) {
        wavePath.reset();
        final float yDataRange = yMax - yMin;
        final float yAxisRangeOnView = botLeftYOnView - viewYStart;
        final float yDataStep = yAxisRangeOnView / yDataRange;

        float[] dataArr = dataBuffer.array();
        float maxData = dataArr[dataStartIndexInBuffer];

        float waveStartX = botLeftXOnView;
        float waveStartY = getYL(dataArr[dataStartIndexInBuffer], yDataStep);
        wavePath.moveTo(waveStartX, waveStartY);

        for (int i = dataStartIndexInBuffer; i < dataBuffer.position() - 1; i++) {
            float curData = dataArr[i];
            float nextData = dataArr[i + 1];
            wavePath.lineTo(botLeftXOnView + (i - dataStartIndexInBuffer + 1) * xStep, getYL(nextData, yDataStep));
            canvas.drawLine(botLeftXOnView + (i - dataStartIndexInBuffer) * xStep, getYL(curData, yDataStep),
                    botLeftXOnView + (i - dataStartIndexInBuffer + 1) * xStep, getYL(nextData, yDataStep),
                    linePaint);
            maxData = Math.max(maxData, nextData);
        }
        wavePath.lineTo(viewWidth, viewHeight);
        wavePath.lineTo(botLeftXOnView, viewHeight);
        wavePath.lineTo(waveStartX, waveStartY);
        wavePath.close();
        wavePaint.setShader(new LinearGradient(0, getYL(maxData, yDataStep), 0, viewHeight, waveTopColor, Color.TRANSPARENT, Shader.TileMode.CLAMP));
        canvas.drawPath(wavePath, wavePaint);
    }

    private float getYL(final float yData, float yDataStep) {
        return botLeftYOnView - (yData - yMin) * yDataStep;
    }

}

后台用户行为记录

最开始设计后台服务的时候,并没有考虑到太多的记录功能。仅仅记录了用户登录行为。 今后应该记录更详细的。例如获取用户信息,时间,客户端类型,userID等等。获取用户信息是否成功,可作为缓存登录的依据。

后台可以记录的用户行为,例如获取用户信息,用户查看飞行记录列表,用户查看飞行记录详情,用户点赞。

WebView设置

webview不显示图片的问题。LL后加载https的网页,默认会不加载http的资源。需要设置。

代码语言:javascript
复制
webSettings.setBlockNetworkImage(false);
webSettings.setMixedContentMode(WebSettings.MIXED_CONTENT_ALWAYS_ALLOW);

方法调用记录调用原因

调用某个方法的时候,比如中止某项功能。可以在log上记录一些原因,方便debug。

Glide

设定播放gif的次数

代码语言:javascript
复制
Glide.get(getApplicationContext()).setMemoryCategory(MemoryCategory.NORMAL);
Glide.with(this).asGif().listener(new RequestListener<GifDrawable>() {
    @Override
    public boolean onLoadFailed(@Nullable GlideException e, Object model, Target<GifDrawable> target, boolean isFirstResource) {
        return false;
    }

    @Override
    public boolean onResourceReady(GifDrawable resource, Object model, Target<GifDrawable> target, DataSource dataSource, boolean isFirstResource) {
        resource.setLoopCount(1);
        return false;
    }
}).load(R.drawable.app_start_up).into(imageView);
imageView.setOnApplyWindowInsetsListener(new View.OnApplyWindowInsetsListener() {
    @Override
    public WindowInsets onApplyWindowInsets(View v, WindowInsets insets) {
        MyAppControl.setPhoneStatusBarHeight(insets.getSystemWindowInsetTop());
        return insets;
    }
});
本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2018-07-29,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 斜体文字被切掉了一块
  • 动态给LinearLayout添加子View
  • 获取当前WiFi的名字
  • 检查权限的方法
  • 设置Click监听器
  • 设置字体
  • 收起软键盘
  • 判断网络是否有连接
  • 判断某个App是否安装
  • 性能优化
    • 给UI线程更多的CPU资源
    • log记录工具
    • 自定义View
      • 折线图 ColorAutoLineChart
      • 后台用户行为记录
      • WebView设置
      • 方法调用记录调用原因
      • Glide
        • 设定播放gif的次数
        相关产品与服务
        数据库
        云数据库为企业提供了完善的关系型数据库、非关系型数据库、分析型数据库和数据库生态工具。您可以通过产品选择和组合搭建,轻松实现高可靠、高可用性、高性能等数据库需求。云数据库服务也可大幅减少您的运维工作量,更专注于业务发展,让企业一站式享受数据上云及分布式架构的技术红利!
        领券
        问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档