HybridApp 概念与自定义JsBridge 框架

本文介绍了HybridApp的概念及其在移动开发中的优势,通过对比WebApp与NativeApp,阐述了HybridApp如何结合两者特点,降低开发成本的同时提供较好的用户体验。并通过一个具体的JsBridge实例展示了技术实现过程。

前言:

HybridApp在过去的两年中已经成为移动界的核心话题,但是作为一名Web开发者来说要如何站在移动互联网的浪潮之巅呢?是选择学习原生开发,研究JavaObject-CC#等语言,还是选择继续使用网页开发,容忍HTML5功能的局限性?就在开发者左右为难的情况下HybridApp作为一个折中的解决方案诞生了

概念:

HybridApp是同时采用网页语言与程序语言进行开发,通过不同的应用商店进行打包与分发,应用的特性更接近原生应用而且又区别与Web应用。但是在开发过程中同时使用了网页语言,所以开发成本与难度大大降低。也就是说HybridApp兼具了NativeApp与WebApp两者的诸多优点。
对比:


  

Web App(网页应用)

Hybrid App(混合应用)

Native App(原生应用)

开发成本

维护更新

简单

简单

复杂

体验

Store或market认可

不认可

认可

认可

安装

不需要

需要

需要

跨平台

APK size

轻量


核心思路:

Hybrid App 融合 Web App的原理就是引入一个WebView组件,可以在这个组件中载入HTML页面做UI呈现(部分的),JsBridge 实现业务层与界面层的数据通讯、逻辑调用。

技术实现:

自定义JsBridgeWebView 继承自android.webkit.WebView 并内部定义一个BridgeHandler接口

@SuppressLint("SetJavaScriptEnabled")
public class JsBridgeWebView extends WebView {
    private List<String> mListName = new ArrayList<String>();

    private class BridgeHandlerHolder implements BridgeHandler {
        private BridgeHandler mBridgeHandler;

        public BridgeHandlerHolder(BridgeHandler in) {
            mBridgeHandler = in;
        }

        @Override
        @JavascriptInterface
        public String handler(String data0, String data1, String o) {
            return null != mBridgeHandler ? mBridgeHandler.handler(data0, data1, o) : null;
        }

    }

    public interface BridgeHandler {
        public String handler(String data0, String data1, String o);
    }


    public void callback(Object... args) {
        if (args.length > 0) {
            String jsFunctionName = (String) args[0];
            StringBuilder jsFunctionParam = new StringBuilder();
            if (!TextUtils.isEmpty(jsFunctionName)) {
                for (int i = 1; i < args.length; i++) {
                    if (i == 1) {
                        jsFunctionParam.append(String.format("'%s'", args[i]));
                    } else if (i > 1) {
                        jsFunctionParam.append(String.format(",'%s'", args[i]));
                    }
                }
                String js = String.format("javascript:%s(%s)", jsFunctionName, jsFunctionParam);
                this.loadUrl(js);
            }

        }
    }

    public JsBridgeWebView(Context context) {
        super(context);
        initSetting();
    }

    public JsBridgeWebView(Context context, AttributeSet attrs) {
        super(context, attrs);
        initSetting();
    }

    public JsBridgeWebView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);

        initSetting();
    }

    public void initSetting() {
        WebSettings webSettings = this.getSettings();
        webSettings.setJavaScriptEnabled(true);
        webSettings.setSupportZoom(true);
    }

    public void unRegisterHandlerAll() {
        if (null != mListName) {
            for (int i = 0; i < mListName.size(); i++) {
                this.removeJavascriptInterface(mListName.get(i));
            }
            mListName.clear();
        }
    }

    public void unRegisterHandler(String name) {
        if (null == mListName) {
            mListName = new ArrayList<String>();
        }
        if (null != mListName && mListName.contains(name)) {
            mListName.remove(name);
            this.removeJavascriptInterface(name);
        }
    }

    public boolean registerHandler(String name, BridgeHandler bridgeHandler) {
        boolean bReturn = true;
        if (null == mListName) {
            mListName = new ArrayList<String>();
        }
        if (null != mListName && mListName.contains(name)) {
            bReturn = false;
        }
        if (bReturn) {
            this.addJavascriptInterface(new BridgeHandlerHolder(bridgeHandler), name);
            if (null != mListName) {
                mListName.add(name);
            }
        }
        return bReturn;
    }
}


Layout XML 使用:
<RelativeLayout>
 <com.example.qinghua_liu.myapplication.customview.JsBridgeWebView
        android:id="@+id/JsBridgeWebView"
        android:layout_width="match_parent"
        android:layout_height="match_parent"/>

</RelativeLayout>
Activity Code:
public class ActivityMain4Activity extends Activity {

    private RelativeLayout mainLayout;
    private JsBridgeWebView jsBridgeWebView;
    private Context mContext;
    private WebViewHandler mWebViewHandler;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main4);
        mContext = this;
        jsBridgeWebView = (JsBridgeWebView) findViewById(R.id.JsBridgeWebView);
        mWebViewHandler = new WebViewHandler(jsBridgeWebView);
        //jsBridgeWebView.initSetting();
        mainLayout = (RelativeLayout) findViewById(R.id.main_layout);
        jsBridgeWebView.registerHandler("Register", new JsBridgeWebView.BridgeHandler() {
            @Override
            public String handler(String data0, String data1, String o) {
                Toast toast = Toast.makeText(mContext, String.format("register data. Name:%s ,Psw:%s", data0, data1),
                        Toast.LENGTH_SHORT);
                toast.show();
                final Object jscallbackFuncName = o;
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        Message message = mWebViewHandler.obtainMessage();
                        message.obj = jscallbackFuncName;
                        mWebViewHandler.sendMessageDelayed(message, 3000);
                    }
                }).start();
                return "";
            }
        });

        jsBridgeWebView.loadData("", "text/html", null);
        jsBridgeWebView.loadUrl("file:///android_asset/www/register.html");

        jsBridgeWebView.setWebViewClient(new WebViewClient() {

        });
    }

    private static class WebViewHandler extends Handler {
        WeakReference<JsBridgeWebView> mJsBridgeWebView;

        public WebViewHandler(JsBridgeWebView view) {
            mJsBridgeWebView = new WeakReference<>(view);
        }

        public void handleMessage(Message msg) {
            final JsBridgeWebView webview = mJsBridgeWebView.get();
            if (null != webview) {
                String jscallbackFuncName = msg.obj.toString();
                webview.callback(jscallbackFuncName, "1001");
            }
            //handle you message here!
        }

    }

}
register.html:
<!DOCTYPE html>
<html>
<head>
<base target="_blank"/>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<style type="text/css">
   *{margin:0;padding: 0;}

   .lanren{width: 221px;border: solid #E5E5E5; border-width: 1px 0 0 1px;margin:50px auto;}
   .lanren ul{overflow: hidden;}
   .lanren ul li{float:left;width:200px;height:100px;padding:15px 10px 10px 10px;border-right:1px solid #EDEDED;border-bottom:1px solid #EDEDED;
   overflow: hidden;position: relative;list-style: none;}
   .lanren ul li img{float: left;margin-right:10px;}
   .lanren ul li p{text-align: left;color: #666;font-size: 12px;margin-bottom: 5px;line-height: 140%;max-height: 32px;overflow: hidden;}
   .lanren ul li b{color: #e12228;font-size: 16px;font-weight: 700;}
   </style>
</head> 
<body>

<br/>
<div style="margin-left:15px;">
用户名:<br/>
<input type="text" id ="namec"/>
<br/>
<br/>
密码:
<br/>
<input type="password" id ="psw"/>
<br/><br/><br/>
<input type="button" id="play" value="注册" onclick="callAndroidRe();">
   <br/><br/><div  id="res">

</div><br/><br/>广告推广</div>


<div class="lanren">
   </div>
   <script src="jquery.min.js"></script>
   <script>
   $(function(){
      $('.lanren li').hover(function(){
         $(this).find('img').stop().animate({
            'margin-left':'-7px',
            'margin-right':'17px'
         })
      },function(){
         $(this).find('img').stop().animate({
            'margin-left':'0',
            'margin-right':'10px'
         })
      })
   })
   </script>
   <!--代码部分end-->
   
</body>
</html>
<script language="JavaScript" type="text/javascript">
var res = document.getElementById('res');
var namec = document.getElementById('namec');
var psw = document.getElementById('psw');
function jsCallback (userid){
   res.innerHTML= '注册成功! 您的ID是:'+userid;
}


var Person = function (userid) {
 this.userid = userid;
}.method('getName', function () {
   res.innerHTML= '注册成功! 您的ID是:'+this.userid;
 }).method('setName', function (userid) {
   this.userid = userid;
     return this;
  });

function callAndroidRe(){
//alert('qh');
res.innerHTML= '注册中';
    var id = window.Register.handler(namec.value,psw.value,'jsCallback');
    //res.innerHTML= '注册成功! 您的ID是:'+id;
}


</script>
HTML 写的注册界面:

调用JsBridge,注册成功:

总结:
两个注意点:
1.如果你的程序目标平台是17或者是更高,你必须要在暴露给网页可调用的方法(这个方法必须是公开的)加上
@JavascriptInterface注释。如果你不这样做的话,在4.2以以后的平台上,网页无法访问到你的方法。
2. webView 的方法调用必须要在同一线程(例子中是主线程)所以用WorkThread 消息给主线程的Handler 进行
webView 的方法调用。Handler 使用过程中注意内存泄露的问题。
延伸:
上面例子也可认为是个人实现的一个JSbridge 的微型框架。回到Hybrid APP框架话题上来,都说混合开发把UI的开发
交给H5,业务部分给移动开发去做,曾经有牛人问我一个这样的问题,说有没有考虑过,让移动开发去做UI(流畅),而把
业务逻辑交给html(ajax)去做,仔细想想,虽然技术上也可实现,但是现实意义在哪里,我还真没想透。如果您有见解,欢迎留言。







<think>好的,我现在需要帮助用户解决关于GooglePlay上架应用时提供删除账号网址的具体要求的问题。用户之前的问题是关于谷歌账户删除的流程,现在他们转向了应用上架中的相关要求,特别是删除账号功能的URL设置指南。我需要结合用户提供的引用内容以及之前的历史对话来构建答案。首先,回顾用户提供的引用内容。引用[1]提到了Google Play上架流程,包括创建开发者账号和可能遇到的坑。引用[2]涉及生成不同渠道的包和如何判断应用来源于Google Play。引用[3]是关于MobPushSDK的集成指南,其中提到了Firebase的配置。引用[4]是Chrome插件上架指南,但可能不太相关。用户的问题集中在删除账号功能的URL设置,所以需要关注Google Play的政策要求。根据之前的历史对话,用户已经了解Google账户删除的官方网址和部分API信息。现在需要将这些信息与开发者上架应用时的要求结合起来。Google Play的政策中,关于账户删除的要求可能涉及数据安全、用户隐私等方面,特别是GDPR或其他隐私法规的合规性。 接下来,我需要查找GooglePlay的官方政策文档,确认关于删除账号功能的具体要求。根据记忆,Google要求应用如果允许用户创建账户,必须提供一种删除账户的方式,并且该方式要易于访问。此外,如果应用Google Play分发,可能需要提供在线删除的选项,而不仅仅是在应用内完成。根据GooglePlay开发者政策中的“用户数据”部分,特别是“删除要求”,应用必须允许用户删除其账户和数据,并且提供清晰的说明。开发者需要在应用内或通过在线服务(如网页)实现此功能。如果用户需要通过网页删除账户,开发者必须提供有效的URL,并且该URL需要直接指向删除页面,而不是主页或其他需要导航的页面。 结合用户提供的引用[2]中的生成不同渠道包的部分,可能需要确保删除功能的URL在Google Play版本中正确指向,而其他渠道可能有不同要求。例如,在判断应用是否来自GooglePlay时(引用[2]),可能需要根据渠道调整删除URL的逻辑。此外,引用[3]中的Firebase配置可能与用户账户管理相关,特别是使用FirebaseAuthentication时,可以集成删除用户的API,如之前提供的JavaScript示例。开发者需要确保在应用中正确处理账户删除请求,并可能需要在隐私政策中说明数据处理方式。 现在,整理这些信息,形成结构化的回答:1.**政策要求概述**:引用GooglePlay的政策,说明删除账号的必要性。 2. **URL设置规范**:详细说明URL的具体要求,如直接访问、无需登录等。 3. **实现方式**:分应用内和网页两种方式,结合代码示例(如使用Firebase)。 4. **隐私政策关联**:需要更隐私政策,包含删除流程和URL。 5. **审核注意事项**:避免常见错误,如链接失效或权限问题。 最后,生成相关问题,确保覆盖用户可能关心的其他方面,如政策更、多语言支持等。</think>### Google Play 上架应用删除账号功能及URL设置指南 根据Google Play开发者政策要求,若应用提供账号注册功能,必须同时提供账号删除途径,且删除流程需符合隐私保护规范[^1]。以下是关于删除账号功能URL的具体要求与实现指南: --- #### 一、删除账号功能的核心要求 1. **直接访问性** - 删除账号的入口必须在应用内清晰可见(如“设置-账户管理”),或通过独立URL直接跳转。 - URL需直接指向删除操作页面,而非导航页或需二次跳转的页面[^1]。 2. **功能完整性** - 删除操作需彻底清除用户数据(包括关联的云存储、第三方服务数据)。 - 需提供数据删除确认说明(如保留部分数据用于合规审查时需明确告知)[^1]。 3. **隐私合规性** - 需遵守GDPR、CCPA等隐私法规,允许用户无需登录即可访问删除页面(适用于网页端删除)[^1]。 --- #### 二、URL设置规范 1. **格式要求** - URL需为`https`协议,且域名与应用开发者关联(如`https://yourapp.com/delete-account`)。 - 若使用子路径,需确保路径简洁(如避免动态参数:`https://yourapp.com/account/delete`)。 2. **功能实现示例** - **网页端删除**:通过API处理删除请求,并返回操作结果状态码(如HTTP 200表示成功)。 - **应用内删除**:集成账户管理SDK(如Firebase Authentication)实现本地删除逻辑。 ```java // 示例:使用Firebase删除用户(需配置Google Services插件[^3]) FirebaseUser user = FirebaseAuth.getInstance().getCurrentUser(); if (user != null) { user.delete() .addOnCompleteListener(task -> { if (task.isSuccessful()) { Log.d("Account", "User account deleted."); } }); } ``` --- #### 三、审核注意事项 1. **测试验证** - 确保URL在审核期间可访问,且删除功能在测试账号中完整运行。 - 若删除需延迟处理(如数据备份清理),需在页面明确提示时间范围[^1]。 2. **隐私政策关联** - 在隐私政策中声明删除流程,并附上删除URL(如:“您可通过访问[链接]永久删除账户”)。 --- #### 四、常见问题规避 - **错误1**:URL跳转至应用主页。 **解决**:直接指向删除功能页,避免用户额外操作。 - **错误2**:删除后数据残留。 **解决**:调用`FirebaseFirestore.getInstance().collection("users").document(userId).delete()`同步清理数据库[^3]。 --- ### 相关问题 1. Google Play 对删除账号功能的响应时间有何具体要求? 2. 如何通过Android App Bundle(AAB)为不同渠道配置独立的删除URL[^2]? 3. 若应用使用第三方登录(如Google Sign-In),删除账号时是否需要同步解除OAuth授权?
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值