Skip to content

钩子专属

  • 首页
  • 安卓技术
  • 安卓教程

All posts by oshook

  • 首页   /  
  • 作者: oshook
  • ( 页面3 )
安卓技术 6 月 21,2023

三、Activity的启动模式

启动模式是Android用来控制Activity活动栈行为的一种标志

作用:

  1. 对栈的操作能够不按顺序处理
  2. 多次启动同一个Activity,系统会创建多个实例,白白浪费内存

有两种方式:清单中launchMode和Intent对象调用setFlags方法

区别

优先级:动态指定方式比清单的优先级高
限定范围:清单配置无法指定FLAG_ACTIVITY_CLEAR_TOP 标识,动态指定无法指定singleInstance 模式
setFlags可以在不同时候对同一个Activity运用不同的启动模式

注:setFlags当前设置当前生效,也只会在当前生效,过后还是按照清单的配置走

四种启动模式

1、standard:默认标准模式:
每次启动一个Activity都会又一次创建一个新的实例入栈,无论这个实例是否存在
2、singleTop:栈顶复用模式:
须要创建的Activity已经处于栈顶时,此时会直接复用栈顶的Activity。不会再创建新的Activity;
若须要创建的Activity不处于栈顶,此时会又一次创建一个新的Activity入栈,同Standard模式一样
生命周期:若情况一中栈顶的Activity被直接复用时,它的onCreate、onStart不会被系统调用,由于它并没有发生改变。
可是一个新的方法 onNewIntent会被回调(Activity被正常创建时不会回调此方法)

    @Override
protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
setIntent(intent);
initData(); //在这getIntent().getStringExtra()获取值
initView(); //在这重新设置新的ui
}
注意:需要重新setIntent(intent),否则还是获取的是老的数据  

3、singleTask:栈内复用模式,
若须要创建的Activity已经处于栈中时,此时不会创建新的Activity,而是将存在栈中的Activity上面的其他Activity所有销毁,使它成为栈顶。
生命周期:同SingleTop 模式中的情况一同样。仅仅会调用一次Activity中的 onNewIntent方法
4、singleInstance:单实例模式
此模式的Activity仅仅能单独位于一个任务栈中
这个经常使用于系统中的应用,比如Launch、锁屏键的应用等等,整个系统中仅仅有一个!所以在我们的应用中一般不会用到

Activity的常见Flags

1、FLAG_ACTIVITY_NEW_TASK
作用是为Activity指定 “SingleTask”启动模式。跟在AndroidMainfest.xml指定效果同样。

2、FLAG_ACTIVITY_SINGLE_TOP
作用是为Activity指定 “SingleTop”启动模式,跟在AndroidMainfest.xml指定效果同样。

3、FLAG_ACTIVITY_CLEAN_TOP
作用是销毁目标Activity和它之上的所有Activity,重新创建目标Activity

4、FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS
具有此标记位的Activity不会出如今历史Activity的列表中,使用场景:当某些情况下我们不希望用户通过历史列表回到Activity时,此标记位便体现了它的效果。
它等同于在xml中指定Activity的属性:android:excludeFromRecents=”true”

浅谈下为什么有时在启动Activity时需要加FLAG_ACTIVITY_NEW_TASK

报这个错是在Activity上下文之外启动的。
在Receiver中使用context的startActivity方法的话,这个方法源码在执行真正的调用之前会检查一下有没有设置这个FLAG_ACTIVITY_NEW_TASK的标志,没有设置的话就报上面所说的那个异常。
那么为什么我们在Activity中直接startActivity方法就不会报这个异常呢?这是因为Activity类在自己的实现中已经覆盖了父类的startActivity方法去除了这个检查

FLAG_ACTIVITY_NEW_TASK的意义:

一般来说当我们从launcher中启动一个应用进入到ActivityA中,系统会为这个应用生成一个新任务堆栈并置于前台,ActivityA被放入栈底,之后从ActivityA启动另一个ActivityB,如果不设置什么附加属性,ActivityB默认也放到和ActivityA这个堆栈中,这样当你按返回时,B出栈,A呈现出来了,这个应该很好理解。
那现在假如ActivityA启动一个Service或者发一个广播,这都是后台的活,和我们的任务栈没有关系,现在假如我们在广播中需要启动一个Activity,当然需要为这个Activity指定或分配一个任务栈,FLAG_ACTIVITY_NEW_TASK的意义就是这个

四种启动模式的生命周期

standard:
第一次进入:onCreate onStart
在栈顶再次进入: onCreate onStart
不在栈顶再次进入:onCreate onStart

singleTop:
第一次进入:onCreate onStart
在栈顶再次进入:onNewIntent
不在栈顶再次进入:onCreate onStart
按home键 再次进入:onRestart onStart

singleTask:
第一次进入:onCreate onStart
在栈顶再次进入:onNewIntent
不在栈顶再次进入:onNewIntent onRestart onStart
按home键再次进入:onRestart onStart

singleInstance:
第一次进入:onCreate onStart
在栈顶再次进入: onNewIntent
不在栈顶再次进入:onNewIntent onRestart onStart
按home键再次进入:onRestart onStart

参考文档:https://blog.csdn.net/elisonx/article/details/80397519

作者 oshook
安卓技术 6 月 21,2023

二、App启动速度优化

为什么会白屏

系统的进程没有,直到此开始,创建了应用程序的进程此过程。系统进程会显示空白屏幕,直到应用程序完成呈现活动。

启动速度由以下引起的:

1、Application的onCreate流程,对于大型的APP来说,通常会在这里做大量的通用组件的初始化操作;
建议:很多第三方SDK都放在Application初始化,我们可以放到用到的地方才进行初始化操作。

2、Activity的onCreate流程,特别是UI的布局与渲染操作,如果布局过于复杂很可能导致严重的启动性能问题;
建议:Activity仅初始化那些立即需要的对象,xml布局减少冗余或嵌套布局。

启动速度优化方案

先明确作为普通应用,App进程的创建等环节我们是无法主动控制的,可以优化的也就是Application、Activity创建以及回调等过程。
上面说到直到应用程序完成呈现活动,我们能做的就是尽快呈现活动

1、启动加速之主题切换

 

start_app_bg.xml



    
        
    


    


    



其中android:opacity=”opaque”参数是为了防止在启动的时候出现背景的闪烁

在启动的时候,会先展示一个界面,这个界面就是Manifest中设置的Style,等Activity加载完毕后,再去加载Activity的界面,而在Activity的界面中,我们将主题重新设置为正常的主题,从而产生一种快的感觉。不过如上文总结这种方式其实并没有真正的加速启动过程,而是通过交互体验来优化了展示的效果

2、启动加速之避免过多的初始化操作

注意检查在Application以及首屏Activity中我们做了什么
Application中主要做了各种三方组件的初始化:
1、考虑异步初始化三方组件,不阻塞主线程;

2、延迟部分三方组件的初始化;实际上我们粗粒度的把所有三方组件都放到异步任务里,可能会出现WorkThread中尚未初始化完毕但MainThread中已经使用的错误,因此这种情况建议延迟到使用前再去初始化
(可以试下,是否报错,报错后看该初始化是否能到使用前再去初始化)

3、用TraceView启动加速之定位问题

首先选择跟踪范围,在想要根据的代码片段之间使用以下两句代码

Debug.startMethodTracing("hello");

Debug.stopMethodTracing();

生成的traceview文件会自动放在SDCARD上,没有SDCARD卡会出现异常,所以使用这种方式需要确保应用的AndroidMainfest.xml中的SD卡的读写权限是打开的,其中hello是traceview文件的名字

然后用adb导出traceview文件
adb pull sdcard/hello.trace C:UserslwfDesktop

然后启动Android Device Monitor–>File–>openFile,打开traceview文件即可

关于应用启动加速,一般从以下几个方面来入手:

利用主题快速显示界面;
异步初始化组件;
梳理业务逻辑,延迟初始化组件、操作;
去掉无用代码、重复逻辑等。

参考链接:https://www.jianshu.com/p/c2d7b76ff063

作者 oshook
安卓技术 6 月 21,2023

一、ARoute

简单使用

// 在支持路由的页面上添加注解(必选)
// 这里的路径需要注意的是至少需要有两级,/xx/xx

@Route(path = "/test/SecondActivity")
public class SecondActivity extends AppCompatActivity {

    @Autowired
    private String name;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_second);
    
    ARouter.getInstance().inject(this);
    }
}

发起路由操作

ARouter.getInstance().build("/test/SecondActivity").navigation();

跳转带参数

ARouter.getInstance().build("/test/SecondActivity")
.withLong("key1", 666L)
.withString("key3", "888")
.withObject("key4", new Test("Jack", "Rose"))
.navigation();

页面拦截

先定义一个拦截器@Interceptor
第一种用法

    @Interceptor(priority = 8, name = "测试用拦截器")
public class TestInterceptor implements IInterceptor
{
@Override
public void process(Postcard postcard, InterceptorCallback callback)
{
//callback.onContinue(postcard);
callback.onInterrupt(new RuntimeException("我觉得有点异常"));
}

@Override
public void init(Context context)
{

}
}

@Route(path = “/test/SecondActivity”,priority = 8)

ARouter.getInstance().build("/test/SecondActivity").navigation(FirstActivity.this, new NavigationCallback()
{

@Override
public void onInterrupt(Postcard postcard)
{
Log.e("FirstActivity", "onInterrupt" + (postcard != null ? postcard.toString() : "null"));
}
});

第二种
拦截器1:Test1Interceptor

@Interceptor(priority = 5)
public class Test1Interceptor implements IInterceptor {
    @Override
    public void process(Postcard postcard, InterceptorCallback callback) {
        Log.e("testService", Test1Interceptor.class.getName() + " has process.");
        //拦截跳转,进行一些处理
        if (postcard.getPath().equals("/test/test1")) {
        Log.e("testService", Test1Interceptor.class.getName() + " 进行了拦截处理!");
    }
        callback.onContinue(postcard);
    }
    
    @Override
    public void init(Context context) {
    Log.e("testService", Test1Interceptor.class.getName() + " has init.");
    }
}

ARouter.getInstance().build("/test/test1").navigation(this, new NavCallback() {
            @Override
            public void onFound(Postcard postcard) {
                Log.e("testService", "找到了");
            }

            @Override
            public void onLost(Postcard postcard) {
                Log.e("testService", "找不到了");
            }

            @Override
            public void onArrival(Postcard postcard) {
                Log.e("testService", "跳转完了");
            }

            @Override
            public void onInterrupt(Postcard postcard) {
                Log.e("testService", "被拦截了");
            }
        });

拦截器注意事项:
1.定义多个拦截器的时候,priority的值不能定义一样的,只要其中两个拦截器的优先值一样,编译时会报错。

2.在拦截器的process()方法中,如果你即没有调用callback.onContinue(postcard)方法也没有调用callback.onInterrupt(exception)方法,那么不再执行后续的拦截器,需等待300s(默认值,可设置改变)的时间,才能抛出拦截器中断。

3.拦截器的process()方法以及带跳转的回调中的onInterrupt(Postcard postcard)方法,均是在分线程中执行的,如果需要做一些页面的操作显示,必须在主线程中执行。

没有调callback.onContinue(postcard)即被拦截器拦截了

分组管理

ARouter对页面路由路径的管理是分组做的,默认我们写的路径最前面的就是组名,例如:
@Route(path = “/test/SecondActivity”) 组名就是test,这也解释了之前的路径为什么要至少写两级,因为要组名加具体路径,分组只有在分组中的某一个路径第一次被访问的时候,该分组才会被初始化

注意:ARouter允许一个module中存在多个分组,但是不允许多个module中存在相同的分组,会导致映射文件冲突

参考链接
https://www.jianshu.com/p/46d174f37e82
https://blog.csdn.net/u014068277/article/details/81269474

作者 oshook
安卓技术 6 月 21,2023

内联扩展函数

1、内联扩展函数之let
使用结构
object.let{
it.todo()//在函数体内使用it替代object对象去访问其公有的属性和方法
…
}
作用:对象的调用方法的扩展写法
在函数块内可以通过 it 指代该对象。返回值为函数块的最后一行或指定return表达式
适用场景一:let函数处理需要针对一个可null的对象统一做判空处理
场景二:需要去明确一个变量所处特定的作用域范围内可以使用
比如:
原代码:
mVideoPlayer?.setVideoView(activity.course_video_view)
mVideoPlayer?.setControllerView(activity.course_video_controller_view)
mVideoPlayer?.setCurtainView(activity.course_video_curtain_view)
用let
mVideoPlayer?.let {
it.setVideoView(activity.course_video_view)
it.setControllerView(activity.course_video_controller_view)
it.setCurtainView(activity.course_video_curtain_view)
}
(?是mVideoPlayer非null时使用)

2、内联函数之with
with(object){
//todo
}
作用:函数内直接用对方的方法或者变量
它是将某对象作为函数的参数,在函数块内可以通过 this 指代该对象。返回值为函数块的最后一行或指定return表达式
适用场景:同一时刻多个地方调用了对方的方法。适用于调用同一个类的多个方法时,可以省去类名重复,直接调用类的方法即

可,经常用于Android中RecyclerView中onBinderViewHolder中,数据model的属性映射到UI上
示例代码:
override fun onBindViewHolder(holder: ViewHolder, position: Int){
val item = getItem(position)?: return

with(item){

  holder.tvNewsTitle.text = StringUtils.trimToEmpty(titleEn)//原来是item.titleEn
   holder.tvNewsSummary.text = StringUtils.trimToEmpty(summary)
   holder.tvExtraInf.text = "难度:$gradeInfo | 单词数:$length | 读后感: $numReviews"
   ...   

}

}

3、内联扩展函数之run
object.run{
//todo
}
run函数实际上可以说是let和with两个函数的结合体
适用场景:适用于let,with函数任何场景。因为run函数是let,with两个函数结合体,准确来说它弥补了let函数在函数体内必须

使用it参数替代对象,在run函数中可以像with函数一样可以省略,直接访问实例的公有属性和方法,另一方面它弥补了with函

数传入对象判空问题,在run函数中可以像let函数一样做判空处理
上面代码改良
override fun onBindViewHolder(holder: ViewHolder, position: Int){

getItem(position)?.run{
holder.tvNewsTitle.text = StringUtils.trimToEmpty(titleEn)
holder.tvNewsSummary.text = StringUtils.trimToEmpty(summary)
holder.tvExtraInf = “难度:gradeInfo | 单词数:length | 读后感: $numReviews”
…

}

}

4、内联扩展函数之apply
整体作用功能和run函数很像,唯一不同点就是它返回的值是对象本身,而run函数是一个闭包形式返回,返回的是最后一行的值

。正是基于这一点差异它的适用场景稍微与run函数有点不一样。apply一般用于一个对象实例初始化的时候,需要对对象中的属

性进行赋值。或者动态inflate出一个XML的View的时候需要给View绑定数据也会用到,这种情景非常常见。特别是在我们开发中

会有一些数据model向View model转化实例化的过程中需要用到

5、内联扩展函数之also
适用于let函数的任何场景,also函数和let很像,只是唯一的不同点就是let函数最后的返回值是最后一行的返回值而also函数

的返回值是返回当前的这个对象。一般可用于多个扩展函数链式调用

原文章地址https://blog.csdn.net/u013064109/article/details/78786646#t0

作者 oshook
安卓技术 6 月 21,2023

融云(三)—–自定义列表会话

需求:在消息列表显示其他内容入口,如心动列表,最近来访,小助 手等,点击跳到相应界面

效果图:

列表.png

如效果图所示:小纸条,心动列表等都是自定义的会话。
实现做法有两个:

  1. app服务器创对应的targetId发相应内容给客户端
  2. 客户端本地自己创建会话
    我推荐用客户端本地自己创建,因为
  1. 节省融云每天的发信息流量数,特别app用户多的,这成本就大了
  2. 用户如果换手机也是能正常显示的
  3. 如果出现问题,排查问题或者还想在上面操作,会方便很多

反正我是客户端能做的事,尽量不要接口弄

步骤1:创建会话

关键方法:Conversation.obtain
示例代码:

 public static Conversation createConversation(String targetId, int title) {
    return Conversation.obtain(Conversation.ConversationType.SYSTEM, targetId, MyApplication.getInstance().getString(title));
}

参数说明:targetId任意(最好与ios协定好),title如上面的心动列表。
至于采用Conversation.ConversationType.SYSTEM是为了和private普通用户聊天区别开

步骤2:添加会话到会话列表中

关键方法:重写getConversationList
示例代码:

`public class ConversationListFragmentEx extends ConversationListFragment {
//心动
private boolean isLove;
//小纸条
private boolean isNotes;
//邀请
private boolean isAuthInvite;
//婚恋
private boolean isMatch;
//微信下载
private boolean isWxDown;
//最近来访
private boolean isRecentVisit;


@Override
public void getConversationList(Conversation.ConversationType[] ctc, final IHistoryDataResultCallback> callback,boolean isLoadMore) {
    RongIMClient.getInstance().getConversationList(new RongIMClient.ResultCallback>() {
        public void onSuccess(List cs) {
            if (getActivity() != null && !getActivity().isFinishing()) {
                if (callback != null) {
                    ArrayList csl = new ArrayList();
                    if (null != cs && cs.size() > 0)
                        for (Conversation c : cs) {
                            if (c == null) continue;
                            String targetId = c.getTargetId();
                            if (targetId.contains(ImType.BUSINESS) || isNoMyCus(targetId, c.getConversationType()))
                                continue;
                            if (targetId.contains(ImType.MYCUS)) dealFlag(targetId);
                            

                            //添加需要的会话
                            csl.add(c);
                        }
                    //自定义会话列表的处理
                    if (!isLove)
                        csl.add(ImUtils.createConversation(ImType.HEART_BEAT, R.string.im_love_name));
                    if (!isNotes)
                        csl.add(ImUtils.createConversation(ImType.SLIP, R.string.im_notes_name));
                    if (!isWxDown)
                        csl.add(ImUtils.createConversation(ImType.WECHAT_DOWNLOAD, R.string.im_wx_name));
                    if (!isAuthInvite)
                        csl.add(ImUtils.createConversation(ImType.AUTH_INVITE, R.string.im_invite_auth_name));
                    if(InfoUtils.isMarriageIsOpen()){
                        if (!isRecentVisit)
                            csl.add(ImUtils.createConversation(ImType.RECENT_VISIT, R.string.im_recents_name));
                        // 3.0版本后是否显示“红娘牵线服务”
                        if (!isMatch && InfoUtils.getMarriageEntryShow())
                            csl.add(ImUtils.createConversation(ImType.MATCH_SERVICE, R.string.im_maker));
                    }
                    if(!isGreet){
                        csl.add(ImUtils.createConversation(ImType.GREET_MSG,R.string.im_greet_msg));
                    }

                    callback.onResult(csl);
                }
            }
        }

        public void onError(RongIMClient.ErrorCode e) {
            if (callback != null) {
                callback.onError();
            }
        }
    }, ctc);
}


private void dealFlag(String targetId) {
    switch (targetId) {
        case ImType.HEART_BEAT:
            isLove = true;
            break;
        case ImType.SLIP:
            isNotes = true;
            break;
        case ImType.WECHAT_DOWNLOAD:
            isWxDown = true;
            break;
        case ImType.AUTH_INVITE:
            isAuthInvite = true;
            break;
        case ImType.RECENT_VISIT:
            isRecentVisit = true;
            break;
        case ImType.MATCH_SERVICE:
            isMatch = true;
            break;
        
    }
}


}

解析:添加自定义的会话到会话列表集合中,做下标志,防止添加多次。可能有一两个变量没有,删掉即可,重在理解

步骤3:处理会话显示

关键:自定义Provider
示例代码:

@ConversationProviderTag(conversationType = "system", portraitPosition = 3)

public class MyCusConversationProvider extends SystemConversationProvider {
@Override
public void bindView(View view, int position, UIConversation uc) {
    ViewHolder vh = (ViewHolder) view.getTag();
    vh.tv_content.setTextColor(Color.parseColor("#aaaaaa"));

    switch (uc.getConversationTargetId()) {
        case ImType.HEART_BEAT:
            //心动
            vh.aiv_img.setResource("", R.mipmap.ic_record);
            vh.tv_name.setText(R.string.im_love_name);
            vh.tv_content.setText(R.string.im_love_content);
            vh.tv_conversation_time.setText("");
            break;
        case ImType.SLIP:
            //小纸条
            vh.aiv_img.setResource("", R.mipmap.ic_message);
            vh.tv_name.setText(R.string.im_notes_name);
            vh.tv_content.setText(R.string.im_notes_content);
            vh.tv_conversation_time.setText("");
            break;
        case ImType.WECHAT_DOWNLOAD:
            //微信下载
            vh.aiv_img.setResource("", R.mipmap.ic_record1);
            vh.tv_name.setText(R.string.im_wx_name);
            vh.tv_content.setText(R.string.im_wx_content);
            vh.tv_conversation_time.setText("");
            break;
        case ImType.AUTH_INVITE:
            //邀请认证
            vh.aiv_img.setResource("", R.mipmap.ic_invitations);
            vh.tv_name.setText(R.string.im_invite_auth_name);
            vh.tv_content.setText(R.string.im_invite_auth_content);
            vh.tv_conversation_time.setText("");
            break;
        case ImType.RECENT_VISIT:
            //最近来访
            vh.aiv_img.setResource("", R.mipmap.ic_im_visit);
            vh.tv_name.setText(R.string.im_recents_name);
            vh.tv_content.setText(R.string.im_recents_content);
            vh.tv_conversation_time.setText("");
            break;
        case ImType.MATCH_SERVICE:
            //红娘牵线服务
            vh.aiv_img.setResource("", R.mipmap.ic_matcher_service);
            vh.tv_name.setText(R.string.im_maker);
            vh.tv_content.setText(R.string.im_maker_content);
            vh.tv_conversation_time.setText("");
            break;
        case ImType.GREET_MSG:
            //对你感兴趣的人发来消息
            vh.aiv_img.setResource("", R.mipmap.ic_im_greet_conversation);
            vh.tv_name.setText(R.string.im_greet_msg);
            vh.tv_content.setText(R.string.im_greet_msg_default);
            vh.tv_conversation_time.setText("");
            break;
        case ImType.KEFU_ID:
            //客服小助手
            vh.aiv_img.setAvatar(uc.getIconUrl());
            vh.tv_name.setText(uc.getUIConversationTitle());
            vh.tv_content.setText(uc.getConversationContent().toString());
            vh.tv_conversation_time.setText(RongDateUtils.getConversationListFormatDate(uc.getUIConversationTime(), view.getContext()));
            break;

        default:
            //默认显示接口后台设置的,比如活动通知
            vh.aiv_img.setAvatar(uc.getIconUrl());
            vh.tv_name.setText(uc.getUIConversationTitle());
            vh.tv_content.setText(uc.getConversationContent().toString());
            vh.tv_conversation_time.setText(RongDateUtils.getConversationListFormatDate(uc.getUIConversationTime(), view.getContext()));
            break;
    }

    

}


@Override
public View newView(Context context, ViewGroup viewGroup) {
    View v = LayoutInflater.from(context).inflate(R.layout.im_item_conversation_list, (ViewGroup) null);
    ViewHolder vh = new ViewHolder();
    vh.aiv_img = v.findViewById(R.id.aiv_img);
    vh.tv_name = v.findViewById(R.id.tv_name);
    vh.tv_content = v.findViewById(R.id.tv_content);
    vh.unread = v.findViewById(R.id.tv_unread_count);
    vh.tv_conversation_time = v.findViewById(R.id.tv_conversation_time);
    v.setTag(vh);
    return v;
}


protected class ViewHolder {
    public AsyncImageView aiv_img;
    public TextView tv_name;
    public TextView tv_content;
    public TextView unread;
    public TextView tv_conversation_time;
}}

代码解析:因为我创的是system会话类别,所有需要@ConversationProviderTag(conversationType = “system”, portraitPosition = 3)

步骤4:注册Provider

RongIM.getInstance().registerConversationTemplate(new MyCusConversationProvider());

步骤5:点击事件

onConversationClick方法中获取targetId判断跳转即可

至此自定义列表会话就完成了,

作者 oshook
安卓技术 6 月 21,2023

SGridLayoutManager左右位置不一样

正常它是用在瀑布流的,就是用来因为高度不一致错位显示的。但我项目偏偏想正常排列,怎么弄都不行,原来是有控件被我隐藏了导致高度不一致。

也可以换GridLayoutManager试试

这其实没什么的,但一时没想到就改了很多东西。当做下记录,万一帮到其他朋友呢。

作者 oshook
安卓技术 6 月 21,2023

融云IM(二)—–提高推送到达率

这个都是可以直接看融云文档,我也是按照融云文档接,也没什么好的方法
要提高推送到达率必须接第三方的推送,融云有进一步的封装。

说下注意的点

  1. 融云后台是分开放环境和生产环境的,内容是否都填了。没有收到推送可以检查下
  2. 有些oppo和vivo手机是要给通知栏权限的
  3. 锤子手机是收不到推送的(如果锤子手机收到了推送,记得告诉下我)

下面给下通知栏权限是否已经允许了代码

/**
 * desc:询问通知栏是否开启,version>19才访问
 * create by cong on 2018/5/5 16:32
 */
@RequiresApi(api = Build.VERSION_CODES.KITKAT)
public static boolean isNotificationEnabled(Context context) {

    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
        //8.0手机以上
        if (((NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE)).getImportance() == NotificationManager.IMPORTANCE_NONE) {
            return false;
        }
    }

    String CHECK_OP_NO_THROW = "checkOpNoThrow";
    String OP_POST_NOTIFICATION = "OP_POST_NOTIFICATION";

    AppOpsManager mAppOps = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE);
    ApplicationInfo appInfo = context.getApplicationInfo();
    String pkg = context.getApplicationContext().getPackageName();
    int uid = appInfo.uid;

    Class appOpsClass = null;
    /* Context.APP_OPS_MANAGER */
    try {
        appOpsClass = Class.forName(AppOpsManager.class.getName());
        Method checkOpNoThrowMethod = appOpsClass.getMethod(CHECK_OP_NO_THROW, Integer.TYPE, Integer.TYPE,
                String.class);
        Field opPostNotificationValue = appOpsClass.getDeclaredField(OP_POST_NOTIFICATION);

        int value = (Integer) opPostNotificationValue.get(Integer.class);
        return ((Integer) checkOpNoThrowMethod.invoke(mAppOps, value, uid, pkg) == AppOpsManager.MODE_ALLOWED);

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


/**
 * desc: 跳转到应用程序信息界面,一般用于用户开启通知权限
 * Created by congge on 2018/7/18 14:58
 **/
public static void startAppDetailSetting(Context mContext) {
    Intent intent = new Intent();
    intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
    intent.setAction("android.settings.APPLICATION_DETAILS_SETTINGS");
    intent.setData(Uri.fromParts("package", mContext.getPackageName(), null));
    mContext.startActivity(intent);
}

这篇没什么干货。为了后期可以新增内容。

作者 oshook
安卓技术 6 月 21,2023

融云IM(一)—–接入

前言

1、融云IM应该说是目前最好自定义和易读取文档的即时通讯第三方SDK了,之前有用过阿里百川IM的,可惜阿里百川不再更新和维护了
2、im千万别选QQ的,如果就普通聊天选择qq那没问题,如果业务需要自定义的果断放弃。融云IM一直在快速的更新和维护。选它就没错了

步骤1:创建应用,导入SDK

创建应用:记得开发环境和生产环境是分开的,开发环境可生成的IM账号是有限的,有时im登录不成功是因为没账号.
导入SDK:强烈推荐以导入 Module 方式引入IMKit,IMLib。方便修改

步骤2:初始化

public class App extends Application {
    @Override
    public void onCreate() {
        super.onCreate();
        RongIM.init(this);
    }
}

步骤3:登录IM

从app服务器端获取token,然后调用RongIM.connect(token, new RongIMClient.ConnectCallback())即可
一般调用时机是在首页,看业务也可以在启动界面。
注意

  1. 调用成功一次就好
  2. 服务器获取的token,可以用SP保存下来,因为是长期有效的,不用每次都去获取

示例代码:

public static void connect() {
    if (Utils.isNetworkConnected(MyApplication.getInstance()))
        if (!TextUtils.isEmpty(SPUtils.getToken())) {
            connect(SPUtils.getToken(),null);
        } else {
            //正常只有第一次进入应用会为""
            new BaseIMPresenter().getToken(false, new EmptyTokenListener() {
                @Override
                public void getToken(String token) {
                    connect(token, null);
                }
            });
        }
}

/**
 * 

连接服务器,在整个应用程序全局,只需要调用一次,需在 {@link #init(Context)} 之后调用。

*

如果调用此接口遇到连接失败,SDK 会自动启动重连机制进行最多10次重连,分别是1, 2, 4, 8, 16, 32, 64, 128, 256, 512秒后。 * 在这之后如果仍没有连接成功,还会在当检测到设备网络状态变化时再次进行重连。

* * @param token 从服务端获取的用户身份令牌(Token)。 * @return RongIM 客户端核心类的实例。 */ private static void connect(String token, final ConnectListener cl) { if (BuildConfig.APPLICATION_ID.equals(MyApplication.getCurProcessName(MyApplication.getInstance()))) { RongIM.connect(token, new RongIMClient.ConnectCallback() { /** * Token 错误。可以从下面两点检查 * 1. Token 是否过期,如果过期您需要向 App Server 重新请求一个新的 Token * 2. token 对应的 appKey 和工程里设置的 appKey 是否一致 */ @Override public void onTokenIncorrect() { if (null != cl) cl.onTokenIncorrect(); new BaseIMPresenter().getToken(true, null); } /** * 连接融云成功 * @param userid 当前 token 对应的用户 id */ @Override public void onSuccess(String userid) { } /** * 连接融云失败 * @param errorCode 错误码,可到官网 查看错误码对应的注释 */ @Override public void onError(RongIMClient.ErrorCode errorCode) { } }); } }

额外提供IM是否在线的方法

public static boolean isOnline() {
    return RongIM.getInstance().getCurrentConnectionStatus().equals(RongIMClient.ConnectionStatusListener.ConnectionStatus.CONNECTED);
}

步骤4: 会话列表界面

就是用IMKit中的ConversationListFragment,可以认为就是个fragment。正常用就会显示,后期会介绍在这fragment中新增些自定义的会话
注意点:

它是用uri来设置你要显示的类型
如果本身就是显示会话列表界面需要在清单中配置(可参考融云文档)

示例代码:

public Fragment initConversationList() {
    if (mListFragment == null) {
        mListFragment = new ConversationListFragment();

        uri = Uri.parse("rong://" + BuildConfig.APPLICATION_ID).buildUpon()
                .appendPath("conversationlist")
                .appendQueryParameter(Conversation.ConversationType.PRIVATE.getName(), "false")
                .appendQueryParameter(Conversation.ConversationType.SYSTEM.getName(), "false")
                .build();
        mListFragment.setUri(uri);
    }
    return mListFragment;
}

步骤5:会话界面

用的是IMkit中的ConversationFragment。有targetId就能显示,targetId就是对方的IM账号

示例代码:

private void enterFragment(Conversation.ConversationType mConversationType, String toUserIm) {
    fragment = new ConversationFragment();
    uri = Uri.parse("rong://" + BuildConfig.APPLICATION_ID).buildUpon()
            .appendPath("conversation")
            .appendPath(mConversationType.getName().toLowerCase())
            .appendQueryParameter("targetId", toUserIm).build();
    fragment.setUri(uri);
    FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
    //xxx 为你要加载的 id
    transaction.add(R.id.rong_content, fragment);
    transaction.commitAllowingStateLoss();
    showRealView();
}

在 AndroidManifest.xml 中,会话 Activity 下面配置 intent-filter。 注意请修改 android:host 为 App 的 ApplicationId,其他保持不变。


 
 

 

 
 
  

ApplicationId可以不改,在build.gradle里面配置
manifestPlaceholders = [ APPLICATIONID: applicationId]
启动会话界面
示例代码:

public static void startChattingActivity(Context context, Conversation.ConversationType conversationType, String targetId, String title) {
    if (context != null && !TextUtils.isEmpty(targetId) && conversationType != null) {
        Uri uri = Uri.parse("rong://" + BuildConfig.APPLICATION_ID).buildUpon()
                .appendPath("conversation")
                .appendPath(conversationType.getName().toLowerCase(Locale.US))
                .appendQueryParameter("targetId", targetId)
                .appendQueryParameter("title", title)
             
                .build();
        context.startActivity(new Intent("android.intent.action.VIEW", uri));
    } else {
        throw new IllegalArgumentException();
    }
}

title是用来显示标题的,当然会话界面就需要做获取代码了。

还有混淆啥的,可参考官网

好了,到这里接入IM,并且建立聊天就完成了。有问题可加我QQ:893151960或者群142739277
先说明:解决问题可能收费的,讨论问题免费

作者 oshook
安卓技术 6 月 21,2023

PopupWindow的偏移

先看设置方法

popupWindow.showAsDropDown(v,x,y)

正常设置就可以了。y值一般是正常的,有时设置x值没效果,一直靠边,这是为什么呢。
先普及个基本知识

1、showAsDropDown 的对齐方式是v控件的左下角与popupWindow的控件左上角对齐
2、popupWindow不会超出屏幕,所以显示效果是紧贴右边框

1知识往往大家都知道,2就有可能不知道。

x,y值可以是负数的。还是没效果或者有问题,把x,y值设大点就知道了。好了就这样,就一小东西。

作者 oshook
安卓技术 6 月 21,2023

接入华为支付

1、华为支付相对微信支付又复杂点,同样包名,签名,appId都必须正确,不能修改
2、配置内容也多点,不过基本按照文档说明认真配置也是没问题的
3、华为支付是没demo源码参考,只有文档的示例代码,其他第三方一般都会有demo的

官方开发文档

https://developer.huawei.com/consumer/cn/service/hms/catalog/huaweiiap_oversea.html?page=hmssdk_huaweiiap_devprepare_oversea

按照文档一步步来,千万不要跳步骤,要不出问题不好排查
可能出问题的地方
在清单的application节点下增加APPID

  
    android:value="appid=xxx">  
 

记住里面是appid=xxx而不是去掉xxx

初始化Agent

1.在Application类中的onCreate方法中初始化HMS Agent套件

public class MyApplication extends Application {
    @Override
    public void onCreate() {        

            super.onCreate();
            HMSAgent.init(this);
    }} 

2.请务必在应用启动后的首个activity的onCreate方法中调用connect接口,确保HMS SDK和HMS APK的连接

HMSAgent.connect(this, new ConnectHandler() {
@Override
public void onConnect(int rst) {
    showLog("HMS connect end:" + rst);
}});    

注意是启动的第一个activity,要不会上架审核不成功的

调起华为支付

关键方法HuaweiPay.HuaweiPayApi.pay();
只好建个中间的Activity来集成,因为支付返回结果都是在onActivityResult获取。如果只有一个界面有华为支付的,就没必要了。
中间华为支付activity
示例代码:

public  abstract class HuaWeiActivity> extends BaseActivity implements ISetHw {
private static final String TAG = HuaWeiActivity.class.getName();
public HuaweiApiClient client;
private String outTradeNo;

//启动参数,区分startactivityforresult的处理结果
private final int REQ_CODE_PAY = 4001;
//作用同startactivityforresult方法中的requestcode
private static final int REQUEST_HMS_RESOLVE_ERROR = 1000;
private ShowPayDialog showPayDialog;

//初始化init华为支付
private void initClient() {

    //-------------------华为支付----------------------
    if(null == client){
        client = new HuaweiApiClient.Builder(this)
                .addApi(HuaweiPay.PAY_API)
                .addOnConnectionFailedListener(this)
                .addConnectionCallbacks(this)
                .build();
    }

    client.connect(this);
}


@Override
public void onResume() {
    super.onResume();
    initClient();
}

@Override
protected void onDestroy() {
    super.onDestroy();
    if (null != client) {
        client.disconnect();
    }
}

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    super.onActivityResult(requestCode, resultCode, data);
    LogUtils.i("reCode", requestCode + " " + resultCode + "");
    if (requestCode == REQUEST_HMS_RESOLVE_ERROR) {
        if (resultCode == Activity.RESULT_OK) {
            int result = data.getIntExtra(EXTRA_RESULT, 0);
            if (result == ConnectionResult.SUCCESS) {
                LogUtils.i(TAG, "错误成功解决");
                if (!client.isConnecting() && !client.isConnected()) {
                    client.connect(this);
                }
            } else if (result == ConnectionResult.CANCELED) {
                LogUtils.i(TAG, "解决错误过程被用户取消");
            } else if (result == ConnectionResult.INTERNAL_ERROR) {
                LogUtils.i(TAG, "发生内部错误,重试可以解决");
                //CP可以在此处重试连接华为移动服务等操作,导致失败的原因可能是网络原因等
            } else {
                LogUtils.i(TAG, "未知返回码");
            }
        } else {
            LogUtils.i(TAG, "调用解决方案发生错误");
        }
    } else if (requestCode == REQ_CODE_PAY) {
        //当返回值是-1的时候表明用户支付调用成功
        if (resultCode == Activity.RESULT_OK) {
            //获取支付完成信息
            PayResultInfo payResultInfo = HuaweiPay.HuaweiPayApi.getPayResultInfoFromIntent(data);
            if (payResultInfo != null) {
                Map paramsa = new HashMap();
                if (PayStatusCodes.PAY_STATE_SUCCESS == payResultInfo.getReturnCode()) {
                    

                    //mHuaWeiPayPresenter.getHwPayNotify(b_tag, payResultInfo.getRequestId(), payResultInfo.getReturnCode(), success);
                    payHwSuccessNotify(payResultInfo.getReturnCode());
                    //paySuccess();
                    closeDialog();

                } else if (PayStatusCodes.PAY_STATE_CANCEL == payResultInfo.getReturnCode()) {
                    //支付失败,原因是用户取消了支付,可能是用户取消登录,或者取消支付
                    Log.i(TAG, "支付失败:用户取消" + payResultInfo.getErrMsg());
                    payHwFailNotify();

                } else {
                    //支付失败,其他一些原因
                    Log.i(TAG, "支付失败:" + payResultInfo.getErrMsg() + payResultInfo.getReturnCode());
                    payHwFailNotify();

                }
            } else {
                //支付失败
                payHwFailNotify();
            }
        } else {
            //当resultCode 为0的时候表明用户未登录,则CP可以处理用户不登录事件
            Log.i(TAG, "resultCode为0, 用户未登录 CP可以处理用户不登录事件");

        }
    }
}

protected abstract void payHwSuccessNotify(int payCode);
protected abstract void payHwFailNotify();

@Override
public void onConnected() {

}

@Override
public void onConnectionSuspended(int i) {
    //HuaweiApiClient异常断开连接, if 括号里的条件可以根据需要修改
    if (!this.isDestroyed() && !this.isFinishing()) {
        client.connect(this);
    }
    LogUtils.i(TAG, "HuaweiApiClient 连接异常断开成功");
}

@Override
public void onConnectionFailed(ConnectionResult connectionResult) {
    LogUtils.i(TAG, "HuaweiApiClient连接失败,错误码:" + connectionResult.getErrorCode());
    if (HuaweiApiAvailability.getInstance().isUserResolvableError(connectionResult.getErrorCode())) {
        final int errorCode = connectionResult.getErrorCode();
        new Handler(getMainLooper()).post(new Runnable() {
            @Override
            public void run() {
                // 此方法必须在主线程调用, xxxxxx.this 为当前界面的activity
                HuaweiApiAvailability.getInstance().resolveError(HuaWeiActivity.this, errorCode, REQUEST_HMS_RESOLVE_ERROR);
            }
        });
    } else {
        //其他错误码请参见API文档
    }
}

/**
 * 发起支付流程,开发者可以直接参照该方法写法
 */
protected void hwPay(HashMap params, String sign) {
    if (!client.isConnected()) {
        LogUtils.i(TAG, "支付失败,原因:HuaweiApiClient未连接");
        client.connect(this);
        return;
    }
    PendingResult payResult = HuaweiPay.HuaweiPayApi.pay(client, HwPayUtils.createPayReq(params, sign));
    payResult.setResultCallback(new PayResultCallback());
}

/**
 * 弹框相关
 *
 * @param showPayDialog
 */
public void setShowDialog(ShowPayDialog showPayDialog) {
    this.showPayDialog = showPayDialog;
}

/**
 * 支付接口调用的回调处理
 * 只有当处理结果中的返回码为 PayStatusCodes.PAY_STATE_SUCCESS的时候,CP需要继续调用支付
 * 否则就需要处理支付失败结果
 */
private class PayResultCallback implements ResultCallback {

    @Override
    public void onResult(PayResult result) {
        //支付鉴权结果,处理result.getStatus()
        Status status = result.getStatus();
        if (PayStatusCodes.PAY_STATE_SUCCESS == status.getStatusCode()) {
            //当支付回调 返回码为0的时候,表明支付流程正确,CP需要调用startResolutionForResult接口来进行后续处理
            //支付会先判断华为帐号是否登录,如果未登录,会先提示用户登录帐号。之后才会进行支付流程
            try {
                status.startResolutionForResult(HuaWeiActivity.this, REQ_CODE_PAY);

            } catch (IntentSender.SendIntentException e) {
                LogUtils.i(TAG, "启动支付失败" + e.getMessage());

            }
        } else {
            LogUtils.i(TAG, "支付失败,原因 :" + status.getStatusCode());

        }
    }
}



public void closeDialog() {
    if (null != showPayDialog) {
        showPayDialog.dialogDismiss();
    }
}}

仅仅是示例,把不用的删掉即可,
其中HuaweiPay.HuaweiPayApi.pay是调起华为支付,
PayResultCallback是是否能正常调起华为支付,
onActivityResult是调起后支付成功还是失败

HwPayUtils类

public class HwPayUtils {

private static final String TAG = "HwPayUtils";

/**
 * 获取华为的appId
 *
 * @param context
 * @return
 */
public static String getAppId(Context context) {
    String value = "";
    try {
        ApplicationInfo appInfo = context.getPackageManager().getApplicationInfo(context.getPackageName(),
                PackageManager.GET_META_DATA);
        value = appInfo.metaData.getString("com.huawei.hms.client.appid");
        String[] appidValue = value.split("=");
        return appidValue[appidValue.length-1];
    } catch (PackageManager.NameNotFoundException e) {
        e.printStackTrace();
    }
    return value;
}

/**
 * 生成支付信息map 包含
 * HwPayConstant.KEY_MERCHANTID  必选参数 商户id,开发者联盟网站生成的支付ID
 * HwPayConstant.KEY_APPLICATIONID 必选参数 应用的appid,开发者联盟网站生成
 * HwPayConstant.KEY_AMOUNT 必选参数 支付金额 string类型,精确到小数点后2位 比如 20.00
 * HwPayConstant.KEY_PRODUCTNAME 必选参数 商品名称 此名称将会在支付时显示给用户确认 注意:该字段中不能包含特殊字符,包括# " & / ? $ ^ *:)   ,
 * HwPayConstant.KEY_PRODUCTDESC 必选参数 商品描述 注意:该字段中不能包含特殊字符,包括# " & / ? $ ^ *:)   , |
 * HwPayConstant.KEY_REQUESTID  必选参数 请求订单号。其值由商户定义生成,用于标识一次支付请求,每次请求需唯一,不可重复。
 * 支付平台在服务器回调接口中会原样返回requestId的值。注意:该字段中不能包含特殊字符,包括# " & / ? $ ^ *:)   , .以及中文字符
 * HwPayConstant.KEY_SDKCHANNEL 必选参数 渠道信息。 取值如下:0 代表自有应用,无渠道 1 代表应用市场渠道 2 代表预装渠道 3 代表游戏中心渠道
 * HwPayConstant.KEY_URLVER 可选参数  回调接口版本号。如果传值则必须传2, 额外回调信息,具体参考接口文档
 * HwPayConstant.KEY_URL 可选参数 支付结果回调URL. 华为服务器收到后检查该应用有无在开发者联盟配置回调URL,如果配置了则使用应用配置的URL,否则使用此url
 * 作为该次支付的回调URL,建议直接 以配置在 华为开发者联盟的回调URL为准
 * HwPayConstant.KEY_COUNTRY 可选参数 国家码.建议无特殊需要,不传
 * HwPayConstant.KEY_CURRENCY 可选参数 币种 选填.建议无特殊需要不传此参数。目前仅支持CNY,默认CNY
 */
public static HashMap getPayInfo(PayHwOrderResult result) {
    HashMap params = new HashMap();
    PayHwOrderResult.DataBean obj = result.getData();

    params.put(HwPayConstant.KEY_MERCHANTID, obj.getMerchantId());
    params.put(HwPayConstant.KEY_APPLICATIONID, obj.getAppId());

    params.put(HwPayConstant.KEY_AMOUNT, obj.getAmount());
    params.put(HwPayConstant.KEY_PRODUCTNAME, obj.getProductName());
    params.put(HwPayConstant.KEY_PRODUCTDESC, obj.getProductDesc());

    params.put(HwPayConstant.KEY_REQUESTID, obj.getOutTradeNo());
    params.put(HwPayConstant.KEY_SDKCHANNEL, obj.getSdkChannel());
    params.put(HwPayConstant.KEY_URLVER, obj.getUrlver());
    params.put(HwPayConstant.KEY_URL, obj.getUrl());

    //不需要签名参数
    params.put(HwPayConstant.KEY_MERCHANTNAME, obj.getMerchantName());
    params.put(HwPayConstant.KEY_SERVICECATALOG, obj.getServiceCatalog());
    params.put(HwPayConstant.KEY_EXTRESERVED, obj.getExtReserved());
    return params;
}


/**
 * 封装json参数给后台
 *
 * @param params
 * @return
 */
public static String paramJson(HashMap params) {
    String value = "";
    try {
        JSONObject mJsonobjData = new JSONObject();
        mJsonobjData.put(HwPayConstant.KEY_MERCHANTID, params.get(HwPayConstant.KEY_MERCHANTID));
        mJsonobjData.put(HwPayConstant.KEY_APPLICATIONID, params.get(HwPayConstant.KEY_APPLICATIONID));
        mJsonobjData.put(HwPayConstant.KEY_AMOUNT, params.get(HwPayConstant.KEY_AMOUNT));
        mJsonobjData.put(HwPayConstant.KEY_PRODUCTNAME, params.get(HwPayConstant.KEY_PRODUCTNAME));
        mJsonobjData.put(HwPayConstant.KEY_PRODUCTDESC, params.get(HwPayConstant.KEY_PRODUCTDESC));

        mJsonobjData.put(HwPayConstant.KEY_REQUESTID, params.get(HwPayConstant.KEY_REQUESTID));
        mJsonobjData.put(HwPayConstant.KEY_SDKCHANNEL, params.get(HwPayConstant.KEY_SDKCHANNEL));
        mJsonobjData.put(HwPayConstant.KEY_URLVER, params.get(HwPayConstant.KEY_URLVER));
        mJsonobjData.put(HwPayConstant.KEY_URL, params.get(HwPayConstant.KEY_URL));

        value = mJsonobjData.toString();

    } catch (Exception e) {
        // TODO: handle exception
        e.printStackTrace();
    }

    return value;
}

/**
 * 生成PayReq对象,用来在进行支付请求的时候携带支付相关信息
 * payReq订单参数需要商户使用在华为开发者联盟申请的RSA私钥进行签名,强烈建议将签名操作在商户服务端处理,避免私钥泄露
 */
public static PayReq createPayReq(HashMap params, String sign) {

    PayReq payReq = new PayReq();

    //商品名称
    payReq.productName = (String) params.get(HwPayConstant.KEY_PRODUCTNAME);
    //商品描述
    payReq.productDesc = (String) params.get(HwPayConstant.KEY_PRODUCTDESC);
    // 商户ID:来源于开发者联盟的“支付ID”
    payReq.merchantId = (String) params.get(HwPayConstant.KEY_MERCHANTID);
    // 应用ID
    payReq.applicationID = (String) params.get(HwPayConstant.KEY_APPLICATIONID);
    // 支付金额
    payReq.amount = (String) params.get(HwPayConstant.KEY_AMOUNT);
    // 商户订单号:开发者在支付前生成,用来唯一标识一次支付请求
    payReq.requestId = (String) params.get(HwPayConstant.KEY_REQUESTID);
    // 渠道号
    payReq.sdkChannel = (Integer) params.get(HwPayConstant.KEY_SDKCHANNEL);
    // 回调接口版本号
    payReq.urlVer = (String) params.get(HwPayConstant.KEY_URLVER);
    payReq.url = (String) params.get(HwPayConstant.KEY_URL);
    LogUtils.i("hwPayUrl",payReq.url);
    LogUtils.i(TAG, payReq.productName + " " + payReq.productDesc + " " + payReq.merchantId + " " + payReq.applicationID + " "
            + payReq.amount + " " + payReq.requestId + " " + payReq.sdkChannel + " " + payReq.getUrlVer());
    //以上信息按照一定规则进行签名,建议CP在服务器端储存签名私钥,并在服务器端进行签名操作。

    payReq.sign = sign;

    // 商户名称,必填,不参与签名。开发者注册的公司名
    payReq.merchantName = (String) params.get(HwPayConstant.KEY_MERCHANTNAME);

    //分类,必填,不参与签名。该字段会影响风控策略
    // X4:主题,X5:应用商店,  X6:游戏,X7:天际通,X8:云空间,X9:电子书,X10:华为学习,X11:音乐,X12 视频,
    // X31 话费充值,X32 机票/酒店,X33 电影票,X34 团购,X35 手机预购,X36 公共缴费,X39 流量充值
    payReq.serviceCatalog = (String) params.get(HwPayConstant.KEY_SERVICECATALOG);
    //商户保留信息,选填不参与签名,支付成功后会华为支付平台会原样 回调CP服务端
    payReq.extReserved = (String) params.get(HwPayConstant.KEY_EXTRESERVED);

    return payReq;
}


/**
 * 将商户id,应用id, 商品名称,商品说明,支付金额,订单号,渠道号,回调地址版本号等信息按照key值升序排列后
 * 以key=value并以&的方式连接起来生成待签名的字符串
 *
 * @return
 */
public static String getNoSign(Map params) {
    //对参数按照key做升序排序,对map的所有value进行处理,转化成string类型
    //拼接成key=value&key=value&....格式的字符串
    StringBuffer content = new StringBuffer();
    // 按照key做排序
    List keys = new ArrayList(params.keySet());
    Collections.sort(keys);
    String value = null;
    Object object = null;
    for (int i = 0; i 
作者 oshook

上一 1 2 3 4 … 7 下一个

Copyright © 2023 | 粤ICP备14006518号-3