Android常见问题总结(三)

1.Https是如何实现的

htttps是基于ssl(Secure Sockets Layer安全套接层)的http协议,https协议在http协议与tcp协议之间增加一层安全层,数据在网络传输之前,会先进行加密,再进行传输。

在这里插入图片描述
https的主要流程如下:
在这里插入图片描述
2.Android事件流程和OnTouchListener的关系
3.双指缩放拖动大图

4.客户端网络安全实现
1.使用https
2.设置网络安全配置文件

5.Android应用保活
1.单进程守护、双进程守护
原理:利用其它app与自身绑定Service
2.开启前台进程的Service,监听系统锁屏广播制造“1”像素的悬浮窗
3.循环在后台播放一段无声音乐

6.RemoteViews的实现和使用场景
RemoteViews 的作用是在其他进程中显示并更新 View 界面。主要用于通知栏和桌面小部件上。

1 通知栏的使用
我们使用 NotificationCompat.Builder.build() 来创建一个通知,然后调用 NotificationManager.notify() 来显示通知栏,在需要自定义通知栏 UI 时,就需要 RemoteViews 来帮忙了。

第一步:设置通知栏的 UI 布局文件
第二步:使用 RemoteViews 绑定布局
注意:Android 8.0 (API 26) 以上的手机需要 NotificationChannel 实现通知栏。

@TargetApi(26)
void testRemoteViewsInNotification() {
    Intent intent = new Intent(this, NotiActivity.class);
    PendingIntent pendingIntent = PendingIntent
            .getActivity(this, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);

    String id = "my_channel_01";
    CharSequence name = "channel";
    String description = "description";
    int importance = NotificationManager.IMPORTANCE_DEFAULT;
    NotificationChannel channel = new NotificationChannel(id, name, importance);
    channel.setDescription(description);
    channel.enableLights(true);
    channel.setLightColor(Color.RED);
    channel.enableVibration(true);
    // 偶数表示静止时间,奇数表示振动时间
    channel.setVibrationPattern(new long[]{100, 200, 300, 400, 500, 400, 300, 200, 400});

    NotificationManager manager =
            (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
    manager.createNotificationChannel(channel);

    RemoteViews remoteViews = new RemoteViews(getPackageName(), R.layout.layout_notification);
    remoteViews.setTextViewText(R.id.msg, "demo");
    remoteViews.setOnClickPendingIntent(R.id.notification, pendingIntent);

    Notification notificaton = new Notification.Builder(this, id)
            .setAutoCancel(false)
            .setContentTitle("title")
            .setContentText("text")
            .setSmallIcon(R.mipmap.ic_launcher_round)
            .setOngoing(true)
            .setCustomContentView(remoteViews)
            .setWhen(System.currentTimeMillis())
            .build();
    manager.notify(1, notificaton);
}

2 桌面小部件的使用
第一步:定义小部件界面

<!-- res/layout/my_app_widget.xml -->
<RelativeLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@color/colorAccent"
    android:padding="8dp">

    <TextView
        android:id="@+id/my_app_widget_view_text"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerHorizontal="true"
        android:layout_centerVertical="true"
        android:layout_margin="8dp"
        android:text="@string/app_name"
        android:textSize="24sp"
        android:textStyle="bold|italic"/>

</RelativeLayout>

第二步:定义小部件配置信息

<!-- res/xml/my_app_widget_info.xml -->
<appwidget-provider
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:initialLayout="@layout/my_app_widget"
    android:minHeight="84dp"
    android:minWidth="84dp"
    android:updatePeriodMillis="86400000">

</appwidget-provider>

第三步:定义小部件的实现类

public class MyAppWidgetProvider extends AppWidgetProvider {
    public static final String CLICK_ACTION = "com.mindle.androidtest.action.CLICK";

    static void updateAppWidget(Context context, AppWidgetManager appWidgetManager,
                                int appWidgetId) {
        // Construct the RemoteViews object
        RemoteViews remoteViews = new RemoteViews(context.getPackageName(), R.layout.my_app_widget);

        Intent onIntent = new Intent();
        onIntent.setAction(CLICK_ACTION);
        PendingIntent onPendingIntent = PendingIntent.getBroadcast(context, 0, onIntent, 0);
        remoteViews.setOnClickPendingIntent(R.id.my_app_widget_view_text, onPendingIntent);

        // Instruct the widget manager to update the widget
        appWidgetManager.updateAppWidget(appWidgetId, remoteViews);
    }

    @Override
    public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
        // There may be multiple widgets active, so update all of them
        for (int appWidgetId : appWidgetIds) {
            updateAppWidget(context, appWidgetManager, appWidgetId);
        }
    }

    @Override
    public void onEnabled(Context context) {
        // Enter relevant functionality for when the first widget is created
    }

    @Override
    public void onDisabled(Context context) {
        // Enter relevant functionality for when the last widget is disabled
    }

    @Override
    public void onReceive(Context context, Intent intent) {
        super.onReceive(context, intent);
        RemoteViews remoteViews = new RemoteViews(context.getPackageName(), R.layout.my_app_widget);
        if (Objects.equals(intent.getAction(), CLICK_ACTION)) {
            Toast.makeText(context, "hello, widget!", Toast.LENGTH_SHORT).show();

            AppWidgetManager manager = AppWidgetManager.getInstance(context);

            ComponentName thisName = new ComponentName(context, MyAppWidgetProvider.class);

            manager.updateAppWidget(thisName, remoteViews);
        }
    }
}

最后一步:注册桌面小部件

<receiver android:name=".NewAppWidget">
    <intent-filter>
        <action android:name="com.mindle.androidtest.action.CLICK"/>
        <action android:name="android.appwidget.action.APPWIDGET_UPDATE"/>
    </intent-filter>

    <meta-data
        android:name="android.appwidget.provider"
        android:resource="@xml/new_app_widget_info"/>
</receiver>

工作原理
RemoteViews 是实现了 Parcelable 接口的,可以通过 Binder 传递到其他进程中。比如,前面提到的两个使用场景中,通知栏和桌面小部件分别由 NotificationManager 和 AppWidgetManager 管理,它们 通过 Binder 分别和 SystemServer 进程中的 NotificationManagerService 以及 AppWidgetManagerService 通信,实现了跨进程传递 RemoteViews。

RemoteViews 的一系列 set 操作是通过将更新操作包装成 Action(Parcelable)对象集合保存下来。Action 最重要的是 reply 方法,通过反射或别的方法,在其它进程中更新 RemoteViews 中控件的布局。

RemoteViews 中有 apply 方法和 reApply 方法。apply 方法会加载布局并更新界面,而 reApply 方法仅仅更新界面。通知栏和桌面小部件在初始化界面时会调用 apply 方法,在后续的更新界面时会调用 reApply 方法。

注意:RemoteViews 的 apply 方法会加载布局,如下面代码所示,其中 rv.getLayoutId() 得到的 RemoteViews 的布局文件 ID 是在构造函数中传进去的,即 RemoteViews(packageName, layoutId)。因为 RemoteViews 常用于在其他进程中显示并更新 View 界面,但是如果两个进程属于不同的应用,那么它们的资源 ID 很可能是不一致的。在应用 A 中使用资源 ID1 创建了 RemoteViews 传给应用 B 之后,apply 方法中直接在应用 B 中用 ID1 来绘制布局很可能会出错。

解决上面问题的办法是,手动加载布局,然后用 reApply 方法来更新界面。如下面的例子所示,通过约定好的布局文件名来获取资源 ID,然后再手动加载布局。

int layoutId = getResources()
        .getIdentifier("layout_notification", "layout", getPackageName());
View v = getLayoutInflater().inflate(layoutId, layout, false);
remoteViews.reapply(this, v);
layout.addView(v);

7.RecyclerView的绘制步骤和复用机制

绘制流程:
onMeasure:

protected void onMeasure(int widthSpec, int heightSpec) {
    /** 1.判断mLayout是否为null,为null执行defaultOnMeasure,return **/
    if (mLayout == null) {
        defaultOnMeasure(widthSpec, heightSpec);
        return;
    }
    /** 2.isAutoMeasureEnabled()一般都是true **/
    if (mLayout.isAutoMeasureEnabled()) {
        final int widthMode = MeasureSpec.getMode(widthSpec);
        final int heightMode = MeasureSpec.getMode(heightSpec);
        /** 3.调用了LayoutManager的onMeasure **/
        mLayout.onMeasure(mRecycler, mState, widthSpec, heightSpec);
        /** 4.当RecyclerView的宽高都是EXACTLY时,skipMeasure为true,那么接下来直接return **/
        final boolean measureSpecModeIsExactly =
                widthMode == MeasureSpec.EXACTLY && heightMode == MeasureSpec.EXACTLY;
        if (measureSpecModeIsExactly || mAdapter == null) {
            return;
        }
        /** 5.mState.mLayoutStep为STEP_START,这里会执行dispatchLayoutStep1(); **/
        if (mState.mLayoutStep == State.STEP_START) {
            dispatchLayoutStep1();
        }
        // set dimensions in 2nd step. Pre-layout should happen with old dimensions for
        // consistency
        mLayout.setMeasureSpecs(widthSpec, heightSpec);
        mState.mIsMeasuring = true;
        /** 6.执行dispatchLayoutStep2();这个方法很重要,下面继续分析 **/
        dispatchLayoutStep2();
        // now we can get the width and height from the children.
        mLayout.setMeasuredDimensionFromChildren(widthSpec, heightSpec);
        // if RecyclerView has non-exact width and height and if there is at least one child
        // which also has non-exact width & height, we have to re-measure.
        if (mLayout.shouldMeasureTwice()) {
            mLayout.setMeasureSpecs(
                    MeasureSpec.makeMeasureSpec(getMeasuredWidth(), MeasureSpec.EXACTLY),
                    MeasureSpec.makeMeasureSpec(getMeasuredHeight(), MeasureSpec.EXACTLY));
            mState.mIsMeasuring = true;
            dispatchLayoutStep2();
            // now we can get the width and height from the children.
            mLayout.setMeasuredDimensionFromChildren(widthSpec, heightSpec);
        }
    } else {
        if (mHasFixedSize) {
            mLayout.onMeasure(mRecycler, mState, widthSpec, heightSpec);
            return;
        }
        // custom onMeasure
        if (mAdapterUpdateDuringMeasure) {
            startInterceptRequestLayout();
            onEnterLayoutOrScroll();
            processAdapterUpdatesAndSetAnimationFlags();
            onExitLayoutOrScroll();
            if (mState.mRunPredictiveAnimations) {
                mState.mInPreLayout = true;
            } else {
                // consume remaining updates to provide a consistent state with the layout pass.
                mAdapterHelper.consumeUpdatesInOnePass();
                mState.mInPreLayout = false;
            }
            mAdapterUpdateDuringMeasure = false;
            stopInterceptRequestLayout(false);
        } else if (mState.mRunPredictiveAnimations) {
            // If mAdapterUpdateDuringMeasure is false and mRunPredictiveAnimations is true:
            // this means there is already an onMeasure() call performed to handle the pending
            // adapter change, two onMeasure() calls can happen if RV is a child of LinearLayout
            // with layout_width=MATCH_PARENT. RV cannot call LM.onMeasure() second time
            // because getViewForPosition() will crash when LM uses a child to measure.
            setMeasuredDimension(getMeasuredWidth(), getMeasuredHeight());
            return;
        }
        if (mAdapter != null) {
            mState.mItemCount = mAdapter.getItemCount();
        } else {
            mState.mItemCount = 0;
        }
        startInterceptRequestLayout();
        mLayout.onMeasure(mRecycler, mState, widthSpec, heightSpec);
        stopInterceptRequestLayout(false);
        mState.mInPreLayout = false; // clear
    }
}

1.mLayout为null时,只是绘制了RecyclerView的宽高,这也就是不设置setLayoutManager导致RecyclerView显示为空白的原因。
2.isAutoMeasureEnabled() 是否为自动测量,返回为true,则执行RecyclerView的autoMeasure,否则执行layoutManager的onMeasure。LinearLayoutManager的构造方法里自动设置为true。
3.onMeasure主要调用了dispatchLayoutStep1和dispatchLayoutStep2,onLayout会调用dispatchLayoutStep3
dispatchLayoutStep1: Adapter的更新; 决定该启动哪种动画; 保存当前View的信息(getLeft(), getRight(), getTop(), getBottom()等); 如果有必要,先跑一次布局并将信息保存下来。
dispatchLayoutStep2:真正对子View做布局的地方。
dispatchLayoutStep3:为动画保存View的相关信息; 触发动画; 相应的清理工作。

RecyclerView 滑动场景下的回收复用涉及到的结构体两个:mCachedViews 和 RecyclerViewPool。

mCachedViews 优先级高于 RecyclerViewPool,回收时,最新的 ViewHolder 都是往 mCachedViews 里放,如果它满了,那就移出一个扔到 ViewPool 里好空出位置来缓存最新的 ViewHolder。

复用时,也是先到 mCachedViews 里找 ViewHolder,但需要各种匹配条件,概括一下就是只有原来位置的卡位可以复用存在 mCachedViews 里的 ViewHolder,如果 mCachedViews 里没有,那么才去 ViewPool 里找。

在 ViewPool 里的 ViewHolder 都是跟全新的 ViewHolder 一样,只要 type 一样,有找到,就可以拿出来复用,重新绑定下数据即可。

复用的流程图如下:
在这里插入图片描述
8.Finalize机制:
finalize是Object下的方法,任何类都可以重写,jdk9以后使用cleaner机制代替了finalize机制。

参考博客:
RemoteViews的原理及使用场景:https://blog.csdn.net/weixin_40255793/article/details/81482266
RecyclerView的绘制流程:https://www.jianshu.com/p/f91b41c8f487
RecyclerView的复用机制:https://www.cnblogs.com/dasusu/p/7746946.html

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值